From bc88f71675b25273571e2109711b76b187df1653 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 27 Aug 2026 21:32:28 +0200 Subject: [PATCH 1/4] refactor(manifest): the flow pages are an index and a flow `flows` and `flow-detail` are deprecated aliases. `flows` predates named index sources: a flow lives in OpenRegister's native flow table rather than a register/schema pair, so an object-backed index had nothing to bind to and the list needed a page type of its own. `config.entitySource` closes that, so the list is an ordinary `index` and only the EDITOR still needs its own type, now named `flow`. Behaviour is unchanged - both aliases resolve to the same components. The list page's `_note` is updated in the same commit: it asserted the old rationale (that an index "cannot address" a flow), which this change disproves. `config.app` is untouched and is load-bearing: the editor stamps it on a flow created there and the index filters on it. Requires @conduction/nextcloud-vue 2.21, where a named source's columns and create button are actually read. --- src/manifest.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/manifest.json b/src/manifest.json index c72c5a55..24ea316e 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -831,15 +831,15 @@ { "id": "Flows", "route": "/flows", - "type": "flows", + "type": "index", "title": "Flows", - "config": { "app": "stackiq" }, - "_note": "ADR-110 Decision 4: a flow is app-specific, so the authoring surface lives here rather than behind a deep link to another app's list. Rendered by the shared CnFlowsPage page type over OpenRegister's one native flow store (ADR-065), scoped to this app." + "config": { "entitySource": "flows", "app": "stackiq" }, + "_note": "ADR-110 Decision 4: a flow is app-specific, so the authoring surface lives here rather than behind a deep link to another app's list. An ordinary index over the named `flows` source (config.entitySource) rather than the deprecated `flows` page type, reading OpenRegister's one native flow store (ADR-065), scoped to this app by config.app." }, { "id": "FlowDetail", "route": "/flows/:id", - "type": "flow-detail", + "type": "flow", "title": "Flow", "config": { "app": "stackiq" }, "_note": "The shared CnFlowDetail canvas over the same single engine. Controls render in the NC app sidebar so the canvas keeps full width." From 4ee883ad2ae7a181c567507862c9d7317b436e3a Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Thu, 27 Aug 2026 23:26:13 +0200 Subject: [PATCH 2/4] fix(deps): nextcloud-vue 2.20.1, whose manifest schema knows the flow page type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check:manifest failed on this PR with [validate-manifest] schema.version: 2.25.0 Ajv validation: FAIL - /pages/N/type must be equal to one of the allowed values (keyword=enum) This PR declares a `type: "flow"` page. That type entered the manifest schema at 2.26.0, which ships in @conduction/nextcloud-vue 2.20.1 (published today 20:33). The app locked 2.19.0, which carries schema 2.25.0 and has no `flow` in its enum. Both files move. A caret alone changes nothing — package-lock.json is what npm ci installs, and it pinned 2.19.0. Verified by diffing this manifest's page types against each schema enum: against 2.26.0 nothing is rejected; against 2.25.0 exactly the new type is, which is the failure above. --- package-lock.json | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index bda8fc68..bf44dede 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@babel/core": "^7.22.9", "@codemirror/lang-json": "^6.0.0", - "@conduction/nextcloud-vue": "^2.19.0", + "@conduction/nextcloud-vue": "^2.20.1", "@nextcloud/auth": "^2.6.0", "@nextcloud/axios": "^2.5.0", "@nextcloud/capabilities": "^1.2.1", @@ -2192,9 +2192,9 @@ } }, "node_modules/@conduction/nextcloud-vue": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@conduction/nextcloud-vue/-/nextcloud-vue-2.19.0.tgz", - "integrity": "sha512-gSLl4RZ7hy1e2TBuzYZLiTLBMggRYgfuxFMva59JBhb1EP93GgvEd/HpQcg+zoBL9FJPgJw9ir2OGf9Tore7og==", + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/@conduction/nextcloud-vue/-/nextcloud-vue-2.20.1.tgz", + "integrity": "sha512-OEYGzgtH/uhK/nrRCcQitOCzJsIO39AIut+59QXncQRdWuoaaxg/UTIAZJF97IHH2LOHzLNpJcld5k6jJkvlBA==", "license": "EUPL-1.2", "dependencies": { "@ckpack/vue-color": "^1.6.0", @@ -2256,7 +2256,7 @@ "dompurify": "^3.0.0", "eslint": "^8.56.0 || ^9.0.0 || ^10.0.0", "eslint-plugin-vue": "^9.21.0 || ^10.0.0", - "gridstack": "^12.0.0", + "gridstack": "^12.0.0 || ^13.0.0", "marked": "^12.0.0", "pinia": "^2.0.0 || ^3.0.0", "vue": "^3.5.0", diff --git a/package.json b/package.json index 549cf332..10f47a8d 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "dependencies": { "@babel/core": "^7.22.9", "@codemirror/lang-json": "^6.0.0", - "@conduction/nextcloud-vue": "^2.19.0", + "@conduction/nextcloud-vue": "^2.20.1", "@nextcloud/auth": "^2.6.0", "@nextcloud/axios": "^2.5.0", "@nextcloud/capabilities": "^1.2.1", From 79e5a6802d9bbf24b1e3ab1a6552b37509660f50 Mon Sep 17 00:00:00 2001 From: Conduction Release Bot Date: Fri, 28 Aug 2026 09:12:10 +0200 Subject: [PATCH 3/4] chore(deps): bump @conduction/nextcloud-vue to ^2.21.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flow pages need 2.21.0: earlier releases DECLARE a named index source's columns, create button and row actions without reading them, so the migrated page renders a columnless table with no working create action. The lock is the part that matters. CI installs with `npm ci`, which honours package-lock.json and ignores how permissive the caret is — bumping the range alone would change nothing about what actually installs. --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index bf44dede..1fee71ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@babel/core": "^7.22.9", "@codemirror/lang-json": "^6.0.0", - "@conduction/nextcloud-vue": "^2.20.1", + "@conduction/nextcloud-vue": "^2.21.0", "@nextcloud/auth": "^2.6.0", "@nextcloud/axios": "^2.5.0", "@nextcloud/capabilities": "^1.2.1", @@ -2192,9 +2192,9 @@ } }, "node_modules/@conduction/nextcloud-vue": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/@conduction/nextcloud-vue/-/nextcloud-vue-2.20.1.tgz", - "integrity": "sha512-OEYGzgtH/uhK/nrRCcQitOCzJsIO39AIut+59QXncQRdWuoaaxg/UTIAZJF97IHH2LOHzLNpJcld5k6jJkvlBA==", + "version": "2.21.0", + "resolved": "https://registry.npmjs.org/@conduction/nextcloud-vue/-/nextcloud-vue-2.21.0.tgz", + "integrity": "sha512-5HjI4X8k2IYxUeBdHa2OK/MnLhOFf4HxkF5dUGl42dWmYPDaPHjvawDMZcUcyP6wsP+rk0QzmPov4FGu/4Rn7Q==", "license": "EUPL-1.2", "dependencies": { "@ckpack/vue-color": "^1.6.0", diff --git a/package.json b/package.json index 10f47a8d..716fa859 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "dependencies": { "@babel/core": "^7.22.9", "@codemirror/lang-json": "^6.0.0", - "@conduction/nextcloud-vue": "^2.20.1", + "@conduction/nextcloud-vue": "^2.21.0", "@nextcloud/auth": "^2.6.0", "@nextcloud/axios": "^2.5.0", "@nextcloud/capabilities": "^1.2.1", From 003c4374dc47efc35f03f3cfd1eef71cbd2ecb82 Mon Sep 17 00:00:00 2001 From: Conduction Release Bot Date: Fri, 28 Aug 2026 10:03:40 +0200 Subject: [PATCH 4/4] test(e2e): Edit lands on the detail page for a schema that has one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nextcloud-vue 2.21 brings #806: a record whose schema has a same-schema DETAIL page is edited there, not in a modal launched from the table — the modal renders only the schema's flat scalars and cannot express a record whose related rows live elsewhere. CnPageRenderer sets `editOpensDetail` from `detailPageByRegisterSchema`. Both edit blocks in this spec waited on a dialog that no longer opens from the index. They now go through one helper that BRANCHES: dialog if it opens directly, otherwise follow the navigation and click the detail page's header Edit. Which route applies is a property of the schema, not of the test, so branching is the accurate shape rather than a relaxed one — the helper still returns a real, visible edit dialog and every assertion after it is unchanged. --- tests/e2e/workflows/crud-persistence.spec.ts | 40 ++++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/tests/e2e/workflows/crud-persistence.spec.ts b/tests/e2e/workflows/crud-persistence.spec.ts index 7587c34e..4b388d90 100644 --- a/tests/e2e/workflows/crud-persistence.spec.ts +++ b/tests/e2e/workflows/crud-persistence.spec.ts @@ -63,6 +63,40 @@ import { type VoorzieningenConfig, } from './_fixtures' +/** + * Wait for the edit dialog after an `Edit` row action, wherever it opens. + * + * Since nextcloud-vue 2.21 (#806) a record whose schema has a same-schema + * DETAIL page is edited on that page, not in a modal launched from the table: + * the modal renders only the schema's flat scalars and cannot express a record + * whose related rows live elsewhere. `CnPageRenderer` sets `editOpensDetail` + * from `detailPageByRegisterSchema`. + * + * Which route applies is a property of the SCHEMA, so this branches instead of + * assuming. Either way it returns a real, visible edit dialog — the assertion + * is not weakened, only its route. + * + * @param page The Playwright page. + * @return The visible edit dialog locator. + */ +async function editDialogAfterEdit(page: import('@playwright/test').Page) { + const dialog = page.locator('[role="dialog"], .modal-container').first() + const direct = await dialog + .waitFor({ state: 'visible', timeout: 5000 }) + .then(() => true) + .catch(() => false) + if (!direct) { + const headerEdit = page.getByRole('button', { name: /^Edit$/ }).first() + await expect( + headerEdit, + 'detail page header Edit button visible', + ).toBeVisible({ timeout: 15000 }) + await headerEdit.click() + await dialog.waitFor({ state: 'visible', timeout: 15000 }) + } + return dialog +} + let apiCtx: APIRequestContext let cfg: VoorzieningenConfig @@ -204,8 +238,7 @@ test.describe('Contactpersoon CRUD-persistence', () => { await openRowActions(page, contactsUid) await clickAction(page, 'Edit') - const editDialog = page.locator('[role="dialog"], .modal-container').first() - await editDialog.waitFor({ state: 'visible', timeout: 15000 }) + const editDialog = await editDialogAfterEdit(page) // The editor is populated from the persisted row — the values we created // read back into the form (detail read-back persistence). @@ -231,8 +264,7 @@ test.describe('Contactpersoon CRUD-persistence', () => { await openRowActions(page, contactsUid) await clickAction(page, 'Edit') - const editDialog = page.locator('[role="dialog"], .modal-container').first() - await editDialog.waitFor({ state: 'visible', timeout: 15000 }) + const editDialog = await editDialogAfterEdit(page) // The edit form is pre-filled with the existing UID — proves the row // loaded into the editor (read-back persistence). await expect(