@conduction/nextcloud-vue 2.75.0 → 2.76.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 (67) hide show
  1. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +6 -0
  2. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  3. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +3 -2
  4. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  5. package/dist/esm/components/CnCredentials/CnCredentials.vue2.js +11 -10
  6. package/dist/esm/components/CnCredentials/CnCredentials.vue2.js.map +1 -1
  7. package/dist/esm/components/CnCredentials/CnCredentials.vue3.js.map +1 -1
  8. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +8 -5
  9. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  10. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  11. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +144 -6
  12. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  13. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +12 -11
  14. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  15. package/dist/esm/components/CnIndexPage/selfModeActions.js +90 -14
  16. package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
  17. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +15 -3
  18. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  19. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +26 -0
  20. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  21. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +26 -9
  22. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  23. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +19 -3
  24. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  25. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  26. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +153 -38
  27. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
  28. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js +74 -56
  29. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
  30. package/dist/esm/composables/useObjectLock.js +41 -10
  31. package/dist/esm/composables/useObjectLock.js.map +1 -1
  32. package/dist/esm/index.js +1 -0
  33. package/dist/esm/index.js.map +1 -1
  34. package/dist/esm/l10n/en.json.js +7 -1
  35. package/dist/esm/l10n/en.json.js.map +1 -1
  36. package/dist/esm/l10n/nl.json.js +8 -2
  37. package/dist/esm/l10n/nl.json.js.map +1 -1
  38. package/dist/esm/nextcloud-vue.css +88 -1
  39. package/dist/esm/store/useObjectStore.js +37 -9
  40. package/dist/esm/store/useObjectStore.js.map +1 -1
  41. package/dist/esm/utils/objectSignals.js +44 -0
  42. package/dist/esm/utils/objectSignals.js.map +1 -0
  43. package/dist/nextcloud-vue.cjs.js +871 -328
  44. package/dist/nextcloud-vue.cjs.js.map +2 -2
  45. package/dist/nextcloud-vue.css +88 -1
  46. package/l10n/en.json +7 -1
  47. package/l10n/nl.json +8 -2
  48. package/package.json +1 -1
  49. package/src/components/CnCardGrid/CnCardGrid.md +1 -0
  50. package/src/components/CnCardGrid/CnCardGrid.vue +7 -0
  51. package/src/components/CnCredentials/CnCredentials.vue +17 -16
  52. package/src/components/CnDetailPage/CnDetailPage.vue +8 -5
  53. package/src/components/CnIndexPage/CnIndexPage.md +15 -0
  54. package/src/components/CnIndexPage/CnIndexPage.vue +155 -16
  55. package/src/components/CnIndexPage/selfModeActions.js +89 -13
  56. package/src/components/CnIndexPage/useSelfFetchList.js +15 -3
  57. package/src/components/CnObjectCard/CnObjectCard.md +1 -0
  58. package/src/components/CnObjectCard/CnObjectCard.vue +89 -1
  59. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +19 -3
  60. package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +181 -38
  61. package/src/composables/__tests__/useObjectLock.endpoints.spec.js +83 -0
  62. package/src/composables/useObjectLock.js +41 -10
  63. package/src/css/look-board-index.css +35 -1
  64. package/src/index.js +1 -0
  65. package/src/store/useObjectStore.js +37 -9
  66. package/src/utils/index.js +1 -0
  67. package/src/utils/objectSignals.js +41 -0
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/dist/nextcloud-vue.cjs.js.map
3
- size: 38285312 bytes
4
- sha256: 91421df55f0b2ab43c1c2b9428d1808f048aa4d3a8a0909a9681e4f49b38b627
3
+ size: 38337896 bytes
4
+ sha256: a7d0d4bb2ccffd14adbe47978a666f89a6e3b1376dfecb854356280ac6440360
@@ -1490,7 +1490,10 @@
1490
1490
  }
1491
1491
 
1492
1492
  .cn-look-board .cn-index-page__main.cn-index-page__main--table {
1493
- flex: 0 1 auto;
1493
+ /* The body is a row: the card takes the width left beside a sidebar (the
1494
+ board's table card spans the content), and keeps the height of its rows. */
1495
+ flex: 1 1 auto;
1496
+ align-self: flex-start;
1494
1497
  border: 1px solid var(--color-border);
1495
1498
  border-radius: var(--cn-board-card-radius, 12px);
1496
1499
  background: var(--color-main-background);
@@ -1680,6 +1683,37 @@
1680
1683
  background: var(--color-primary-element-light);
1681
1684
  }
