@conduction/nextcloud-vue 1.0.0-beta.20 → 1.0.0-beta.22

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.
@@ -43033,7 +43033,7 @@ var isObject = function isObject(obj) {
43033
43033
  }; // Strict object type check
43034
43034
  // Only returns true for plain JavaScript objects
43035
43035
 
43036
- var isPlainObject$2 = function isPlainObject(obj) {
43036
+ var isPlainObject$3 = function isPlainObject(obj) {
43037
43037
  return Object.prototype.toString.call(obj) === '[object Object]';
43038
43038
  };
43039
43039
  var isDate = function isDate(value) {
@@ -43115,7 +43115,7 @@ var cloneDeep = function cloneDeep(obj) {
43115
43115
  }, []);
43116
43116
  }
43117
43117
 
43118
- if (isPlainObject$2(obj)) {
43118
+ if (isPlainObject$3(obj)) {
43119
43119
  return keys(obj).reduce(function (result, key) {
43120
43120
  return _objectSpread$8(_objectSpread$8({}, result), {}, _defineProperty$b({}, key, cloneDeep(obj[key], obj[key])));
43121
43121
  }, {});
@@ -43323,7 +43323,7 @@ var kebabCase = function kebabCase(str) {
43323
43323
 
43324
43324
  var toString = function toString(val) {
43325
43325
  var spaces = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 2;
43326
- return isUndefinedOrNull(val) ? '' : isArray(val) || isPlainObject$2(val) && val.toString === Object.prototype.toString ? JSON.stringify(val, null, spaces) : String(val);
43326
+ return isUndefinedOrNull(val) ? '' : isArray(val) || isPlainObject$3(val) && val.toString === Object.prototype.toString ? JSON.stringify(val, null, spaces) : String(val);
43327
43327
  }; // Remove leading white space from a string
43328
43328
 
43329
43329
  var ELEMENT_PROTO = Element$1.prototype;
@@ -43591,7 +43591,7 @@ var BVTransition = /*#__PURE__*/extend({
43591
43591
  props = _ref.props;
43592
43592
  var transProps = props.transProps;
43593
43593
 
43594
- if (!isPlainObject$2(transProps)) {
43594
+ if (!isPlainObject$3(transProps)) {
43595
43595
  transProps = props.noFade ? NO_FADE_PROPS : FADE_PROPS;
43596
43596
 
43597
43597
  if (props.appear) {
@@ -43652,7 +43652,7 @@ var encode = function encode(str) {
43652
43652
  // See: https://github.com/vuejs/vue-router/blob/dev/src/util/query.js
43653
43653
 
43654
43654
  var stringifyQueryObj = function stringifyQueryObj(obj) {
43655
- if (!isPlainObject$2(obj)) {
43655
+ if (!isPlainObject$3(obj)) {
43656
43656
  return '';
43657
43657
  }
43658
43658
 
@@ -43743,7 +43743,7 @@ var computeHref = function computeHref() {
43743
43743
  } // Fallback to `to.path' + `to.query` + `to.hash` prop (if `to` is an object)
43744
43744
 
43745
43745
 
43746
- if (isPlainObject$2(to) && (to.path || to.query || to.hash)) {
43746
+ if (isPlainObject$3(to) && (to.path || to.query || to.hash)) {
43747
43747
  var path = toString(to.path);
43748
43748
  var query = stringifyQueryObj(to.query);
43749
43749
  var hash = toString(to.hash);
@@ -55925,6 +55925,29 @@ function useContextMenu() {
55925
55925
  }
55926
55926
  }
55927
55927
 
55928
+ /**
55929
+ * Pattern matching the `manifest-resolve-sentinel` capability's
55930
+ * sentinel — `@resolve:<key>` where `<key>` is lowercase alphanumeric
55931
+ * with `_` / `-` separators. The full string IS the sentinel; partial
55932
+ * substitution like `prefix-@resolve:foo` is NOT supported and is left
55933
+ * as a plain string for downstream renderers.
55934
+ *
55935
+ * Build-time validation accepts this pattern as a valid `string` for
55936
+ * any `string`-typed field UNDER `pages[].config`, regardless of any
55937
+ * narrower per-field constraint. Other paths reject it explicitly.
55938
+ */
55939
+ const SENTINEL_PATTERN$1 = /^@resolve:[a-z][a-z0-9_-]*$/;
55940
+
55941
+ /**
55942
+ * Test whether a string is a manifest `@resolve:` sentinel.
55943
+ *
55944
+ * @param {*} value Candidate value.
55945
+ * @return {boolean} True when the value is a fully-matched sentinel.
55946
+ */
55947
+ function isSentinel(value) {
55948
+ return typeof value === 'string' && SENTINEL_PATTERN$1.test(value)
55949
+ }
55950
+
55928
55951
  /**
55929
55952
  * Validate a manifest object against the manifest JSON Schema.
55930
55953
  *
@@ -55942,6 +55965,13 @@ function useContextMenu() {
55942
55965
  * - `pages[].component` is required when `type` is "custom".
55943
55966
  * - Per-type `config` shape rules for the built-in types `logs`,
55944
55967
  * `settings`, `chat`, `files` (REQ from manifest-page-type-extensions).
55968
+ * - The `manifest-resolve-sentinel` sentinel `@resolve:<key>` is
55969
+ * permissively accepted under `pages[].config.*` and explicitly
55970
+ * REJECTED in `version`, `dependencies[]`, `menu[].route`,
55971
+ * `menu[].id`, `pages[].id`, `pages[].route`, `pages[].component`,
55972
+ * `pages[].headerComponent`, `pages[].actionsComponent`,
55973
+ * `pages[].slots.*` — those are router invariants or registry
55974
+ * keys.
55945
55975
  *
55946
55976
  * The richer schema constraints (`additionalProperties: false`, `format`
55947
55977
  * URI, etc.) are enforced by the BE / hydra CI validators that consume
@@ -55961,7 +55991,7 @@ function useContextMenu() {
55961
55991
  function validateManifest(manifest, options = {}) {
55962
55992
  const errors = [];
55963
55993
 
55964
- if (!isPlainObject$1(manifest)) {
55994
+ if (!isPlainObject$2(manifest)) {
55965
55995
  return { valid: false, errors: ['manifest must be an object'] }
55966
55996
  }
55967
55997
 
@@ -55969,6 +55999,10 @@ function validateManifest(manifest, options = {}) {
55969
55999
 
55970
56000
  if (typeof manifest.version !== 'string') {
55971
56001
  errors.push('/version must be a string');
56002
+ } else if (isSentinel(manifest.version)) {
56003
+ // `manifest-resolve-sentinel` REQ-MRS-004: sentinel is a router /
56004
+ // registry invariant violation when used here.
56005
+ errors.push(`/version "${manifest.version}" must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
55972
56006
  } else if (!versionPattern.test(manifest.version)) {
55973
56007
  errors.push(`/version "${manifest.version}" must match semver pattern`);
55974
56008
  }
@@ -55977,12 +56011,19 @@ function validateManifest(manifest, options = {}) {
55977
56011
  errors.push('/menu must be an array');
55978
56012
  } else {
55979
56013
  manifest.menu.forEach((item, index) => {
55980
- if (!isPlainObject$1(item)) {
56014
+ if (!isPlainObject$2(item)) {
55981
56015
  errors.push(`/menu/${index} must be an object`);
55982
56016
  return
55983
56017
  }
55984
- if (typeof item.id !== 'string') errors.push(`/menu/${index}/id must be a string`);
56018
+ if (typeof item.id !== 'string') {
56019
+ errors.push(`/menu/${index}/id must be a string`);
56020
+ } else if (isSentinel(item.id)) {
56021
+ errors.push(`/menu/${index}/id must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
56022
+ }
55985
56023
  if (typeof item.label !== 'string') errors.push(`/menu/${index}/label must be a string`);
56024
+ if (item.route !== undefined && isSentinel(item.route)) {
56025
+ errors.push(`/menu/${index}/route must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
56026
+ }
55986
56027
  if (item.children !== undefined && !Array.isArray(item.children)) {
55987
56028
  errors.push(`/menu/${index}/children must be an array`);
55988
56029
  }
@@ -55998,18 +56039,24 @@ function validateManifest(manifest, options = {}) {
55998
56039
  } else {
55999
56040
  const seenIds = new Set();
56000
56041
  manifest.pages.forEach((page, index) => {
56001
- if (!isPlainObject$1(page)) {
56042
+ if (!isPlainObject$2(page)) {
56002
56043
  errors.push(`/pages/${index} must be an object`);
56003
56044
  return
56004
56045
  }
56005
56046
  if (typeof page.id !== 'string') {
56006
56047
  errors.push(`/pages/${index}/id must be a string`);
56048
+ } else if (isSentinel(page.id)) {
56049
+ errors.push(`/pages/${index}/id must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
56007
56050
  } else if (seenIds.has(page.id)) {
56008
56051
  errors.push(`/pages/${index}/id "${page.id}" must be unique within pages[]`);
56009
56052
  } else {
56010
56053
  seenIds.add(page.id);
56011
56054
  }
56012
- if (typeof page.route !== 'string') errors.push(`/pages/${index}/route must be a string`);
56055
+ if (typeof page.route !== 'string') {
56056
+ errors.push(`/pages/${index}/route must be a string`);
56057
+ } else if (isSentinel(page.route)) {
56058
+ errors.push(`/pages/${index}/route must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
56059
+ }
56013
56060
  if (typeof page.title !== 'string') errors.push(`/pages/${index}/title must be a string`);
56014
56061
  if (typeof page.type !== 'string' || page.type.length === 0) {
56015
56062
  errors.push(`/pages/${index}/type must be a non-empty string`);
@@ -56019,6 +56066,25 @@ function validateManifest(manifest, options = {}) {
56019
56066
  if (page.type === 'custom' && typeof page.component !== 'string') {
56020
56067
  errors.push(`/pages/${index}/component is required when type is "custom"`);
56021
56068
  }
56069
+ // `manifest-resolve-sentinel` REQ-MRS-004: registry-key
56070
+ // fields cannot be dynamic — they resolve at module-load
56071
+ // time against `customComponents`, before the loader runs.
56072
+ if (page.component !== undefined && isSentinel(page.component)) {
56073
+ errors.push(`/pages/${index}/component must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
56074
+ }
56075
+ if (page.headerComponent !== undefined && isSentinel(page.headerComponent)) {
56076
+ errors.push(`/pages/${index}/headerComponent must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
56077
+ }
56078
+ if (page.actionsComponent !== undefined && isSentinel(page.actionsComponent)) {
56079
+ errors.push(`/pages/${index}/actionsComponent must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
56080
+ }
56081
+ if (isPlainObject$2(page.slots)) {
56082
+ for (const [slotName, slotValue] of Object.entries(page.slots)) {
56083
+ if (isSentinel(slotValue)) {
56084
+ errors.push(`/pages/${index}/slots/${slotName} must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
56085
+ }
56086
+ }
56087
+ }
56022
56088
 
56023
56089
  // Per-type config-shape validation for built-in extended types.
56024
56090
  // (`manifest-page-type-extensions` spec — covers logs/settings/chat/files.)
@@ -56042,6 +56108,8 @@ function validateManifest(manifest, options = {}) {
56042
56108
  manifest.dependencies.forEach((dep, index) => {
56043
56109
  if (typeof dep !== 'string') {
56044
56110
  errors.push(`/dependencies/${index} must be a string`);
56111
+ } else if (isSentinel(dep)) {
56112
+ errors.push(`/dependencies/${index} must not be a @resolve: sentinel (sentinels are only valid under pages[].config.*)`);
56045
56113
  }
56046
56114
  });
56047
56115
  }
@@ -56050,7 +56118,7 @@ function validateManifest(manifest, options = {}) {
56050
56118
  return { valid: errors.length === 0, errors }
56051
56119
  }
56052
56120
 
56053
- function isPlainObject$1(value) {
56121
+ function isPlainObject$2(value) {
56054
56122
  return value !== null && typeof value === 'object' && !Array.isArray(value)
56055
56123
  }
56056
56124
 
@@ -56073,7 +56141,7 @@ function isPlainObject$1(value) {
56073
56141
  */
56074
56142
  function validateTypeConfig(page, index, errors) {
56075
56143
  if (!page || typeof page.type !== 'string') return
56076
- const cfg = isPlainObject$1(page.config) ? page.config : null;
56144
+ const cfg = isPlainObject$2(page.config) ? page.config : null;
56077
56145
  const pathBracket = `pages[${index}].config`;
56078
56146
  const pathSlash = `/pages/${index}/config`;
56079
56147
 
@@ -56105,7 +56173,70 @@ function validateTypeConfig(page, index, errors) {
56105
56173
  break
56106
56174
  }
56107
56175
  case 'settings': {
56108
- if (!cfg || !Array.isArray(cfg.sections)) {
56176
+ // `manifest-settings-orchestration` REQ-MSO-1: a settings page
56177
+ // MUST declare EXACTLY ONE of `sections` | `tabs`. When both
56178
+ // are set, emit the orchestration mutex error. When neither is
56179
+ // set, fall through to the legacy `sections required` error
56180
+ // (back-compat — REQ-MSO-7 / REQ-MSO-1 last scenario).
56181
+ const hasSections = cfg && Array.isArray(cfg.sections);
56182
+ const hasTabs = cfg && Array.isArray(cfg.tabs);
56183
+
56184
+ if (hasSections && hasTabs) {
56185
+ errors.push(`${pathSlash}: ${pathBracket}: must declare exactly one of sections | tabs`);
56186
+ break
56187
+ }
56188
+
56189
+ if (hasTabs) {
56190
+ // `manifest-settings-orchestration` REQ-MSO-2..4: validate
56191
+ // the `tabs[]` orchestration shape.
56192
+ if (cfg.tabs.length === 0) {
56193
+ errors.push(`${pathSlash}/tabs: ${pathBracket}.tabs: must contain at least 1 tab`);
56194
+ break
56195
+ }
56196
+ const seenTabIds = Object.create(null);
56197
+ cfg.tabs.forEach((tab, tIndex) => {
56198
+ if (!isPlainObject$2(tab)) {
56199
+ errors.push(`${pathSlash}/tabs/${tIndex}: must be an object`);
56200
+ return
56201
+ }
56202
+ if (typeof tab.id !== 'string' || tab.id.length === 0) {
56203
+ errors.push(`${pathSlash}/tabs/${tIndex}/id: required, must be a non-empty string`);
56204
+ }
56205
+ if (typeof tab.label !== 'string' || tab.label.length === 0) {
56206
+ errors.push(`${pathSlash}/tabs/${tIndex}/label: required, must be a non-empty string`);
56207
+ }
56208
+ // REQ-MSO-3: tab IDs must be unique within a page.
56209
+ if (typeof tab.id === 'string' && tab.id.length > 0) {
56210
+ if (seenTabIds[tab.id]) {
56211
+ errors.push(`${pathSlash}/tabs/${tIndex}/id: ${pathBracket}.tabs[${tIndex}].id: duplicate id "${tab.id}" — tab IDs must be unique within a page`);
56212
+ }
56213
+ seenTabIds[tab.id] = true;
56214
+ }
56215
+ // `tab.sections` MUST be a non-empty array.
56216
+ if (!Array.isArray(tab.sections)) {
56217
+ errors.push(`${pathSlash}/tabs/${tIndex}/sections: ${pathBracket}.tabs[${tIndex}].sections: required, must be an array`);
56218
+ return
56219
+ }
56220
+ if (tab.sections.length === 0) {
56221
+ errors.push(`${pathSlash}/tabs/${tIndex}/sections: ${pathBracket}.tabs[${tIndex}].sections: must contain at least 1 section`);
56222
+ return
56223
+ }
56224
+ // REQ-MSO-4: each tab's sections follow the same rules
56225
+ // as the flat case — share the per-section validator.
56226
+ tab.sections.forEach((section, sIndex) => {
56227
+ validateSettingsSection(
56228
+ section,
56229
+ `${pathSlash}/tabs/${tIndex}/sections/${sIndex}`,
56230
+ `${pathBracket}.tabs[${tIndex}].sections[${sIndex}]`,
56231
+ errors,
56232
+ );
56233
+ });
56234
+ });
56235
+ break
56236
+ }
56237
+
56238
+ // Flat `sections[]` (existing path — REQ-MSRS-* + back-compat).
56239
+ if (!hasSections) {
56109
56240
  errors.push(`${pathSlash}/sections: ${pathBracket}.sections: required, must be an array`);
56110
56241
  break
56111
56242
  }
@@ -56114,56 +56245,12 @@ function validateTypeConfig(page, index, errors) {
56114
56245
  break
56115
56246
  }
56116
56247
  cfg.sections.forEach((section, sIndex) => {
56117
- if (!isPlainObject$1(section)) {
56118
- errors.push(`${pathSlash}/sections/${sIndex}: must be an object`);
56119
- return
56120
- }
56121
- if (typeof section.title !== 'string') {
56122
- errors.push(`${pathSlash}/sections/${sIndex}/title: required, must be a string`);
56123
- }
56124
-
56125
- // `manifest-settings-rich-sections` REQ-MSRS-1: each
56126
- // section MUST declare exactly one of fields | component
56127
- // | widgets. Mixed bodies confuse the renderer + duplicate
56128
- // the section chrome; empty bodies render nothing so they
56129
- // are a manifest-author bug.
56130
- const hasFields = Array.isArray(section.fields);
56131
- const hasComponent = typeof section.component === 'string' && section.component.length > 0;
56132
- const hasWidgets = Array.isArray(section.widgets) && section.widgets.length > 0;
56133
- const bodyCount = (hasFields ? 1 : 0) + (hasComponent ? 1 : 0) + (hasWidgets ? 1 : 0);
56134
-
56135
- if (bodyCount !== 1) {
56136
- errors.push(`${pathSlash}/sections/${sIndex}: ${pathBracket}.sections[${sIndex}]: must declare exactly one of fields | component | widgets`);
56137
- }
56138
-
56139
- // `widgets` set but not an array (string / object / etc.)
56140
- if (section.widgets !== undefined && !Array.isArray(section.widgets)) {
56141
- errors.push(`${pathSlash}/sections/${sIndex}/widgets: must be an array when set`);
56142
- }
56143
-
56144
- // `component` set but not a string.
56145
- if (section.component !== undefined && typeof section.component !== 'string') {
56146
- errors.push(`${pathSlash}/sections/${sIndex}/component: must be a string when set`);
56147
- }
56148
-
56149
- // Per-widget shape rules.
56150
- if (hasWidgets) {
56151
- section.widgets.forEach((widget, wIndex) => {
56152
- if (!isPlainObject$1(widget)) {
56153
- errors.push(`${pathSlash}/sections/${sIndex}/widgets/${wIndex}: must be an object`);
56154
- return
56155
- }
56156
- if (typeof widget.type !== 'string' || widget.type.length === 0) {
56157
- errors.push(`${pathSlash}/sections/${sIndex}/widgets/${wIndex}/type: must be a non-empty string`);
56158
- }
56159
- });
56160
- }
56161
-
56162
- // `manifest-config-refs` REQ-MCR — when fields[] body is
56163
- // used, each entry must match the formField $def shape.
56164
- if (hasFields) {
56165
- validateFieldsArray(section.fields, `${pathSlash}/sections/${sIndex}/fields`, errors);
56166
- }
56248
+ validateSettingsSection(
56249
+ section,
56250
+ `${pathSlash}/sections/${sIndex}`,
56251
+ `${pathBracket}.sections[${sIndex}]`,
56252
+ errors,
56253
+ );
56167
56254
  });
56168
56255
  break
56169
56256
  }
@@ -56251,12 +56338,12 @@ function validateTypeConfig(page, index, errors) {
56251
56338
  */
56252
56339
  function validateSidebarConfig(page, pageIndex, errors) {
56253
56340
  const config = page.config;
56254
- if (!isPlainObject$1(config)) return
56341
+ if (!isPlainObject$2(config)) return
56255
56342
 
56256
56343
  // --- Index sidebar ---
56257
56344
  if (page.type === 'index' && config.sidebar !== undefined) {
56258
56345
  const path = `/pages/${pageIndex}/config/sidebar`;
56259
- if (!isPlainObject$1(config.sidebar)) {
56346
+ if (!isPlainObject$2(config.sidebar)) {
56260
56347
  errors.push(`${path} must be an object`);
56261
56348
  } else {
56262
56349
  if (config.sidebar.enabled !== undefined && typeof config.sidebar.enabled !== 'boolean') {
@@ -56269,13 +56356,13 @@ function validateSidebarConfig(page, pageIndex, errors) {
56269
56356
  if (config.sidebar.columnGroups !== undefined && !Array.isArray(config.sidebar.columnGroups)) {
56270
56357
  errors.push(`${path}/columnGroups must be an array`);
56271
56358
  }
56272
- if (config.sidebar.facets !== undefined && !isPlainObject$1(config.sidebar.facets)) {
56359
+ if (config.sidebar.facets !== undefined && !isPlainObject$2(config.sidebar.facets)) {
56273
56360
  errors.push(`${path}/facets must be an object`);
56274
56361
  }
56275
56362
  if (config.sidebar.showMetadata !== undefined && typeof config.sidebar.showMetadata !== 'boolean') {
56276
56363
  errors.push(`${path}/showMetadata must be a boolean`);
56277
56364
  }
56278
- if (config.sidebar.search !== undefined && !isPlainObject$1(config.sidebar.search)) {
56365
+ if (config.sidebar.search !== undefined && !isPlainObject$2(config.sidebar.search)) {
56279
56366
  errors.push(`${path}/search must be an object`);
56280
56367
  }
56281
56368
  }
@@ -56290,7 +56377,7 @@ function validateSidebarConfig(page, pageIndex, errors) {
56290
56377
  const path = `/pages/${pageIndex}/config/sidebar`;
56291
56378
  const sb = config.sidebar;
56292
56379
  const isBool = typeof sb === 'boolean';
56293
- const isObj = isPlainObject$1(sb);
56380
+ const isObj = isPlainObject$2(sb);
56294
56381
  if (!isBool && !isObj) {
56295
56382
  errors.push(`${path} must be a boolean (legacy) or object`);
56296
56383
  } else if (isObj) {
@@ -56330,7 +56417,7 @@ function validateSidebarConfig(page, pageIndex, errors) {
56330
56417
  }
56331
56418
 
56332
56419
  // --- Detail sidebar tabs (legacy sidebarProps.tabs path) ---
56333
- if (page.type === 'detail' && isPlainObject$1(config.sidebarProps) && config.sidebarProps.tabs !== undefined) {
56420
+ if (page.type === 'detail' && isPlainObject$2(config.sidebarProps) && config.sidebarProps.tabs !== undefined) {
56334
56421
  const tabsPath = `/pages/${pageIndex}/config/sidebarProps/tabs`;
56335
56422
  validateDetailTabsArray(config.sidebarProps.tabs, tabsPath, errors);
56336
56423
  }
@@ -56354,7 +56441,7 @@ function validateDetailTabsArray(tabs, tabsPath, errors) {
56354
56441
  const seenIds = new Set();
56355
56442
  tabs.forEach((tab, tabIndex) => {
56356
56443
  const tabPath = `${tabsPath}/${tabIndex}`;
56357
- if (!isPlainObject$1(tab)) {
56444
+ if (!isPlainObject$2(tab)) {
56358
56445
  errors.push(`${tabPath} must be an object`);
56359
56446
  return
56360
56447
  }
@@ -56405,7 +56492,7 @@ function validateDetailTabsArray(tabs, tabsPath, errors) {
56405
56492
  function validatePageSidebar(page, pageIndex, errors) {
56406
56493
  if (page.sidebar === undefined) return
56407
56494
  const path = `/pages/${pageIndex}/sidebar`;
56408
- if (!isPlainObject$1(page.sidebar)) {
56495
+ if (!isPlainObject$2(page.sidebar)) {
56409
56496
  errors.push(`${path} must be an object`);
56410
56497
  return
56411
56498
  }
@@ -56443,7 +56530,7 @@ function validateColumnsArray(cfg, pathSlash, pathBracket, errors) {
56443
56530
  // Legacy shorthand — accepted as-is.
56444
56531
  return
56445
56532
  }
56446
- if (!isPlainObject$1(col)) {
56533
+ if (!isPlainObject$2(col)) {
56447
56534
  errors.push(`${colPath}: must be a string (legacy shorthand) or object`);
56448
56535
  return
56449
56536
  }
@@ -56475,7 +56562,7 @@ function validateActionsArray(cfg, pathSlash, pathBracket, errors) {
56475
56562
  }
56476
56563
  cfg.actions.forEach((action, aIndex) => {
56477
56564
  const actionPath = `${pathSlash}/actions/${aIndex}`;
56478
- if (!isPlainObject$1(action)) {
56565
+ if (!isPlainObject$2(action)) {
56479
56566
  errors.push(`${actionPath}: must be an object`);
56480
56567
  return
56481
56568
  }
@@ -56532,7 +56619,7 @@ function validateWidgetsArray(cfg, pathSlash, pathBracket, errors) {
56532
56619
  }
56533
56620
  cfg.widgets.forEach((widget, wIndex) => {
56534
56621
  const widgetPath = `${pathSlash}/widgets/${wIndex}`;
56535
- if (!isPlainObject$1(widget)) {
56622
+ if (!isPlainObject$2(widget)) {
56536
56623
  errors.push(`${widgetPath}: must be an object`);
56537
56624
  return
56538
56625
  }
@@ -56567,7 +56654,7 @@ function validateLayoutArray(cfg, pathSlash, pathBracket, errors) {
56567
56654
  }
56568
56655
  cfg.layout.forEach((item, lIndex) => {
56569
56656
  const layoutPath = `${pathSlash}/layout/${lIndex}`;
56570
- if (!isPlainObject$1(item)) {
56657
+ if (!isPlainObject$2(item)) {
56571
56658
  errors.push(`${layoutPath}: must be an object`);
56572
56659
  return
56573
56660
  }
@@ -56591,6 +56678,82 @@ function validateLayoutArray(cfg, pathSlash, pathBracket, errors) {
56591
56678
  });
56592
56679
  }
56593
56680
 
56681
+ /**
56682
+ * Validate a single `sections[]` entry for `type:"settings"` pages.
56683
+ * Shared between the flat `pages[].config.sections[]` path AND the
56684
+ * tab-nested `pages[].config.tabs[].sections[]` path
56685
+ * (`manifest-settings-orchestration` REQ-MSO-4).
56686
+ *
56687
+ * Enforces the rich-sections REQ-MSRS-1 mutex (`fields | component |
56688
+ * widgets` exactly-one-of) plus per-widget shape rules. The new
56689
+ * `widget.type === "component"` discriminator (REQ-MSO-6) requires
56690
+ * `componentName: <non-empty string>`.
56691
+ *
56692
+ * @param {*} section The section under validation
56693
+ * @param {string} pathSlash JSON-pointer-style path prefix for errors
56694
+ * @param {string} pathBracket Human-readable bracket-path for errors
56695
+ * @param {string[]} errors Accumulator
56696
+ */
56697
+ function validateSettingsSection(section, pathSlash, pathBracket, errors) {
56698
+ if (!isPlainObject$2(section)) {
56699
+ errors.push(`${pathSlash}: must be an object`);
56700
+ return
56701
+ }
56702
+ if (typeof section.title !== 'string') {
56703
+ errors.push(`${pathSlash}/title: required, must be a string`);
56704
+ }
56705
+
56706
+ // `manifest-settings-rich-sections` REQ-MSRS-1: exactly one of
56707
+ // fields | component | widgets.
56708
+ const hasFields = Array.isArray(section.fields);
56709
+ const hasComponent = typeof section.component === 'string' && section.component.length > 0;
56710
+ const hasWidgets = Array.isArray(section.widgets) && section.widgets.length > 0;
56711
+ const bodyCount = (hasFields ? 1 : 0) + (hasComponent ? 1 : 0) + (hasWidgets ? 1 : 0);
56712
+
56713
+ if (bodyCount !== 1) {
56714
+ errors.push(`${pathSlash}: ${pathBracket}: must declare exactly one of fields | component | widgets`);
56715
+ }
56716
+
56717
+ // `widgets` set but not an array (string / object / etc.)
56718
+ if (section.widgets !== undefined && !Array.isArray(section.widgets)) {
56719
+ errors.push(`${pathSlash}/widgets: must be an array when set`);
56720
+ }
56721
+
56722
+ // `component` set but not a string.
56723
+ if (section.component !== undefined && typeof section.component !== 'string') {
56724
+ errors.push(`${pathSlash}/component: must be a string when set`);
56725
+ }
56726
+
56727
+ // Per-widget shape rules.
56728
+ if (hasWidgets) {
56729
+ section.widgets.forEach((widget, wIndex) => {
56730
+ if (!isPlainObject$2(widget)) {
56731
+ errors.push(`${pathSlash}/widgets/${wIndex}: must be an object`);
56732
+ return
56733
+ }
56734
+ if (typeof widget.type !== 'string' || widget.type.length === 0) {
56735
+ errors.push(`${pathSlash}/widgets/${wIndex}/type: must be a non-empty string`);
56736
+ return
56737
+ }
56738
+ // `manifest-settings-orchestration` REQ-MSO-6: when the
56739
+ // discriminator is "component", `componentName` MUST be a
56740
+ // non-empty string. Other widget types ignore
56741
+ // `componentName`.
56742
+ if (widget.type === 'component') {
56743
+ if (typeof widget.componentName !== 'string' || widget.componentName.length === 0) {
56744
+ errors.push(`${pathSlash}/widgets/${wIndex}/componentName: required when type is "component", must be a non-empty string`);
56745
+ }
56746
+ }
56747
+ });
56748
+ }
56749
+
56750
+ // `manifest-config-refs` REQ-MCR — when fields[] body is used,
56751
+ // each entry must match the formField $def shape.
56752
+ if (hasFields) {
56753
+ validateFieldsArray(section.fields, `${pathSlash}/fields`, errors);
56754
+ }
56755
+ }
56756
+
56594
56757
  /**
56595
56758
  * Validate `config.sections[].fields[]` for settings page type
56596
56759
  * (`manifest-config-refs` REQ-MCR). Each field MUST be an object with
@@ -56607,7 +56770,7 @@ function validateFieldsArray(fields, fieldsPath, errors) {
56607
56770
  if (!Array.isArray(fields)) return
56608
56771
  fields.forEach((field, fIndex) => {
56609
56772
  const fieldPath = `${fieldsPath}/${fIndex}`;
56610
- if (!isPlainObject$1(field)) {
56773
+ if (!isPlainObject$2(field)) {
56611
56774
  errors.push(`${fieldPath}: must be an object`);
56612
56775
  return
56613
56776
  }
@@ -56626,17 +56789,292 @@ function validateFieldsArray(fields, fieldsPath, errors) {
56626
56789
  }
56627
56790
 
56628
56791
  /**
56629
- * Composable that loads and validates a Conduction app manifest.
56792
+ * Manifest `@resolve:` sentinel resolver.
56793
+ *
56794
+ * Implements the `manifest-resolve-sentinel` capability: walks the
56795
+ * `pages[].config` subtrees of an assembled manifest and replaces every
56796
+ * fully-matched `@resolve:<key>` string with the result of the consuming
56797
+ * app's `IAppConfig` lookup for `<key>`. Other manifest paths
56798
+ * (`route`, `id`, top-level fields, `menu[]`, `pages[].component` etc.)
56799
+ * are intentionally untouched — sentinels there are router / registry
56800
+ * invariants and the schema validator rejects them.
56801
+ *
56802
+ * Resolution source (canonical, per spec):
56803
+ *
56804
+ * 1. `@nextcloud/initial-state` provisioned key
56805
+ * `app-{appId}-{key}` — zero-network, preferred.
56806
+ * 2. Runtime `GET /index.php/apps/{appId}/api/configs/{key}` — falls
56807
+ * through silently on 4xx / network error.
56808
+ * 3. `null` — unresolved.
56809
+ *
56810
+ * Empty-state semantics: an unset / empty value substitutes `null` (not
56811
+ * empty string) and is accumulated into the returned `unresolved` array
56812
+ * so consumers can render an admin warning. A `console.warn` is emitted
56813
+ * once per unresolved key with the offending sentinel.
56814
+ *
56815
+ * The resolver is intentionally split into a synchronous walk + an
56816
+ * asynchronous batch resolution: every sentinel is collected first, then
56817
+ * each unique `(appId, key)` is resolved exactly once (initial-state
56818
+ * lookup is synchronous; runtime fetch is per-key cached for the page
56819
+ * lifetime), and finally the manifest is rewritten in a second walk
56820
+ * with the resolved values. This makes the substitution deterministic
56821
+ * — five `@resolve:foo` references in five pages share one fetch.
56822
+ *
56823
+ * @module utils/resolveManifestSentinels
56824
+ */
56825
+
56826
+ const SENTINEL_PATTERN = /^@resolve:([a-z][a-z0-9_-]*)$/;
56827
+
56828
+ /**
56829
+ * Process-wide cache of resolved IAppConfig values, keyed by
56830
+ * `${appId}::${key}`. Cleared via `clearResolveCache()` (test-only).
56831
+ *
56832
+ * @type {Map<string, Promise<*>>}
56833
+ */
56834
+ const resolveCache = new Map();
56835
+
56836
+ /**
56837
+ * Test-only helper to reset the per-page resolve cache between runs.
56838
+ * Production callers do not need this — the cache is page-lifetime by
56839
+ * design, consistent with the manifest's load-once model.
56840
+ *
56841
+ * @return {void}
56842
+ */
56843
+ function clearResolveCache() {
56844
+ resolveCache.clear();
56845
+ }
56846
+
56847
+ /**
56848
+ * Walk the manifest's `pages[].config` subtrees and replace every
56849
+ * `@resolve:<key>` sentinel with the resolved IAppConfig value.
56850
+ *
56851
+ * Returns a Promise resolving to `{ manifest, unresolved }`:
56852
+ * - `manifest` — a NEW manifest object with sentinels substituted; the
56853
+ * input is NOT mutated.
56854
+ * - `unresolved` — array of IAppConfig keys whose sentinels resolved
56855
+ * to `null` (unset / empty / fetch failure). Useful for surfacing
56856
+ * "n settings unconfigured" admin warnings.
56857
+ *
56858
+ * Sentinels OUTSIDE `pages[].config` are left intact; the schema
56859
+ * validator rejects them downstream so consumers see a clear error
56860
+ * rather than a silent substitution that breaks routing or registry
56861
+ * lookups.
56862
+ *
56863
+ * @param {object} manifest The merged (bundled + backend) manifest.
56864
+ * Walked but not mutated.
56865
+ * @param {string} appId Nextcloud app ID. Used to scope the
56866
+ * IAppConfig lookup namespace.
56867
+ * @param {object} [options] Resolver overrides.
56868
+ * @param {Function} [options.getAppConfigValue] Async (appId, key) =>
56869
+ * value resolver. Override for tests; defaults to the
56870
+ * initial-state-then-fetch chain documented above.
56871
+ * @param {Function} [options.warn] Override for `console.warn`. Used in
56872
+ * tests to capture warning calls without polluting test output.
56873
+ * @return {Promise<{ manifest: object, unresolved: string[] }>}
56874
+ */
56875
+ async function resolveManifestSentinels(manifest, appId, options = {}) {
56876
+ if (!isPlainObject$1(manifest)) {
56877
+ return { manifest, unresolved: [] }
56878
+ }
56879
+
56880
+ const getAppConfigValue = options.getAppConfigValue ?? defaultGetAppConfigValue;
56881
+ const warn = options.warn ?? ((...args) => {
56882
+ // eslint-disable-next-line no-console
56883
+ console.warn(...args);
56884
+ });
56885
+
56886
+ // Phase 1: scan for sentinels under pages[].config only. We only
56887
+ // need the unique key set — the second walk does the substitution.
56888
+ const keys = new Set();
56889
+ const pages = Array.isArray(manifest.pages) ? manifest.pages : [];
56890
+ for (const page of pages) {
56891
+ if (!isPlainObject$1(page) || !isPlainObject$1(page.config)) continue
56892
+ collectSentinelKeys(page.config, keys);
56893
+ }
56894
+
56895
+ if (keys.size === 0) {
56896
+ return { manifest, unresolved: [] }
56897
+ }
56898
+
56899
+ // Phase 2: resolve each unique (appId, key) exactly once.
56900
+ const resolved = new Map();
56901
+ const unresolved = [];
56902
+ await Promise.all(Array.from(keys).map(async (key) => {
56903
+ const value = await getAppConfigValue(appId, key);
56904
+ if (value === undefined || value === null || value === '') {
56905
+ resolved.set(key, null);
56906
+ unresolved.push(key);
56907
+ warn(`[resolveManifestSentinels] Manifest sentinel '@resolve:${key}' resolved to null (key unset)`);
56908
+ } else {
56909
+ resolved.set(key, value);
56910
+ }
56911
+ }));
56912
+
56913
+ // Phase 3: rebuild the manifest immutably, substituting sentinels in
56914
+ // pages[].config only. Other fields are passed through by reference.
56915
+ const out = { ...manifest };
56916
+ out.pages = pages.map((page) => {
56917
+ if (!isPlainObject$1(page) || !isPlainObject$1(page.config)) return page
56918
+ return { ...page, config: substituteInTree(page.config, resolved) }
56919
+ });
56920
+
56921
+ return { manifest: out, unresolved }
56922
+ }
56923
+
56924
+ /**
56925
+ * Recursively scan a tree, accumulating every sentinel key into the
56926
+ * provided `keys` Set. Plain objects + arrays are descended; primitive
56927
+ * leaves are checked against the sentinel pattern.
56928
+ *
56929
+ * @param {*} node Current tree node (object, array, or primitive).
56930
+ * @param {Set<string>} keys Accumulator for unique sentinel keys.
56931
+ * @return {void}
56932
+ */
56933
+ function collectSentinelKeys(node, keys) {
56934
+ if (typeof node === 'string') {
56935
+ const match = node.match(SENTINEL_PATTERN);
56936
+ if (match) keys.add(match[1]);
56937
+ return
56938
+ }
56939
+ if (Array.isArray(node)) {
56940
+ for (const item of node) collectSentinelKeys(item, keys);
56941
+ return
56942
+ }
56943
+ if (isPlainObject$1(node)) {
56944
+ for (const value of Object.values(node)) collectSentinelKeys(value, keys);
56945
+ }
56946
+ }
56947
+
56948
+ /**
56949
+ * Recursively rebuild a tree, replacing each fully-matched sentinel
56950
+ * with its resolved value. Returns a NEW tree; input is unchanged.
56951
+ *
56952
+ * @param {*} node Current tree node.
56953
+ * @param {Map<string,*>} resolved Map of key → resolved value (or null).
56954
+ * @return {*} New tree with sentinels substituted.
56955
+ */
56956
+ function substituteInTree(node, resolved) {
56957
+ if (typeof node === 'string') {
56958
+ const match = node.match(SENTINEL_PATTERN);
56959
+ if (match && resolved.has(match[1])) {
56960
+ return resolved.get(match[1])
56961
+ }
56962
+ return node
56963
+ }
56964
+ if (Array.isArray(node)) {
56965
+ return node.map((item) => substituteInTree(item, resolved))
56966
+ }
56967
+ if (isPlainObject$1(node)) {
56968
+ const out = {};
56969
+ for (const [key, value] of Object.entries(node)) {
56970
+ out[key] = substituteInTree(value, resolved);
56971
+ }
56972
+ return out
56973
+ }
56974
+ return node
56975
+ }
56976
+
56977
+ /**
56978
+ * Default `getAppConfigValue` implementation: consult
56979
+ * `@nextcloud/initial-state` first (zero-network), fall back to a
56980
+ * runtime fetch with per-(appId, key) caching for the page lifetime.
56630
56981
  *
56631
- * The composable implements the three-phase flow specified in
56632
- * REQ-JMR-002 of the json-manifest-renderer capability:
56982
+ * Returns `null` when neither source resolves a value. Network / 4xx
56983
+ * errors are swallowed silently — the resolver downgrades to "key
56984
+ * unset" in that case (consistent with the silent-fallback pattern in
56985
+ * `useAppManifest`'s backend-merge step).
56986
+ *
56987
+ * @param {string} appId Nextcloud app ID.
56988
+ * @param {string} key IAppConfig key (already validated as
56989
+ * lowercase + alphanumeric + `_-` by the sentinel regex).
56990
+ * @return {Promise<*>} Resolved value or `null` when unset.
56991
+ */
56992
+ async function defaultGetAppConfigValue(appId, key) {
56993
+ const cacheKey = `${appId}::${key}`;
56994
+ if (resolveCache.has(cacheKey)) {
56995
+ return resolveCache.get(cacheKey)
56996
+ }
56997
+ const promise = (async () => {
56998
+ // Step 1: initial-state — synchronous, zero-network.
56999
+ const initial = readInitialState(appId, key);
57000
+ if (initial !== undefined && initial !== null && initial !== '') {
57001
+ return initial
57002
+ }
57003
+ // Step 2: runtime fetch — silent fallback on any error.
57004
+ try {
57005
+ const { default: axios } = await import('@nextcloud/axios');
57006
+ const { generateUrl } = await import('@nextcloud/router');
57007
+ const url = generateUrl(`/apps/${appId}/api/configs/${key}`);
57008
+ const response = await axios.get(url);
57009
+ if (response && response.status === 200 && response.data !== undefined) {
57010
+ const data = response.data;
57011
+ // API may return either a raw scalar or `{ value: ... }`.
57012
+ if (isPlainObject$1(data) && 'value' in data) return data.value
57013
+ return data
57014
+ }
57015
+ } catch (e) {
57016
+ // Silent — caller treats as "unset".
57017
+ }
57018
+ return null
57019
+ })();
57020
+ resolveCache.set(cacheKey, promise);
57021
+ return promise
57022
+ }
57023
+
57024
+ /**
57025
+ * Read a key from `@nextcloud/initial-state`. Looks up the conventional
57026
+ * `app-{appId}-{key}` slot. Returns `undefined` when the package is not
57027
+ * installed (e.g. older host) or the key is not provisioned.
57028
+ *
57029
+ * @param {string} appId Nextcloud app ID.
57030
+ * @param {string} key IAppConfig key.
57031
+ * @return {*} Provisioned value or `undefined`.
57032
+ */
57033
+ function readInitialState(appId, key) {
57034
+ try {
57035
+ // `@nextcloud/initial-state` is an optional peer; the host page
57036
+ // may not provision the slot at all. We resolve via require so
57037
+ // jest mocks the import; bundle-side, the package is treeshaken
57038
+ // when no caller pulls it in.
57039
+ // eslint-disable-next-line global-require, import/no-unresolved, n/no-extraneous-require
57040
+ const mod = require('@nextcloud/initial-state');
57041
+ if (typeof mod.loadState === 'function') {
57042
+ return mod.loadState(appId, key, undefined)
57043
+ }
57044
+ } catch (e) {
57045
+ // Package not installed or no slot provisioned — fall through.
57046
+ }
57047
+ return undefined
57048
+ }
57049
+
57050
+ /**
57051
+ * Type guard — true when value is a plain (non-array, non-null) object.
57052
+ *
57053
+ * @param {*} value Candidate.
57054
+ * @return {boolean} True when value is a plain object.
57055
+ */
57056
+ function isPlainObject$1(value) {
57057
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
57058
+ }
57059
+
57060
+ /**
57061
+ * Composable that loads, resolves, and validates a Conduction app manifest.
57062
+ *
57063
+ * The composable implements the four-phase flow specified in
57064
+ * REQ-JMR-002 of the json-manifest-renderer capability + the
57065
+ * substitution step from the `manifest-resolve-sentinel` capability:
56633
57066
  *
56634
57067
  * 1. Synchronous bundled load — `bundledManifest` is the immediate value.
56635
57068
  * 2. Async backend merge — fetches `/index.php/apps/{appId}/api/manifest`
56636
57069
  * and deep-merges any 200 response over the bundled manifest. 4xx /
56637
57070
  * 5xx / network errors are silently ignored so apps work without a
56638
57071
  * backend endpoint.
56639
- * 3. Validationthe merged result is validated against
57072
+ * 3. Sentinel resolution `@resolve:<key>` strings under
57073
+ * `pages[].config` are substituted with `IAppConfig` values via the
57074
+ * `resolveManifestSentinels` utility (see its module docs for the
57075
+ * resolution source chain). Unresolved keys surface on the
57076
+ * returned `unresolvedSentinels` ref.
57077
+ * 4. Validation — the resolved result is validated against
56640
57078
  * `app-manifest.schema.json`. On failure, the bundled manifest is
56641
57079
  * kept and a `console.warn` is emitted with the error list.
56642
57080
  *
@@ -56644,7 +57082,8 @@ function validateFieldsArray(fields, fieldsPath, errors) {
56644
57082
  * can hot-swap the manifest without a page reload.
56645
57083
  *
56646
57084
  * @param {string} appId Nextcloud app ID. Used to build the default
56647
- * backend endpoint URL via `@nextcloud/router`.
57085
+ * backend endpoint URL via `@nextcloud/router` and to scope
57086
+ * IAppConfig lookups for `@resolve:<key>` sentinels.
56648
57087
  * @param {object} bundledManifest The manifest shipped with the app (the
56649
57088
  * default value, available synchronously).
56650
57089
  * @param {object} [options] Configuration options.
@@ -56654,7 +57093,10 @@ function validateFieldsArray(fields, fieldsPath, errors) {
56654
57093
  * return a promise resolving to `{ status: number, data: object }`.
56655
57094
  * Defaults to `axios.get` from `@nextcloud/axios` (which inherits the
56656
57095
  * Nextcloud CSRF token automatically).
56657
- * @return {{ manifest: import('vue').Ref<object>, isLoading: import('vue').Ref<boolean>, validationErrors: import('vue').Ref<string[]|null> }}
57096
+ * @param {Function} [options.getAppConfigValue] Override the
57097
+ * IAppConfig resolver consumed by `resolveManifestSentinels`. Useful
57098
+ * for tests that want to mount a fixture-driven config map.
57099
+ * @return {{ manifest: import('vue').Ref<object>, isLoading: import('vue').Ref<boolean>, validationErrors: import('vue').Ref<string[]|null>, unresolvedSentinels: import('vue').Ref<string[]> }}
56658
57100
  *
56659
57101
  * @example Basic usage (Composition API)
56660
57102
  * const { manifest, isLoading } = useAppManifest('decidesk', bundled)
@@ -56671,11 +57113,17 @@ function validateFieldsArray(fields, fieldsPath, errors) {
56671
57113
  * endpoint: '/custom/manifest/url',
56672
57114
  * fetcher: (url) => Promise.resolve({ status: 200, data: { ... } }),
56673
57115
  * })
57116
+ *
57117
+ * @example With sentinel resolution + admin warning surface
57118
+ * const { manifest, unresolvedSentinels } = useAppManifest('softwarecatalog', bundled)
57119
+ * // unresolvedSentinels.value is e.g. ['voorzieningen_register']
57120
+ * // when that IAppConfig key is unset on the tenant.
56674
57121
  */
56675
57122
  function useAppManifest(appId, bundledManifest, options = {}) {
56676
57123
  const manifest = ref(bundledManifest);
56677
57124
  const isLoading = ref(true);
56678
57125
  const validationErrors = ref(null);
57126
+ const unresolvedSentinels = ref([]);
56679
57127
 
56680
57128
  const endpoint = options.endpoint ?? generateUrl(`/apps/${appId}/api/manifest`);
56681
57129
  const fetcher = options.fetcher ?? ((url) => axios.get(url))
@@ -56687,7 +57135,18 @@ function useAppManifest(appId, bundledManifest, options = {}) {
56687
57135
  return
56688
57136
  }
56689
57137
  const merged = deepMerge(bundledManifest, response.data);
56690
- const result = validateManifest(merged);
57138
+
57139
+ // Sentinel resolution runs BEFORE validation per
57140
+ // REQ-MRS-002: the validator MUST NEVER observe an
57141
+ // unresolved sentinel at runtime. Resolution failures
57142
+ // (unset IAppConfig keys) substitute null and accumulate
57143
+ // on `unresolvedSentinels`; they do NOT block validation.
57144
+ const { manifest: resolved, unresolved } = await resolveManifestSentinels(merged, appId, {
57145
+ getAppConfigValue: options.getAppConfigValue,
57146
+ });
57147
+ unresolvedSentinels.value = unresolved;
57148
+
57149
+ const result = validateManifest(resolved);
56691
57150
  if (!result.valid) {
56692
57151
  validationErrors.value = result.errors;
56693
57152
  // eslint-disable-next-line no-console
@@ -56697,7 +57156,7 @@ function useAppManifest(appId, bundledManifest, options = {}) {
56697
57156
  );
56698
57157
  return
56699
57158
  }
56700
- manifest.value = merged;
57159
+ manifest.value = resolved;
56701
57160
  } catch (err) {
56702
57161
  // Silent fallback on 404, network errors, non-200 responses.
56703
57162
  // Apps without a backend endpoint should keep working.
@@ -56706,7 +57165,7 @@ function useAppManifest(appId, bundledManifest, options = {}) {
56706
57165
  }
56707
57166
  })();
56708
57167
 
56709
- return { manifest, isLoading, validationErrors }
57168
+ return { manifest, isLoading, validationErrors, unresolvedSentinels }
56710
57169
  }
56711
57170
 
56712
57171
  /**
@@ -107628,7 +108087,45 @@ var CnSettingsWidgetMount = {
107628
108087
  //
107629
108088
  //
107630
108089
  //
108090
+ //
108091
+ //
108092
+ //
108093
+ //
108094
+ //
108095
+ //
108096
+ //
108097
+ //
108098
+ //
108099
+ //
108100
+ //
108101
+ //
108102
+ //
108103
+ //
108104
+ //
108105
+ //
108106
+ //
108107
+ //
108108
+ //
108109
+ //
108110
+ //
108111
+ //
108112
+ //
108113
+ //
108114
+ //
108115
+ //
108116
+ //
108117
+ //
108118
+ //
108119
+
107631
108120
 
108121
+ /**
108122
+ * Sentinel value used in the built-in widget registry to mark the
108123
+ * `'component'` discriminator (manifest-settings-orchestration
108124
+ * REQ-MSO-6). The discriminator does NOT resolve to a fixed
108125
+ * component — instead, the resolver detects this sentinel and looks
108126
+ * up `widget.componentName` in the customComponents registry.
108127
+ */
108128
+ const COMPONENT_DISCRIMINATOR = Symbol('cn-settings-component-widget');
107632
108129
 
107633
108130
  /**
107634
108131
  * Built-in widget registry. Used by `CnSettingsPage` to resolve
@@ -107638,13 +108135,19 @@ var CnSettingsWidgetMount = {
107638
108135
  * The order matters — built-ins win on collision so consumers can't
107639
108136
  * accidentally shadow `version-info` with their own component. If a
107640
108137
  * consumer needs to truly replace one of these, they can render their
107641
- * own component via `section.component` instead of `widgets[]`.
108138
+ * own component via `section.component` or
108139
+ * `{ type: "component", componentName: <name> }` instead of `widgets[]`.
107642
108140
  *
107643
- * Spec: REQ-MSRS-2 (manifest-settings-rich-sections).
108141
+ * Spec:
108142
+ * - REQ-MSRS-2 (manifest-settings-rich-sections) — fixed-component
108143
+ * built-ins (`version-info`, `register-mapping`).
108144
+ * - REQ-MSO-6 (manifest-settings-orchestration) — `'component'`
108145
+ * discriminator (sentinel value, resolved via componentName).
107644
108146
  */
107645
108147
  const BUILTIN_SETTINGS_WIDGETS = Object.freeze({
107646
108148
  'version-info': __vue_component__$17,
107647
108149
  'register-mapping': __vue_component__$I,
108150
+ component: COMPONENT_DISCRIMINATOR,
107648
108151
  });
107649
108152
 
107650
108153
  /**
@@ -107752,19 +108255,50 @@ var script$8 = {
107752
108255
  default: '',
107753
108256
  },
107754
108257
  /**
107755
- * Section definitions. Each section MUST declare EXACTLY ONE of:
108258
+ * Section definitions (flat shape back-compat). Each section
108259
+ * MUST declare EXACTLY ONE of:
107756
108260
  * - `fields: Array<Field>` (back-compat flat-field body)
107757
108261
  * - `component: <registry-name>` + optional `props`
107758
- * - `widgets: Array<{ type, props? }>`
108262
+ * - `widgets: Array<{ type, props?, componentName? }>`
107759
108263
  *
107760
108264
  * Common keys: `{ title, description?, icon?, collapsible?, docUrl? }`.
107761
108265
  *
108266
+ * Mutually exclusive with `tabs[]` (XOR — see
108267
+ * manifest-settings-orchestration REQ-MSO-1).
108268
+ *
107762
108269
  * @type {Array<object>}
107763
108270
  */
107764
108271
  sections: {
107765
108272
  type: Array,
107766
108273
  default: () => [],
107767
108274
  },
108275
+ /**
108276
+ * Tab definitions (orchestration shape — manifest-settings-
108277
+ * orchestration REQ-MSO-2). When set, CnSettingsPage renders
108278
+ * a tab strip above the section area; the active tab's
108279
+ * `sections[]` flow into the same renderer used by the flat
108280
+ * shape. Mutually exclusive with `sections[]`.
108281
+ *
108282
+ * Each tab MUST be `{ id: string, label: string,
108283
+ * icon?: string, sections: array<Section> }`.
108284
+ *
108285
+ * @type {Array<object>}
108286
+ */
108287
+ tabs: {
108288
+ type: Array,
108289
+ default: () => [],
108290
+ },
108291
+ /**
108292
+ * Optional ID of the tab to activate on mount. When empty AND
108293
+ * `tabs[]` is non-empty, the first tab is active by default.
108294
+ * Unknown IDs fall back to the first tab.
108295
+ *
108296
+ * @type {string}
108297
+ */
108298
+ initialTab: {
108299
+ type: String,
108300
+ default: '',
108301
+ },
107768
108302
  /**
107769
108303
  * Initial values keyed by `field.key`. Defaults to an empty
107770
108304
  * object; in practice the consumer passes the current
@@ -107826,14 +108360,30 @@ var script$8 = {
107826
108360
  },
107827
108361
  },
107828
108362
 
107829
- emits: ['save', 'error', 'input', 'widget-event'],
108363
+ emits: ['save', 'error', 'input', 'widget-event', 'tab-change'],
107830
108364
 
107831
108365
  data() {
108366
+ // Resolve the initial active-tab id synchronously so the very
108367
+ // first render has the correct tab active (otherwise tests
108368
+ // that mount + assert without an `await tick` see the empty
108369
+ // default). Mirrors the logic in `resolveInitialTabId` (the
108370
+ // watcher path); keep them aligned.
108371
+ let activeTabId = '';
108372
+ const tabs = Array.isArray(this.tabs) ? this.tabs : [];
108373
+ if (tabs.length > 0) {
108374
+ if (typeof this.initialTab === 'string' && this.initialTab.length > 0
108375
+ && tabs.some(t => t && t.id === this.initialTab)) {
108376
+ activeTabId = this.initialTab;
108377
+ } else if (tabs[0] && typeof tabs[0].id === 'string') {
108378
+ activeTabId = tabs[0].id;
108379
+ }
108380
+ }
107832
108381
  return {
107833
108382
  formData: this.cloneInitial(),
107834
108383
  originalData: this.cloneInitial(),
107835
108384
  saving: false,
107836
108385
  lastError: null,
108386
+ activeTabId,
107837
108387
  }
107838
108388
  },
107839
108389
 
@@ -107852,6 +108402,35 @@ var script$8 = {
107852
108402
  effectiveCustomComponents() {
107853
108403
  return this.customComponents ?? this.cnCustomComponents ?? {}
107854
108404
  },
108405
+ /**
108406
+ * Whether the page is in tabs orchestration mode. True when
108407
+ * `tabs[]` is non-empty — drives the tab-strip render gate
108408
+ * (manifest-settings-orchestration REQ-MSO-5).
108409
+ *
108410
+ * @return {boolean}
108411
+ */
108412
+ hasTabs() {
108413
+ return Array.isArray(this.tabs) && this.tabs.length > 0
108414
+ },
108415
+ /**
108416
+ * The sections to render right now. In flat mode, this is the
108417
+ * `sections` prop directly. In tabs mode, this is the
108418
+ * `sections[]` array of the currently active tab. Centralising
108419
+ * this in one computed keeps the template's `v-for` simple
108420
+ * and decouples it from the body kind dispatcher (which
108421
+ * applies per-section, not per-mode).
108422
+ *
108423
+ * @return {Array<object>}
108424
+ */
108425
+ activeSections() {
108426
+ if (!this.hasTabs) return this.sections || []
108427
+ const active = this.tabs.find(t => t && t.id === this.activeTabId);
108428
+ if (active && Array.isArray(active.sections)) return active.sections
108429
+ // Defensive fallback — should not happen because
108430
+ // `resolveInitialTabId` always lands on a known tab.
108431
+ const first = this.tabs[0];
108432
+ return first && Array.isArray(first.sections) ? first.sections : []
108433
+ },
107855
108434
  },
107856
108435
 
107857
108436
  watch: {
@@ -107862,6 +108441,22 @@ var script$8 = {
107862
108441
  this.originalData = this.cloneInitial();
107863
108442
  },
107864
108443
  },
108444
+ // When `tabs[]` changes (e.g. consumer swaps manifests at
108445
+ // runtime), re-resolve the active tab so the page doesn't get
108446
+ // stuck on a removed id.
108447
+ tabs: {
108448
+ handler() {
108449
+ this.activeTabId = this.resolveInitialTabId();
108450
+ },
108451
+ },
108452
+ // When `initialTab` changes (consumer-controlled tab
108453
+ // activation), follow it.
108454
+ initialTab(next) {
108455
+ if (typeof next === 'string' && next.length > 0) {
108456
+ const exists = this.tabs.some(t => t && t.id === next);
108457
+ if (exists) this.activeTabId = next;
108458
+ }
108459
+ },
107865
108460
  },
107866
108461
 
107867
108462
  methods: {
@@ -107907,11 +108502,20 @@ var script$8 = {
107907
108502
  },
107908
108503
  cloneInitial() {
107909
108504
  const merged = { ...(this.initialValues || {}) };
107910
- // Pre-populate any field with a `default` if no value is set yet.
107911
- // Only flat-field sections contribute defaults; component and
107912
- // widgets sections own their own state.
107913
- for (const section of this.sections || []) {
107914
- if (!Array.isArray(section.fields)) continue
108505
+ // Collect every section across both modes (flat
108506
+ // `sections[]` AND `tabs[].sections[]`) so default values
108507
+ // are applied regardless of orchestration shape.
108508
+ // Only flat-field sections contribute defaults; component
108509
+ // and widgets sections own their own state.
108510
+ const allSections = [];
108511
+ for (const section of this.sections || []) allSections.push(section);
108512
+ for (const tab of this.tabs || []) {
108513
+ if (tab && Array.isArray(tab.sections)) {
108514
+ for (const section of tab.sections) allSections.push(section);
108515
+ }
108516
+ }
108517
+ for (const section of allSections) {
108518
+ if (!section || !Array.isArray(section.fields)) continue
107915
108519
  for (const field of section.fields) {
107916
108520
  if (field.default !== undefined && merged[field.key] === undefined) {
107917
108521
  merged[field.key] = field.default;
@@ -107977,23 +108581,55 @@ var script$8 = {
107977
108581
  },
107978
108582
 
107979
108583
  /**
107980
- * Resolve a `widgets[].type` string to a Vue component. Lookup
107981
- * order:
108584
+ * Resolve a single `widgets[]` entry to a concrete Vue
108585
+ * component. Lookup order:
107982
108586
  *
107983
108587
  * 1. Built-in widget map (`version-info`, `register-mapping`).
107984
- * 2. `effectiveCustomComponents` registry.
108588
+ * 2. `'component'` discriminator (REQ-MSO-6) — resolves
108589
+ * `widget.componentName` against `effectiveCustomComponents`.
108590
+ * 3. Legacy fallback — looks up `widget.type` against
108591
+ * `effectiveCustomComponents`. Kept for back-compat with
108592
+ * manifest-settings-rich-sections consumers; flagged as
108593
+ * deprecated in JSDoc — manifest authors should migrate to
108594
+ * the explicit `{ type: "component", componentName }` shape.
107985
108595
  *
107986
- * Returns `null` (and warns) when neither resolves. Built-ins
107987
- * win on collision so consumers can't accidentally shadow them
107988
- * (REQ-MSRS-2).
108596
+ * Returns `null` (and warns) when nothing resolves. Built-ins
108597
+ * win on collision so consumers can't accidentally shadow them.
107989
108598
  *
107990
- * @param {string} type The widget type string.
108599
+ * @param {object} widget A `widgets[]` entry, e.g. `{ type, props?, componentName? }`.
107991
108600
  * @return {object|null} Vue component or null.
107992
108601
  */
107993
- resolveWidgetComponent(type) {
108602
+ resolveWidgetComponent(widget) {
108603
+ const type = widget && typeof widget.type === 'string' ? widget.type : '';
108604
+ if (!type) return null
107994
108605
  if (Object.prototype.hasOwnProperty.call(BUILTIN_SETTINGS_WIDGETS, type)) {
107995
- return BUILTIN_SETTINGS_WIDGETS[type]
108606
+ const builtin = BUILTIN_SETTINGS_WIDGETS[type];
108607
+ if (builtin === COMPONENT_DISCRIMINATOR) {
108608
+ // REQ-MSO-6: discriminator — look up `componentName`.
108609
+ const name = widget.componentName;
108610
+ if (typeof name !== 'string' || name.length === 0) {
108611
+ // eslint-disable-next-line no-console
108612
+ console.warn(
108613
+ '[CnSettingsPage] Widget {type:"component"} requires a non-empty `componentName`. Widget will be skipped.',
108614
+ );
108615
+ return null
108616
+ }
108617
+ const resolved = this.effectiveCustomComponents[name];
108618
+ if (!resolved) {
108619
+ // eslint-disable-next-line no-console
108620
+ console.warn(
108621
+ `[CnSettingsPage] Widget component "${name}" not found in customComponents registry. Widget will be skipped.`,
108622
+ );
108623
+ return null
108624
+ }
108625
+ return resolved
108626
+ }
108627
+ return builtin
107996
108628
  }
108629
+ // Legacy fallback (manifest-settings-rich-sections REQ-MSRS-2).
108630
+ // Deprecated — manifest authors should migrate to
108631
+ // `{ type: "component", componentName: <X> }`. Kept here so
108632
+ // existing consumers continue working unchanged.
107997
108633
  const resolved = this.effectiveCustomComponents[type];
107998
108634
  if (!resolved) {
107999
108635
  // eslint-disable-next-line no-console
@@ -108012,6 +108648,11 @@ var script$8 = {
108012
108648
  * has already logged a warn. The filter happens here so the
108013
108649
  * template can use a clean `v-for` without nested `v-if`.
108014
108650
  *
108651
+ * The `widgetType` carried on the bubbled `@widget-event`
108652
+ * payload is the widget's `componentName` (when the
108653
+ * discriminator is `'component'`) or `widget.type` otherwise —
108654
+ * giving consumers a stable identifier for the dispatch.
108655
+ *
108015
108656
  * @param {object} section A section entry with `widgets[]`.
108016
108657
  * @param {number} sectionIndex Index in `sections[]`.
108017
108658
  * @return {Array<{key: string, component: object, props: object, widgetType: string, widgetIndex: number}>}
@@ -108021,19 +108662,57 @@ var script$8 = {
108021
108662
  const widgets = Array.isArray(section.widgets) ? section.widgets : [];
108022
108663
  for (let widgetIndex = 0; widgetIndex < widgets.length; widgetIndex++) {
108023
108664
  const widget = widgets[widgetIndex] || {};
108024
- const component = this.resolveWidgetComponent(widget.type);
108665
+ const component = this.resolveWidgetComponent(widget);
108025
108666
  if (!component) continue
108667
+ const widgetType = widget.type === 'component' && typeof widget.componentName === 'string'
108668
+ ? widget.componentName
108669
+ : widget.type;
108026
108670
  entries.push({
108027
108671
  key: `widget-${sectionIndex}-${widgetIndex}`,
108028
108672
  component,
108029
108673
  props: widget.props || {},
108030
- widgetType: widget.type,
108674
+ widgetType,
108031
108675
  widgetIndex,
108032
108676
  });
108033
108677
  }
108034
108678
  return entries
108035
108679
  },
108036
108680
 
108681
+ /**
108682
+ * Resolve the active-tab id on mount / when `tabs[]` changes.
108683
+ * Lookup order: explicit `initialTab` prop → first tab in
108684
+ * `tabs[]` → empty string. Unknown `initialTab` values fall
108685
+ * back to the first tab so the page never gets stuck.
108686
+ * (manifest-settings-orchestration REQ-MSO-5.)
108687
+ *
108688
+ * @return {string} The resolved tab id (empty in flat mode).
108689
+ */
108690
+ resolveInitialTabId() {
108691
+ if (!this.hasTabs) return ''
108692
+ if (typeof this.initialTab === 'string' && this.initialTab.length > 0) {
108693
+ const exists = this.tabs.some(t => t && t.id === this.initialTab);
108694
+ if (exists) return this.initialTab
108695
+ }
108696
+ const first = this.tabs[0];
108697
+ return first && typeof first.id === 'string' ? first.id : ''
108698
+ },
108699
+
108700
+ /**
108701
+ * Handle a tab button click. Switches the active tab and
108702
+ * emits `@tab-change` so consumers can react (e.g. persist the
108703
+ * active tab in their preference store, update the URL hash).
108704
+ * (manifest-settings-orchestration REQ-MSO-5.)
108705
+ *
108706
+ * @param {object} tab The clicked tab definition.
108707
+ * @param {number} tabIndex The tab's index in `tabs[]`.
108708
+ */
108709
+ onTabClick(tab, tabIndex) {
108710
+ if (!tab || typeof tab.id !== 'string') return
108711
+ if (this.activeTabId === tab.id) return
108712
+ this.activeTabId = tab.id;
108713
+ this.$emit('tab-change', { tabId: tab.id, tabIndex });
108714
+ },
108715
+
108037
108716
  /**
108038
108717
  * Re-emit a widget's `widget-event` (caught by the local
108039
108718
  * CnSettingsWidgetMount helper) as a top-level `widget-event`
@@ -108109,11 +108788,51 @@ var __vue_render__$8 = function () {
108109
108788
  )
108110
108789
  : _vm._e(),
108111
108790
  _vm._v(" "),
108112
- _vm._l(_vm.sections, function (section, sectionIndex) {
108791
+ _vm.hasTabs
108792
+ ? _c(
108793
+ "div",
108794
+ {
108795
+ staticClass: "cn-settings-page__tabs",
108796
+ attrs: { role: "tablist" },
108797
+ },
108798
+ _vm._l(_vm.tabs, function (tab, tabIndex) {
108799
+ return _c(
108800
+ "button",
108801
+ {
108802
+ key: "tab-" + tab.id,
108803
+ staticClass: "cn-settings-page__tab",
108804
+ class: {
108805
+ "cn-settings-page__tab--active": _vm.activeTabId === tab.id,
108806
+ },
108807
+ attrs: {
108808
+ role: "tab",
108809
+ type: "button",
108810
+ "aria-selected":
108811
+ _vm.activeTabId === tab.id ? "true" : "false",
108812
+ "aria-controls": "cn-settings-tab-panel-" + tab.id,
108813
+ },
108814
+ on: {
108815
+ click: function ($event) {
108816
+ return _vm.onTabClick(tab, tabIndex)
108817
+ },
108818
+ },
108819
+ },
108820
+ [
108821
+ _vm._v(
108822
+ "\n\t\t\t" + _vm._s(_vm.resolveLabel(tab.label)) + "\n\t\t"
108823
+ ),
108824
+ ]
108825
+ )
108826
+ }),
108827
+ 0
108828
+ )
108829
+ : _vm._e(),
108830
+ _vm._v(" "),
108831
+ _vm._l(_vm.activeSections, function (section, sectionIndex) {
108113
108832
  return _c(
108114
108833
  "CnSettingsCard",
108115
108834
  {
108116
- key: "section-" + sectionIndex,
108835
+ key: "section-" + (_vm.activeTabId || "flat") + "-" + sectionIndex,
108117
108836
  attrs: {
108118
108837
  title: _vm.resolveLabel(section.title),
108119
108838
  icon: section.icon || "",
@@ -108378,7 +109097,7 @@ __vue_render__$8._withStripped = true;
108378
109097
  /* style */
108379
109098
  const __vue_inject_styles__$8 = undefined;
108380
109099
  /* scoped */
108381
- const __vue_scope_id__$8 = "data-v-f43cb932";
109100
+ const __vue_scope_id__$8 = "data-v-4286dbd4";
108382
109101
  /* module identifier */
108383
109102
  const __vue_module_identifier__$8 = undefined;
108384
109103
  /* functional template */
@@ -112183,5 +112902,5 @@ function registerTranslations() {
112183
112902
  register(APP_NAME, bundle.translations);
112184
112903
  }
112185
112904
 
112186
- export { __vue_component__$$ as CnActionsBar, __vue_component__$N as CnAdvancedFormDialog, __vue_component__$2 as CnAppLoading, __vue_component__$3 as CnAppNav, __vue_component__ as CnAppRoot, __vue_component__$n as CnCard, __vue_component__$15 as CnCardGrid, __vue_component__$1f as CnCellRenderer, __vue_component__$C as CnChartWidget, __vue_component__$7 as CnChatPage, __vue_component__$S as CnColorPicker, __vue_component__$18 as CnConfigurationCard, __vue_component__$12 as CnContextMenu, __vue_component__$V as CnCopyDialog, __vue_component__$G as CnDashboardGrid, __vue_component__$B as CnDashboardPage, __vue_component__$1e as CnDataTable, __vue_component__$W as CnDeleteDialog, __vue_component__$1 as CnDependencyMissing, __vue_component__$q as CnDetailCard, __vue_component__$k as CnDetailGrid, __vue_component__$H as CnDetailPage, __vue_component__$14 as CnFacetSidebar, __vue_component__$6 as CnFilesPage, __vue_component__$1d as CnFilterBar, __vue_component__$T as CnFormDialog, __vue_component__$11 as CnIcon, __vue_component__$L as CnIndexPage, __vue_component__$M as CnIndexSidebar, __vue_component__$b as CnInfoWidget, __vue_component__$A as CnItemCard, __vue_component__$U as CnJsonViewer, __vue_component__$J as CnKpiGrid, __vue_component__$9 as CnLogsPage, __vue_component__$K as CnMassActionBar, __vue_component__$Z as CnMassCopyDialog, __vue_component__$_ as CnMassDeleteDialog, __vue_component__$Y as CnMassExportDialog, __vue_component__$X as CnMassImportDialog, __vue_component__$P as CnMetadataTab, __vue_component__$y as CnNoteCard, __vue_component__$p as CnNotesCard, __vue_component__$16 as CnObjectCard, __vue_component__$e as CnObjectDataWidget, __vue_component__$d as CnObjectMetadataWidget, __vue_component__$c as CnObjectSidebar, __vue_component__$10 as CnPageHeader, __vue_component__$4 as CnPageRenderer, __vue_component__$1c as CnPagination, __vue_component__$m as CnProgressBar, __vue_component__$Q as CnPropertiesTab, __vue_component__$R as CnPropertyValueCell, __vue_component__$I as CnRegisterMapping, __vue_component__$13 as CnRowActions, __vue_component__$t as CnSchemaFormDialog, __vue_component__$1b as CnSettingsCard, __vue_component__$8 as CnSettingsPage, __vue_component__$1a as CnSettingsSection, __vue_component__$19 as CnStatsBlock, __vue_component__$l as CnStatsPanel, __vue_component__$1g as CnStatusBadge, __vue_component__$z as CnTabbedFormDialog, __vue_component__$a as CnTableWidget, __vue_component__$o as CnTasksCard, __vue_component__$D as CnTileWidget, __vue_component__$s as CnTimelineStages, __vue_component__$r as CnUserActionMenu, __vue_component__$17 as CnVersionInfoCard, __vue_component__$E as CnWidgetRenderer, __vue_component__$F as CnWidgetWrapper, SEARCH_TYPE, auditTrailsPlugin, buildHeaders, buildQueryString, columnsFromSchema, createCrudStore, createObjectStore, createSubResourcePlugin, defaultPageTypes, emptyPaginated, fieldsFromSchema, filesPlugin, filterWidgetsByVisibility, filtersFromSchema, formatValue, genericError, getCurrentUserGroups, getCurrentUserId, getRegisterApiUrl, getSchemaApiUrl, isWidgetVisible, lifecyclePlugin, liveUpdatesPlugin, logsPlugin, networkError, parseResponseError, registerIcons, registerMappingPlugin, registerTranslations, relationsPlugin, resetVisibilityCache, searchPlugin, selectionPlugin, useAppManifest, useAppStatus, useContextMenu, useDashboardView, useDetailView, useListView, useObjectStore, useSubResource, validateManifest, validateValue };
112905
+ export { __vue_component__$$ as CnActionsBar, __vue_component__$N as CnAdvancedFormDialog, __vue_component__$2 as CnAppLoading, __vue_component__$3 as CnAppNav, __vue_component__ as CnAppRoot, __vue_component__$n as CnCard, __vue_component__$15 as CnCardGrid, __vue_component__$1f as CnCellRenderer, __vue_component__$C as CnChartWidget, __vue_component__$7 as CnChatPage, __vue_component__$S as CnColorPicker, __vue_component__$18 as CnConfigurationCard, __vue_component__$12 as CnContextMenu, __vue_component__$V as CnCopyDialog, __vue_component__$G as CnDashboardGrid, __vue_component__$B as CnDashboardPage, __vue_component__$1e as CnDataTable, __vue_component__$W as CnDeleteDialog, __vue_component__$1 as CnDependencyMissing, __vue_component__$q as CnDetailCard, __vue_component__$k as CnDetailGrid, __vue_component__$H as CnDetailPage, __vue_component__$14 as CnFacetSidebar, __vue_component__$6 as CnFilesPage, __vue_component__$1d as CnFilterBar, __vue_component__$T as CnFormDialog, __vue_component__$11 as CnIcon, __vue_component__$L as CnIndexPage, __vue_component__$M as CnIndexSidebar, __vue_component__$b as CnInfoWidget, __vue_component__$A as CnItemCard, __vue_component__$U as CnJsonViewer, __vue_component__$J as CnKpiGrid, __vue_component__$9 as CnLogsPage, __vue_component__$K as CnMassActionBar, __vue_component__$Z as CnMassCopyDialog, __vue_component__$_ as CnMassDeleteDialog, __vue_component__$Y as CnMassExportDialog, __vue_component__$X as CnMassImportDialog, __vue_component__$P as CnMetadataTab, __vue_component__$y as CnNoteCard, __vue_component__$p as CnNotesCard, __vue_component__$16 as CnObjectCard, __vue_component__$e as CnObjectDataWidget, __vue_component__$d as CnObjectMetadataWidget, __vue_component__$c as CnObjectSidebar, __vue_component__$10 as CnPageHeader, __vue_component__$4 as CnPageRenderer, __vue_component__$1c as CnPagination, __vue_component__$m as CnProgressBar, __vue_component__$Q as CnPropertiesTab, __vue_component__$R as CnPropertyValueCell, __vue_component__$I as CnRegisterMapping, __vue_component__$13 as CnRowActions, __vue_component__$t as CnSchemaFormDialog, __vue_component__$1b as CnSettingsCard, __vue_component__$8 as CnSettingsPage, __vue_component__$1a as CnSettingsSection, __vue_component__$19 as CnStatsBlock, __vue_component__$l as CnStatsPanel, __vue_component__$1g as CnStatusBadge, __vue_component__$z as CnTabbedFormDialog, __vue_component__$a as CnTableWidget, __vue_component__$o as CnTasksCard, __vue_component__$D as CnTileWidget, __vue_component__$s as CnTimelineStages, __vue_component__$r as CnUserActionMenu, __vue_component__$17 as CnVersionInfoCard, __vue_component__$E as CnWidgetRenderer, __vue_component__$F as CnWidgetWrapper, SEARCH_TYPE, auditTrailsPlugin, buildHeaders, buildQueryString, clearResolveCache, columnsFromSchema, createCrudStore, createObjectStore, createSubResourcePlugin, defaultPageTypes, emptyPaginated, fieldsFromSchema, filesPlugin, filterWidgetsByVisibility, filtersFromSchema, formatValue, genericError, getCurrentUserGroups, getCurrentUserId, getRegisterApiUrl, getSchemaApiUrl, isWidgetVisible, lifecyclePlugin, liveUpdatesPlugin, logsPlugin, networkError, parseResponseError, registerIcons, registerMappingPlugin, registerTranslations, relationsPlugin, resetVisibilityCache, resolveManifestSentinels, searchPlugin, selectionPlugin, useAppManifest, useAppStatus, useContextMenu, useDashboardView, useDetailView, useListView, useObjectStore, useSubResource, validateManifest, validateValue };
112187
112906
  //# sourceMappingURL=nextcloud-vue.esm.js.map