1682
1685
 
1686
+ /* The status accent outranks the board's border, also on hover and selection. */
1687
+ .cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent {
1688
+ border-inline-start-width: 4px;
1689
+ padding-inline-start: 13px;
1690
+ }
1691
+
1692
+ .cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-success,
1693
+ .cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-success:hover {
1694
+ border-inline-start-color: var(--color-element-success);
1695
+ }
1696
+
1697
+ .cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-warning,
1698
+ .cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-warning:hover {
1699
+ border-inline-start-color: var(--color-element-warning);
1700
+ }
1701
+
1702
+ .cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-error,
1703
+ .cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-error:hover {
1704
+ border-inline-start-color: var(--color-element-error);
1705
+ }
1706
+
1707
+ .cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-info,
1708
+ .cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-info:hover {
1709
+ border-inline-start-color: var(--color-element-info);
1710
+ }
1711
+
1712
+ .cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-primary,
1713
+ .cn-look-board .cn-object-card.cn-object-card--board.cn-object-card--accent-primary:hover {
1714
+ border-inline-start-color: var(--color-primary-element);
1715
+ }
1716
+
1683
1717
  .cn-look-board .cn-object-card--board .cn-object-card__content {
1684
1718
  display: flex;
1685
1719
  flex: 1 1 auto;
@@ -19425,6 +19459,18 @@ a.cn-xwiki-card__title[data-v-54e85fe3]:hover {
19425
19459
  color: var(--color-text-maxcontrast);
19426
19460
  font-style: italic;
19427
19461
  }
19462
+ .cn-related-objects-widget__section-status[data-v-44c2988f] {
19463
+ margin: 0;
19464
+ padding: var(--default-grid-baseline, 4px) calc(2 * var(--default-grid-baseline, 4px));
19465
+ color: var(--color-text-maxcontrast);
19466
+ font-size: 0.85em;
19467
+ }
19468
+ .cn-related-objects-widget__section-error[data-v-44c2988f] {
19469
+ margin: 0;
19470
+ padding: var(--default-grid-baseline, 4px) calc(2 * var(--default-grid-baseline, 4px));
19471
+ color: var(--color-error-text, var(--color-error));
19472
+ font-size: 0.85em;
19473
+ }
19428
19474
  .cn-related-objects-widget__empty-state[data-v-44c2988f] {
19429
19475
  /* NcEmptyContent defaults to filling tall parents; inside a widget cell a
19430
19476
  compact block reads better. */
@@ -22090,6 +22136,47 @@ to { background-position: -200% 0;
22090
22136
  border-color: var(--color-primary-element);
22091
22137
  background: var(--color-primary-element-light);
22092
22138
  }
22139
+
22140
+ /* The 4px start border takes 3px of padding, so the content stays put. */
22141
+ .cn-object-card.cn-object-card--accent[data-v-6f672c8f] {
22142
+ border-inline-start-width: 4px;
22143
+ padding-inline-start: 13px;
22144
+ }
22145
+ .cn-object-card.cn-object-card--accent-success[data-v-6f672c8f] {
22146
+ border-inline-start-color: var(--color-element-success);
22147
+ }
22148
+ .cn-object-card.cn-object-card--accent-warning[data-v-6f672c8f] {
22149
+ border-inline-start-color: var(--color-element-warning);
22150
+ }
22151
+ .cn-object-card.cn-object-card--accent-error[data-v-6f672c8f] {
22152
+ border-inline-start-color: var(--color-element-error);
22153
+ }
22154
+ .cn-object-card.cn-object-card--accent-info[data-v-6f672c8f] {
22155
+ border-inline-start-color: var(--color-element-info);
22156
+ }
22157
+ .cn-object-card.cn-object-card--accent-primary[data-v-6f672c8f] {
22158
+ border-inline-start-color: var(--color-primary-element);
22159
+ }
22160
+ .cn-object-card__accent-icon[data-v-6f672c8f] {
22161
+ display: inline-flex;
22162
+ vertical-align: -2px;
22163
+ margin-inline-end: 4px;
22164
+ }
22165
+ .cn-object-card--accent-success .cn-object-card__accent-icon[data-v-6f672c8f] {
22166
+ color: var(--color-text-success);
22167
+ }
22168
+ .cn-object-card--accent-warning .cn-object-card__accent-icon[data-v-6f672c8f] {
22169
+ color: var(--color-text-warning);
22170
+ }
22171
+ .cn-object-card--accent-error .cn-object-card__accent-icon[data-v-6f672c8f] {
22172
+ color: var(--color-text-error);
22173
+ }
22174
+ .cn-object-card--accent-info .cn-object-card__accent-icon[data-v-6f672c8f] {
22175
+ color: var(--color-element-info);
22176
+ }
22177
+ .cn-object-card--accent-primary .cn-object-card__accent-icon[data-v-6f672c8f] {
22178
+ color: var(--color-primary-element);
22179
+ }
22093
22180
  .cn-object-card__checkbox[data-v-6f672c8f] {
22094
22181
  flex-shrink: 0;
22095
22182
  padding-top: 2px;
package/l10n/en.json CHANGED
@@ -1,5 +1,10 @@
1
1
  {
2
2
  "translations": {
3
+ "Linked items": "Linked items",
4
+ "Still loading: {sections}": "Still loading: {sections}",
5
+ "Could not load {section}.": "Could not load {section}.",
6
+ "Organisation credentials let apps act for your organisation. Keepiq, the credential vault of Nextcloud, keeps the secret. The app never holds it. You choose which apps may use each credential.": "Organisation credentials let apps act for your organisation. Keepiq, the credential vault of Nextcloud, keeps the secret. The app never holds it. You choose which apps may use each credential.",
7
+ "Apps sometimes act for you on an external service. You give the secret to Nextcloud once, and Keepiq keeps it in an encrypted vault. Apps make the call through Keepiq and never see the secret. You choose which apps may use each credential. Share one across apps, or keep one per app.": "Apps sometimes act for you on an external service. You give the secret to Nextcloud once, and Keepiq keeps it in an encrypted vault. Apps make the call through Keepiq and never see the secret. You choose which apps may use each credential. Share one across apps, or keep one per app.",
3
8
  "Show as": "Show as",
4
9
  "Board": "Board",
5
10
  "Calendar": "Calendar",
@@ -1284,7 +1289,8 @@
1284
1289
  "Development environment": "Development environment",
1285
1290
  "Test environment": "Test environment",
1286
1291
  "Acceptance environment": "Acceptance environment",
1287
- "About {label}": "About {label}"
1292
+ "About {label}": "About {label}",
1293
+ "Could not load the form for this item.": "Could not load the form for this item."
1288
1294
  },
1289
1295
  "plurals": {
1290
1296
  "%n year": [
package/l10n/nl.json CHANGED
@@ -1,5 +1,10 @@
1
1
  {
2
2
  "translations": {
3
+ "Linked items": "Gekoppelde items",
4
+ "Still loading: {sections}": "Nog aan het laden: {sections}",
5
+ "Could not load {section}.": "{section} kon niet worden geladen.",
6
+ "Organisation credentials let apps act for your organisation. Keepiq, the credential vault of Nextcloud, keeps the secret. The app never holds it. You choose which apps may use each credential.": "Met inloggegevens van de organisatie handelen apps namens je organisatie. Keepiq, de kluis voor inloggegevens van Nextcloud, bewaart het geheim. De app krijgt het nooit in handen. Je kiest welke apps elk inloggegeven mogen gebruiken.",
7
+ "Apps sometimes act for you on an external service. You give the secret to Nextcloud once, and Keepiq keeps it in an encrypted vault. Apps make the call through Keepiq and never see the secret. You choose which apps may use each credential. Share one across apps, or keep one per app.": "Apps handelen soms namens jou bij een externe dienst. Je geeft het geheim één keer aan Nextcloud, en Keepiq bewaart het in een versleutelde kluis. Apps doen de aanvraag via Keepiq en zien het geheim nooit. Je kiest welke apps elk inloggegeven mogen gebruiken. Deel er één tussen apps, of houd er één per app.",
3
8
  "Show as": "Toon als",
4
9
  "Board": "Bord",
5
10
  "Calendar": "Kalender",
@@ -171,7 +176,7 @@
171
176
  "This integration is not set up yet. Configure a connection to start using it.": "Deze integratie is nog niet ingesteld. Configureer een verbinding om hem te gebruiken.",
172
177
  "This integration is not set up yet. Install and enable the {app} app to start using it.": "Deze integratie is nog niet ingesteld. Installeer en activeer de app {app} om hem te gebruiken.",
173
178
  "Set up {app}": "{app} instellen",
174
- "Add {type}": "Nieuwe {type}",
179
+ "Add {type}": "{type} toevoegen",
175
180
  "Add new contact": "Nieuw contact toevoegen",
176
181
  "Link contact": "Contact koppelen",
177
182
  "Search contacts": "Contacten zoeken",
@@ -1335,7 +1340,8 @@
1335
1340
  "Development environment": "Ontwikkelomgeving",
1336
1341
  "Test environment": "Testomgeving",
1337
1342
  "Acceptance environment": "Acceptatieomgeving",
1338
- "About {label}": "Over {label}"
1343
+ "About {label}": "Over {label}",
1344
+ "Could not load the form for this item.": "Kon het formulier voor dit item niet laden."
1339
1345
  },
1340
1346
  "plurals": {
1341
1347
  "%n year": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.75.0",
3
+ "version": "2.76.0",
4
4
  "description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
5
5
  "homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
6
6
  "bugs": {
@@ -106,6 +106,7 @@ Custom card slot — replace the default CnObjectCard with your own rendering:
106
106
  |---|---|---|---|
107
107
  | `selectable` | `Boolean` | `false` | When `true`, each card shows a selection checkbox and the grid emits `select` events |
108
108
  | `selectedIds` | `Array` | `[]` | Array of currently selected object IDs (matched against `object[rowKey]`) |
109
+ | `accentOf` | `Function` | `null` | `(object) => { variant, icon?, label? } \| null`, each card's status accent (see CnObjectCard `accent`) |
109
110
 
110
111
  ## Board look props
111
112
 
@@ -40,6 +40,7 @@
40
40
  :status="statusOf ? statusOf(object) : null"
41
41
  :leading="leadingOf ? leadingOf(object) : null"
42
42
  :footerAction="footerActionOf ? footerActionOf(object) : null"
43
+ :accent="accentOf ? accentOf(object) : null"
43
44
  v-on="cardListeners(object)">
44
45
  <template v-if="$slots['card-actions']" #actions="{ object: obj }">
45
46
  <slot name="card-actions" :object="obj" />
@@ -182,6 +183,12 @@ export default {
182
183
  default: null,
183
184
  },
184
185
 
186
+ /** `(object) => { variant, icon?, label? }` for each card's status accent. See CnObjectCard `accent`. */
187
+ accentOf: {
188
+ type: Function,
189
+ default: null,
190
+ },
191
+
185
192
  /** Text shown when there are no objects */
186
193
  emptyText: {
187
194
  type: String,
@@ -2,25 +2,25 @@
2
2
  - SPDX-License-Identifier: EUPL-1.2
3
3
  - SPDX-FileCopyrightText: 2026 Conduction B.V.
4
4
  -
5
- - CnCredentials — the settings surface for the OpenRegister credential
5
+ - CnCredentials: the settings surface for the OpenRegister credential
6
6
  - broker. Apps occasionally need to act on your behalf against an external
7
7
  - service (GitHub, GitLab, …). Rather than hand the secret to the app, you
8
- - give it once to OpenRegister, which stores it in Keepiq — Nextcloud's
8
+ - give it once to OpenRegister, which stores it in Keepiq, Nextcloud's
9
9
  - native, encrypted credential vault. Apps then make the outbound call
10
10
  - THROUGH OpenRegister and never see the secret itself. You decide which
11
11
  - apps may use each credential, so one credential can be shared across apps
12
12
  - or kept dedicated to one.
13
13
  -
14
14
  - Two scopes render from this one component:
15
- - • scope="personal" (default) — the signed-in user's own
15
+ - • scope="personal" (default), the signed-in user's own
16
16
  - credentials, shown in an app's *personal* settings. From here you may
17
17
  - only add/remove *the app you are currently in* to/from a credential,
18
18
  - or add a new credential (the current app is auto-added). You never
19
19
  - manage the full app list of another app from here.
20
- - • scope="organisation" — organisation-wide credentials, shown in an
20
+ - • scope="organisation" , organisation-wide credentials, shown in an
21
21
  - app's *admin* settings. An admin manages the full allowed-app list.
22
22
  -
23
- - Talks to OpenRegister's credential endpoints (metadata only — a secret is
23
+ - Talks to OpenRegister's credential endpoints (metadata only, a secret is
24
24
  - write-only and is NEVER returned or displayed):
25
25
  - GET /apps/openregister/api/credentials?scope={scope}
26
26
  - GET /apps/openregister/api/credentials/providers
@@ -148,7 +148,7 @@
148
148
  </ul>
149
149
  </section>
150
150
 
151
- <!-- Add credential — inline wizard: pick a provider, then fill it in. -->
151
+ <!-- Add credential: inline wizard: pick a provider, then fill it in. -->
152
152
  <section class="cn-credentials__section">
153
153
  <NcButton v-if="!adding"
154
154
  variant="secondary"
@@ -269,7 +269,7 @@ const PROVIDERS_PATH = '/apps/openregister/api/credentials/providers'
269
269
  * Hardcoded per-provider presentation. Keyed by the OpenRegister catalogue
270
270
  * identifier. `colour` drives the tile; `setupHelp`/`setupUrl` explain how to
271
271
  * obtain the secret; `secretLabel` names the field. Providers not listed here
272
- * still work — they render a neutral tile from their identifier.
272
+ * still work, they render a neutral tile from their identifier.
273
273
  */
274
274
  const PROVIDER_META = {
275
275
  github: {
@@ -339,7 +339,7 @@ export default {
339
339
  },
340
340
 
341
341
  /**
342
- * The current app's manifest `credentials[]` declarations — the
342
+ * The current app's manifest `credentials[]` declarations: the
343
343
  * providers this app can reach through the broker. Shape:
344
344
  * `[{ provider, reason, scopes }]`. Rendered read-only under the intro.
345
345
  *
@@ -404,15 +404,16 @@ export default {
404
404
  },
405
405
 
406
406
  /**
407
- * Reframed intro copy per scope.
407
+ * Reframed intro copy per scope. No em-dashes, sentences under 16 words.
408
408
  *
409
+ * @spec openspec/changes/r4-object-lock-url-and-credentials-copy/specs/credentials-settings/spec.md
409
410
  * @return {string} Intro text.
410
411
  */
411
412
  introText() {
412
413
  if (this.scope === 'organisation') {
413
- return t('nextcloud-vue', 'Organisation credentials let apps act on behalf of your organisation. The secret is stored in Keepiq — Nextcloud\'s native credential vault — never in the app. You choose which apps may use each credential.')
414
+ return t('nextcloud-vue', 'Organisation credentials let apps act for your organisation. Keepiq, the credential vault of Nextcloud, keeps the secret. The app never holds it. You choose which apps may use each credential.')
414
415
  }
415
- return t('nextcloud-vue', 'Apps sometimes need to act on your behalf against an external service. So they never hold your secrets, you give a secret to Nextcloud once and it is kept in Keepiq — a native, encrypted credential vault. Apps then make the call through Keepiq and never see the secret. You decide which apps may use each credential — share one across apps, or keep one per app.')
416
+ return t('nextcloud-vue', 'Apps sometimes act for you on an external service. You give the secret to Nextcloud once, and Keepiq keeps it in an encrypted vault. Apps make the call through Keepiq and never see the secret. You choose which apps may use each credential. Share one across apps, or keep one per app.')
416
417
  },
417
418
 
418
419
  /**
@@ -438,7 +439,7 @@ export default {
438
439
  const serverIds = this.providers.map((p) => p.identifier)
439
440
  let ids = serverIds.length ? serverIds : Object.keys(PROVIDER_META)
440
441
  // When the app declares which providers it uses (appCredentials),
441
- // only offer those — an app should not let you add a credential for
442
+ // only offer those, an app should not let you add a credential for
442
443
  // a provider it has no code path to use.
443
444
  if (this.supportedProviders.size > 0) {
444
445
  ids = ids.filter((id) => this.supportedProviders.has(id))
@@ -448,7 +449,7 @@ export default {
448
449
 
449
450
  /**
450
451
  * Provider identifiers the app declares it uses, derived from
451
- * `appCredentials`. Empty when the app declares nothing — filtering is
452
+ * `appCredentials`. Empty when the app declares nothing, filtering is
452
453
  * then a no-op, so consumers that don't declare providers are unchanged.
453
454
  *
454
455
  * @return {Set<string>} Supported provider identifiers.
@@ -460,7 +461,7 @@ export default {
460
461
  },
461
462
 
462
463
  /**
463
- * Stored credentials to display — filtered to the app's supported
464
+ * Stored credentials to display: filtered to the app's supported
464
465
  * providers when it declares any, so you can only see and authorise
465
466
  * credentials the app can actually use; otherwise all of them.
466
467
  *
@@ -503,7 +504,7 @@ export default {
503
504
  },
504
505
 
505
506
  /**
506
- * Whether the add form can be submitted — name, provider and a secret
507
+ * Whether the add form can be submitted: name, provider and a secret
507
508
  * are required (the whole point is to store a secret).
508
509
  *
509
510
  * @return {boolean} True when submittable.
@@ -727,7 +728,7 @@ export default {
727
728
  },
728
729
 
729
730
  /**
730
- * Human-readable provider title — hardcoded catalogue first, then the
731
+ * Human-readable provider title: hardcoded catalogue first, then the
731
732
  * server title, then the raw identifier.
732
733
  *
733
734
  * @param {string} identifier The provider identifier.
@@ -2727,18 +2727,21 @@ export default {
2727
2727
  () => props.objectId,
2728
2728
  { enabled: () => Boolean(resolveType() && props.objectId) },
2729
2729
  )
2730
- const sidebarReg = props.sidebarProps?.register || props.resolvedSidebar?.register || props.register || ''
2731
- const sidebarSchema = props.sidebarProps?.schema || props.resolvedSidebar?.schema || props.schema || ''
2730
+ // Getters, read at call time: a register or schema that arrives after
2731
+ // mount (route params, a resolved sidebar) must reach the lock URL. They
2732
+ // used to be consts captured once at setup.
2733
+ const sidebarReg = () => props.sidebarProps?.register || props.resolvedSidebar?.register || props.register || ''
2734
+ const sidebarSchema = () => props.sidebarProps?.schema || props.resolvedSidebar?.schema || props.schema || ''
2732
2735
  const lock = useObjectLock(
2733
2736
  subscriptionStore,
2734
- () => sidebarReg,
2735
- () => resolveType() || sidebarSchema,
2737
+ sidebarReg,
2738
+ () => resolveType() || sidebarSchema(),
2736
2739
  () => props.objectId,
2737
2740
  // The third argument is the OBJECT CACHE KEY (`<register>-<schema>`)
2738
2741
  // and the lock URL needs the schema SLUG. They were one string, and
2739
2742
  // the acquire went to `/api/objects/<register>/<register>-<schema>/`,
2740
2743
  // which no register answers.
2741
- { schemaSlug: () => props.schema || sidebarSchema },
2744
+ { schemaSlug: () => props.schema || sidebarSchema() },
2742
2745
  )
2743
2746
  return {
2744
2747
  ...registryExposed,
@@ -434,6 +434,21 @@ list nested under a parent route (`/forms/:id/submissions`) is a fully
434
434
  declarative page. When `objects` **is** supplied (every existing consumer),
435
435
  nothing changes — no store is touched and `filter` has no effect.
436
436
 
437
+ ### Mixed-schema lists (`collectionUrl`)
438
+
439
+ Set `collectionUrl` to list from an endpoint that searches several
440
+ register/schema pairs at once. `register` and `schema` stay the page's own
441
+ pair (columns, Add, export, import); a row of another pair edits, saves and
442
+ deletes through its own `@self.register` / `@self.schema`.
443
+
444
+ ```js static
445
+ <CnIndexPage
446
+ title="Publications"
447
+ register="19"
448
+ schema="24"
449
+ :collection-url="generateUrl('/apps/opencatalogi/api/{slug}', { slug })" />
450
+ ```
451
+
437
452
  ## Named entity sources (`config.entitySource`) — lists that are not OpenRegister objects
438
453
 
439
454
  Self-fetch needs a `register` + `schema` pair. Some lists have neither: a flow