Release: merge development into beta - #71
Conversation
Quality Report
Summary
PHP Quality
Vue Quality
Security
License Compliance
composer dependencies (110 total)
npm dependencies (266 total)
PHPUnit Tests
Code coverage: 0% (0 / 10 statements) Integration Tests (Newman)Newman integration tests were not enabled for this run. Generated automatically by the Quality workflow.
|
Quality Report
Summary
PHP Quality
Vue Quality
Security
License Compliance
composer dependencies (110 total)
npm dependencies (266 total)
PHPUnit Tests
Code coverage: 0% (0 / 10 statements) Integration Tests (Newman)Newman integration tests were not enabled for this run. Generated automatically by the Quality workflow.
|
Quality Report
Summary
PHP Quality
Vue Quality
Security
License Compliance
PHPUnit Tests
Integration Tests (Newman)
Generated automatically by the Quality workflow.
|
Quality Report
Summary
PHP Quality
Vue Quality
Security
License Compliance
composer dependencies (110 total)
npm dependencies (266 total)
PHPUnit Tests
Integration Tests (Newman)Newman integration tests were not enabled for this run. Generated automatically by the Quality workflow.
|
Quality Report
Summary
PHP Quality
Vue Quality
Security
License Compliance
composer dependencies (110 total)
npm dependencies (266 total)
PHPUnit Tests
Code coverage: 0% (0 / 10 statements) Integration Tests (Newman)Newman integration tests were not enabled for this run. Generated automatically by the Quality workflow.
|
…bject write (#350) * fix(grondslag): guard string casts so array-valued OR properties stop flooding the log GrondslagProposalService runs on the OpenRegister object-write path and cast `$base['name']` / `$base['description']` with a bare (string) cast. An OpenRegister property may legally be an array (multi-value / nested), and casting one raises a PHP "Array to string conversion" warning per field per object. Measured 2026-07-28 on the dev instance: a SINGLE object create produced 6,240 of these warnings (6,623 log lines total), which is a material part of why `POST /apps/openregister/api/objects/...` was unusable and why nextcloud.log grows without bound (cf. the 163GB log that filled the Docker disk and PANICked Postgres). Adds a scalar-guarded asString() helper and routes both casts through it. * perf(enrichment): stop building the admin-settings payload on every object write EnrichmentRunner runs inside OpenRegister's object-created dispatch — i.e. inside an unrelated app's save. To answer the boolean 'is enrichment enabled' it called SettingsService::getAllSettings(), which assembles what the ADMIN SETTINGS PAGE needs: every available register with its schemas, the object-type configuration, OCR status and the grondslag selector data. Measured on the dev instance 2026-07-29, one object create: SchemaMapper::find() calls from this path 1,471 share of ALL schema reads during a create 96% 1,471 DISTINCT schemas, so OpenRegister's request cache could not help — the caller was genuinely walking nearly every schema on the instance (1,917 of them) to read three IAppConfig booleans. Added SettingsService::getFeatureToggles() and pointed the listener at it. Same three values, no register or schema touched. Effect on an OpenRegister object create (median of 3, same payload): wall 13.7s -> 3.2s schema sequential scans 3,019 -> 58 Part of openregister/openspec/changes/object-write-sub-500ms task 1.
…est on the instance (#351) * fix(grondslag): guard string casts so array-valued OR properties stop flooding the log GrondslagProposalService runs on the OpenRegister object-write path and cast `$base['name']` / `$base['description']` with a bare (string) cast. An OpenRegister property may legally be an array (multi-value / nested), and casting one raises a PHP "Array to string conversion" warning per field per object. Measured 2026-07-28 on the dev instance: a SINGLE object create produced 6,240 of these warnings (6,623 log lines total), which is a material part of why `POST /apps/openregister/api/objects/...` was unusable and why nextcloud.log grows without bound (cf. the 163GB log that filled the Docker disk and PANICked Postgres). Adds a scalar-guarded asString() helper and routes both casts through it. * perf(enrichment): stop building the admin-settings payload on every object write EnrichmentRunner runs inside OpenRegister's object-created dispatch — i.e. inside an unrelated app's save. To answer the boolean 'is enrichment enabled' it called SettingsService::getAllSettings(), which assembles what the ADMIN SETTINGS PAGE needs: every available register with its schemas, the object-type configuration, OCR status and the grondslag selector data. Measured on the dev instance 2026-07-29, one object create: SchemaMapper::find() calls from this path 1,471 share of ALL schema reads during a create 96% 1,471 DISTINCT schemas, so OpenRegister's request cache could not help — the caller was genuinely walking nearly every schema on the instance (1,917 of them) to read three IAppConfig booleans. Added SettingsService::getFeatureToggles() and pointed the listener at it. Same three values, no register or schema touched. Effect on an OpenRegister object create (median of 3, same payload): wall 13.7s -> 3.2s schema sequential scans 3,019 -> 58 Part of openregister/openspec/changes/object-write-sub-500ms task 1. * perf(settings): persist configuration_version so the import gate can close SettingsInitializer::initialize() reads 'configuration_version' to decide whether its OpenRegister configuration is already imported. Nothing ever wrote that key — not this app, not OpenRegister — so $currentVersion stayed at its '0.0.0' default forever, version_compare never short-circuited, and the full importFromApp ran again on every call. It is called from Application::boot(), so that is EVERY request to the whole Nextcloud instance, not just DocuDesk's own pages. Measured 2026-07-29 on the dev instance (OpenRegister object create, 10 runs): configuration_version absent median 354ms configuration_version present median 255ms ~100ms per request, ~28%. Confirmed by call-path attribution: the create's schema lookups drop 23 -> 9, of which the ImportHandler contribution goes 14 -> 0. DocuDesk is the only app in the fleet that runs a settings import from boot(); the others do it from cron or a repair step. Keeping the call but making the gate work is the smaller change, and it now self-heals: the first request after an upgrade imports once and records the version. Second cost this class of pattern has produced today — see the sibling fix in 0495b9e, where an is-enrichment-enabled boolean reached getAllSettings() and issued 1,471 schema reads per object create.
* fix(grondslag): guard string casts so array-valued OR properties stop flooding the log GrondslagProposalService runs on the OpenRegister object-write path and cast `$base['name']` / `$base['description']` with a bare (string) cast. An OpenRegister property may legally be an array (multi-value / nested), and casting one raises a PHP "Array to string conversion" warning per field per object. Measured 2026-07-28 on the dev instance: a SINGLE object create produced 6,240 of these warnings (6,623 log lines total), which is a material part of why `POST /apps/openregister/api/objects/...` was unusable and why nextcloud.log grows without bound (cf. the 163GB log that filled the Docker disk and PANICked Postgres). Adds a scalar-guarded asString() helper and routes both casts through it. * perf(enrichment): stop building the admin-settings payload on every object write EnrichmentRunner runs inside OpenRegister's object-created dispatch — i.e. inside an unrelated app's save. To answer the boolean 'is enrichment enabled' it called SettingsService::getAllSettings(), which assembles what the ADMIN SETTINGS PAGE needs: every available register with its schemas, the object-type configuration, OCR status and the grondslag selector data. Measured on the dev instance 2026-07-29, one object create: SchemaMapper::find() calls from this path 1,471 share of ALL schema reads during a create 96% 1,471 DISTINCT schemas, so OpenRegister's request cache could not help — the caller was genuinely walking nearly every schema on the instance (1,917 of them) to read three IAppConfig booleans. Added SettingsService::getFeatureToggles() and pointed the listener at it. Same three values, no register or schema touched. Effect on an OpenRegister object create (median of 3, same payload): wall 13.7s -> 3.2s schema sequential scans 3,019 -> 58 Part of openregister/openspec/changes/object-write-sub-500ms task 1. * perf(settings): persist configuration_version so the import gate can close SettingsInitializer::initialize() reads 'configuration_version' to decide whether its OpenRegister configuration is already imported. Nothing ever wrote that key — not this app, not OpenRegister — so $currentVersion stayed at its '0.0.0' default forever, version_compare never short-circuited, and the full importFromApp ran again on every call. It is called from Application::boot(), so that is EVERY request to the whole Nextcloud instance, not just DocuDesk's own pages. Measured 2026-07-29 on the dev instance (OpenRegister object create, 10 runs): configuration_version absent median 354ms configuration_version present median 255ms ~100ms per request, ~28%. Confirmed by call-path attribution: the create's schema lookups drop 23 -> 9, of which the ImportHandler contribution goes 14 -> 0. DocuDesk is the only app in the fleet that runs a settings import from boot(); the others do it from cron or a repair step. Keeping the call but making the gate work is the smaller change, and it now self-heals: the first request after an upgrade imports once and records the version. Second cost this class of pattern has produced today — see the sibling fix in 0495b9e, where an is-enrichment-enabled boolean reached getAllSettings() and issued 1,471 schema reads per object create. * feat(icons): adopt the ADR-077 semantic icon vocabulary Menu icons across the fleet had drifted into meaninglessness: a scan of 21 manifest-shipping apps found 120 distinct icons for 262 distinct labels, with one glyph standing for as many as 18 unrelated concepts (`icon-category-monitoring`) and the same concept drawn differently per app — Store was `icon-category-integration` in one app and `icon-category-organization` in another. Moves this app's menu onto the shared vocabulary: MDI PascalCase names, one concept to one icon. Tier A entries (Dashboard, Documentation, Settings, Store, Features & roadmap) now match every other Conduction app, which is the whole point — a glyph should mean the same thing wherever a user meets it. Two defect classes are fixed along the way: * Icon names that do not exist in vue-material-design-icons at all. They could never resolve — rendering a help-circle at best, nothing at all in the navigation. * Menu entries that rendered with NO icon, because CnAppNav resolves an MDI name only through the registry `registerIcons()` populates, with no fallback for a name the app never registered. Apps that relied on legacy `icon-*` classes registered nothing at all and were fine until the first MDI name appeared. src/icons.js is generated from the app's own manifests and register files, so every name the app references is registered and the migration stands on its own against the CURRENTLY RELEASED @conduction/nextcloud-vue — it does not wait on the library-side vocabulary (ConductionNL/nextcloud-vue#563). Verified: 0 menu entries render without an icon (was 51 fleet-wide), every icon import resolves against the app's own node_modules, and hydra's gate-60 icon-vocabulary check passes with no failures or warnings. Spec: ADR-077 (ConductionNL/hydra#408).
Fix Conduction → ConductionNL org reference (all workflows failed with "workflow was not found") and rename the branch-protection caller job `check` → `branch-protection` to satisfy the org ruleset's required check.
…rest (#354) * perf(events): declare DossierCheckedOnListener's register/schema interest DossierCheckedOnListener only ever acts on `docudesk`/`dossier` objects (its own REGISTER / DOSSIER_SCHEMA constants), yet it was registered globally: every object write anywhere on the instance constructed it and its GrondslagenSummaryService dependency before the guard rejected the object. Route it through OpenRegister's ObjectEventSubscription, which records the declared slugs at registration time and dispatches through one shared proxy, so an uninterested write never reaches the listener. Guarded on class_exists so an instance whose OpenRegister predates the mechanism falls back to the exact global registration it replaced. DocuDeskEventListener is deliberately left unfiltered: DocuDeskEventHandler identifies its work by payload SHAPE (looksLikeDossier / detectPolicyShape) and EnrichmentRunner enriches metadata on every object on the instance regardless of register, so any slug declaration there would silently drop work. * fix(events): subscribe from boot() so the OpenRegister guard is order-independent Declaring the filtered subscription in register() was boot-order sensitive: Nextcloud enables each app's autoloader immediately before calling that app's own register(), so OpenRegister's ObjectEventSubscription was only autoloadable to apps registering after it. DocuDesk is app 21 of 92 and OpenRegister is 52, so the class_exists() guard here was ALWAYS false and this app fell back to an unfiltered registration that looked identical to a working narrowing — one of seven fleet conversions that were inert while reading as successful. boot() runs only after every app's register() has completed, so the guard resolves regardless of this app's position. The fallback now logs a warning naming the app and listener instead of degrading silently. Verified live on the reference instance: with this change DocuDesk's DossierCheckedOnListener appears in the subscription registry (count 3 -> 4) and fires on a docudesk/dossier update (invoked=1, listenerUs=1103) while being skipped on a larpingapp/character update of the same event class (invoked=0).
…roprietary vue3-apexcharts (#355) * ci: point reusable workflows at ConductionNL/.github and enable the frontend suites docudesk's CI has produced ZERO jobs for two months. Every caller workflow referenced `Conduction/.github` — an org that does not exist (`GET /repos/Conduction/.github` -> 404). Actions cannot resolve the reference, so each run fails instantly with no jobs at all. The tell is visible in `gh run list --json name`: docudesk's runs are named `.github/workflows/code-quality.yml` (the raw path, i.e. never resolved) while its one local workflow shows its real name, `l10n`. The most recent Code Quality run on `development` reports `total_count: 0` jobs. Introduced by 9279c8c (2026-06-01) "point reusable workflows at Conduction/.github (org rename)" — the rename went the wrong way. Since then no ESLint, stylelint, PHPCS, PHPMD, PHPStan, Psalm, licence scan, security scan, SBOM or PHPUnit has run on this repo. Fixed in all 8 caller workflows. Two of them (issue-triage, openspec-sync) also pinned `@feature/openspec-project-sync`, a branch that does not exist in ConductionNL/.github; both now use `@main`, where the same inputs (`app-name`, `backlog-existing`, `PROJECT_TOKEN`) are still declared. code-quality.yml additionally cloned `Conduction/openregister` through the `additional-apps` input — a `uses:`-only grep would have missed that. `.forgejo/workflows/` is deliberately untouched: on Codeberg the org really is `Conduction`. branch-protection.yml also adopts the job id and `permissions: {}` from #353 so the check reports as `branch-protection / check-branch`, the context name the org ruleset requires. Separately: `frontend-checks` was never set, and it defaults to `"[]"`. `enable-frontend: true` only buys eslint + stylelint, so the repo's own 77-test Jest suite, 45-test Vitest suite, manifest schema validation and l10n key check had ALSO never executed on a pull request. All four are green locally on the pinned node 20 / npm 10 and are now wired in. * feat: migrate to Vue 3 + @conduction/nextcloud-vue 2.1.0-vue3.13 Vue 2.7 -> Vue 3.5, @nextcloud/vue 8 -> 9, vue-router 3 -> 4, pinia 2 -> 3, @nextcloud/dialogs 6 -> 7, webpack-vue-config 6 -> 7, vue-loader 15 -> 17, vue-template-compiler dropped. @conduction/nextcloud-vue is pinned EXACTLY, no caret. A caret on a prerelease DOES float: `^2.1.0-vue3.7` resolves all the way up to vue3.13, and that is how the fleet drifted onto a proprietary dependency. vue3-apexcharts stopped being open source at 1.9.0 (dual-licence, free only under $2M revenue, and it forbids sublicensing under different terms — incompatible with shipping inside an EUPL-1.2 app). nc-vue's vue3.13 pins `~1.8.0`, the last MIT release. Verified from the LOCKFILE, not package.json: exactly two apexcharts entries exist in the tree, `vue3-apexcharts@1.8.0` (MIT) and `apexcharts@4.7.0` (MIT), with no nested copies. No licence override was added — the core `apexcharts` reports an identical `Custom: <url>` symptom at 5.x, so overriding by symptom would have buried this. Bootstrap — no lint rule covers any of this - createApp / createRouter / createWebHistory replace new Vue and VueRouter; PiniaVuePlugin dropped; Vue.mixin -> app.mixin. - vue-router 4 removed the bare `path: '*'`. It matches nothing and raises no error, so the shell renders with an empty <main>. Replaced with `/:pathMatch(.*)*`. - Mount host renamed `#content` -> `#docudesk-app`. Vue 2's `$mount()` REPLACED the matched element, so mounting on templates/index.php's duplicate `<div id="content">` quietly replaced core's own wrapper from layout.user.php and the duplication never showed. Vue 3 renders INSIDE the match, and with two `#content` elements it is undefined which one is matched. - registerBuiltinDashboardWidgets() is now called explicitly at bootstrap: nc-vue's `sideEffects` list lets webpack drop the bare imports that register the built-in widgets, which makes them render "Widget not available" with no error. - settings.js no longer reads `t`/`n` off window globals. Renamed props that no lint rule can see — 96 bindings across 28 files - `<NcButton type="primary">`: v9 repurposed `type` as the NATIVE button type and moved styling to `variant`. 49 buttons were rendering `<button type="primary">` — not a valid native type, so it behaves as submit — while silently falling back to default styling. `native-type="submit"` becomes `type="submit"`. - NcTextField / NcSelect / NcCheckboxRadioSwitch dropped `value` and `checked` in favour of `modelValue`. 18 `:checked`, 18 `@update:checked`, 5 NcSelect `:value` and 2 `@input` were dead. NcCheckboxRadioSwitch keeps a separate `value` prop for radio groups, so those were deliberately left alone. - ESLint's `.sync` -> `v-model:` autofix preserves the prop NAME, so it turned 45 `.sync` modifiers into `v-model:value` on components that no longer have a `value` prop — green lint, dead binding. Corrected to a bare `v-model`. Lint - eslint.config.js layers `conductionVue3Fixes` from nc-vue LAST. The `@nextcloud` v8 base activates ZERO `vue/no-deprecated-*` rules, so Vue-2 idioms survived a green lint; 21 are now active (verified by severity, not by counting listed rule names). - `.eslintrc.js` removed. A marker probe — add a unique rule, re-run `eslint --print-config` — showed the marker never reached the resolved config while a control rule from eslint.config.js did. It was a dead second rule set that could only drift. - Errors 4 -> 0, including the 4 that were already red on the base branch. PdfViewer's unscoped `<style>` is now scoped via `:deep()` anchored on the template-owned `.pdf-viewer__page`; pdfjs builds the text layer with document.createElement, so those nodes can never carry a scope hash. It had been leaking a bare `::selection` rule page-wide. webpack - @nextcloud/vue@9, @nextcloud/dialogs@7 and vue-router@5 ship an `exports` map with no `main` and no `module`, so a Vue-2-era alias to the package DIRECTORY resolves to nothing. Aliased to absolute files. - @nextcloud/vue@9 hard-depends on vue-router ^5.1.0 while the app is on 4, so npm installs a nested second copy. Two router instances mean two injection keys and navigation dies with no console error; a `vue-router$` alias forces the single copy. - path-browserify fallback for the bundled FilePicker chunk. - USE_LOCAL_LIB is opt-OUT and the shared sibling checkout sits on the Vue 2 line, so the default would silently compile Vue 2 library sources into this Vue 3 app. Added a major-version guard. - gridstack CSS imported: nc-vue lists gridstack as a peerDependency and deliberately does not bundle its stylesheet. Without it v12 sizes items from an undefined `--gs-column-width` and every dashboard item renders 0 px wide with nothing in the console. e2e harness — was pointed at the SHARED dev container - playwright.config.ts resolved `baseURL` as `NEXTCLOUD_URL || 'http://localhost:8080'` while global-setup.ts used `PLAYWRIGHT_BASE_URL ?? NEXTCLOUD_URL ?? NC_BASE_URL ?? 8080`. The two disagree, so the documented `PLAYWRIGHT_BASE_URL=... npx playwright test` logged in against the intended instance and then ran every spec against :8080 — the shared container, which bind-mounts other people's working trees. Both now resolve through one strict `tests/e2e/base-url.ts` with NO localhost fallback. It accepts `BASE_URL` as well, because that is the name the shared quality workflow exports. Tests - Vue 3 wraps anything written into reactive state in a Proxy, so `toBe(literal)` against a value the store holds no longer matches. Re-anchored on the object the store exposes — still strict identity, deliberately NOT relaxed to toEqual. Verified by injecting a copy-instead-of-reference bug into the getter: the assertions still go red. - jest 77/77 and vitest 45/45, identical to the base branch. * chore(docs): regenerate features.json The revived `quality / Features Check` job fails on the committed file: "docs/features.json is out of date — run scripts/extract-features.py to regenerate". The drift is entirely pre-existing, not a side effect of the Vue 3 work: running the shared extractor against unmodified `development` produces the same 164-insertion / 145-deletion diff, and the two regenerated files are byte-identical. It simply went unnoticed because the gate has produced zero jobs since 2026-06-01. * fix(deps): regenerate package-lock.json so `npm ci` accepts it under npm 10 The first Code Quality run on this PR — the first run this repo has produced any jobs for in two months — failed 11 jobs at "Install dependencies": npm error `npm ci` can only install packages when your package.json and package-lock.json are in sync. Invalid: lock file's @noble/hashes@1.4.0 does not satisfy @noble/hashes@2.2.0 Missing: @noble/hashes@1.4.0 from lock file `pkijs` depends on exactly `@noble/hashes@1.4.0`, while `@exodus/bytes` (pulled in by whatwg-encoding) declares `@noble/hashes ^1.8.0 || ^2.0.0` as an OPTIONAL peer. npm 10's `npm install` hoists pkijs's 1.4.0 to the root to satisfy that optional peer, then `npm ci` validates the same tree and rejects it. Install and ci disagree with each other. This is NOT a Vue 3 regression. Deleting package-lock.json on unmodified `development` and re-running `npm install` with the pinned npm 10 reproduces the identical error — the committed lockfile only survives because it is frozen from before `@exodus/bytes` entered the tree in this shape. Any regeneration hits it, and a Vue 3 migration has to regenerate. Fixed by generating the lockfile with npm 11 (which nests correctly and leaves the optional peer unmet, exactly like the pre-existing lockfile) and then normalising it with `npm install --package-lock-only` under npm 10, so the file is in npm-10 shape. Verified the only way that counts, with the major `engines` pins: `npm ci` under npm 10.8.2 installs 1781 packages cleanly. An npm-11-written lockfile on its own is NOT enough — npm 10 then fails it with EBADPLATFORM on @esbuild/netbsd-arm64. No `overrides` entry was added. A scoped `pkijs` override and a root `@noble/hashes` override were both tried and then reverted after a control run showed the regeneration sequence alone is sufficient; keeping them would have been inert config implying a constraint that is not doing any work. * style(php): fix the 4 phpcs errors the revived gate surfaced `PHP Quality (phpcs)` was the last red job once CI could resolve its reusable workflow again. All four errors are pre-existing PHP that has not been linted since 2026-06-01, and all four are in one file: - GrondslagProposalService.php:331,339 — Conduction's standard requires named parameters for calls to internal code: `self::asString($x)` -> `self::asString(value: $x)`. - :546 — two blank lines after a function where one is expected. - :549 — `extractObjects()` had a docblock with @param/@return but no short description. phpcs now reports 0 errors across 71 files. The 183 remaining warnings (mostly the repo's own `@spec` tag sniff) are non-fatal by design — phpcs.xml sets `ignore_warnings_on_exit=1` — and are left as pre-existing documentation debt rather than papered over. * chore(licenses): approve duck and pako — permissive strings the matcher cannot parse `License (npm)` denied two packages once the gate could run again: Disallowed license: duck (0.1.12) uses 'BSD*' Disallowed license: pako (1.0.11) uses '(MIT AND Zlib)' Neither is a real licence problem, and neither is new. Both sit at the identical version in unmodified `development` (duck via mammoth -> lop, pako via jszip and browserify-zlib) and were simply never surfaced, because this job has produced no output since 2026-06-01. `BSD*` is a legacy non-SPDX spelling and `(MIT AND Zlib)` is an SPDX compound expression. The allowlist compares whole strings, so it cannot match either even though BSD, MIT and Zlib are all on it. This is deliberately NOT the same move as papering over a `Custom: <url>` symptom: these two declare permissive licences unambiguously, they are just spelled in forms the matcher does not evaluate. While here, the pre-existing `apexcharts` override is annotated. It is keyed on the symptom rather than the licence: apexcharts 4.x is MIT but 5.x is proprietary and emits the same `Custom: <url>` string, so the override would silently approve 5.x. The `^4.7.0` range nc-vue pins cannot reach it (4.0.0-4.7.0 verified MIT), but the trap is now written down. * test(e2e): assert the real template columns instead of a Status column that never existed The Vue 3 build made this spec fail, and the reason is worth writing down. `templates table shows the expected column headers` asserted a "Status" column. src/manifest.json declares the Templates page's columns as `["name","category","format","namespace","description"]` — there has never been a Status column, and CnIndexPage has never rendered one. It passed anyway because the whole block sat behind if (await table.isVisible().catch(() => false)) { ... } On the Vue 2 build the table did not render on this page, the condition was false, and the test passed by asserting NOTHING. The Vue 3 build renders the table — an improvement — so the guard stopped short-circuiting and the stale expectation finally surfaced. The e2e delta on an identically seeded instance was baseline 10 failed / 59 passed versus 11 failed / 58 passed, and this was the single difference. The fix strengthens the test rather than relaxing it: - the guard becomes an assertion, so a missing table is a failure, not a silent skip (playbook: no `if (...)` guards that pass by asserting nothing); - all five manifest-declared headers are asserted, not two of them; - the absence of a Status column is asserted explicitly, so the manifest and this spec cannot drift apart in either direction. With this, the suite matches the base branch exactly: 10 failed / 59 passed, same 10 specs, all pre-existing and all traceable to the unseeded signingRequest / signerRecord schemas described in the PR body.
Psalm, phpstan and phpmd each ended '|| echo ... not installed, skipping...', so they always exited 0 and the CI jobs invoking them could never fail. Replaced with shillinq's 'if [ -f vendor/bin/<tool> ]' pattern, which still tolerates a genuinely absent tool but propagates a real failure. Measured before flipping (clean worktree at origin/development, PHP 8.3.32, after asserting the OCP stub resolves): psalm 1, phpstan 4, phpmd 11. test:all deliberately left unchanged - its cost is unmeasured.
…#357) Version alignment onto the current nc-vue vue3 release. Pinned exactly (no caret) so the resolved version is the reviewed one. Lockfile-verified: @conduction/nextcloud-vue -> 2.1.0-vue3.16 (single entry); vue3-apexcharts -> 1.8.0, below the 1.9.0 line that turned proprietary and forbids sublicensing in our EUPL-1.2 apps. No licence override is used: core apexcharts is MIT and reports an identical `Custom: <url>` symptom, so an override would mask the real signal. Opt-ins vue3.16 makes possible were checked and do not apply: no `@nextcloud/initial-state` overrides entry to drop, no local `vue/no-multiple-template-root: off` to delete. Gates: eslint clean, vitest 45/45, webpack production build clean. The lint gate was proved live with a throwaway `.vue` probe that fired vue/no-deprecated-slot-attribute, vue/no-deprecated-filter and vue/no-deprecated-v-on-native-modifier (exit 1), then exited 0 once removed. A `.js` probe would not exercise those rules at all.
test:all (and test:unit where present) ended in '|| echo Tests require Nextcloud environment, skipping...', so phpunit's exit status was discarded unconditionally and check:strict could never fail on a test failure. This repo's suite runs standalone, so the message was untrue here. Positive control, PHP 8.3.32 container, vendor/ freshly installed, adding one deliberately failing test under tests/: old composer.json + failing test -> test:all never named, swallowed new composer.json + failing test -> check:strict fails NAMING test:all new composer.json, test removed -> test:all passes Also passes --no-coverage: this repo's phpunit.xml requests coverage, and without a driver PHPUnit warns and exits non-zero, which would have made the newly-live gate red for an environment reason rather than a code one. shillinq and decidesk already do this. Tooling only - no findings fixed here.
Adds the four pieces the shared `ConductionNL/.github` quality workflow needs to run DocuDesk's 19-file Playwright suite on a fresh Nextcloud: - `.github/workflows/code-quality.yml`: enable-playwright, test path and seed command; move the openregister additional-app from `beta` to `development`. - `tests/e2e/playwright.config.ts`: CI-only config declaring ONLY the regression project, with report/output at the app root where the workflow's upload steps look. The workflow passes no `--project`, so the root config would also run `docs-capture` and `visual`. - `tests/e2e/ci-seed.sh`: explicit register import through OpenRegister's admin importer + the app-config object-type bindings DocuDesk resolves every write through, both verified, plus a bundle gate. - `tests/e2e/global-setup.ts`: a missing bundle is now a hard error on CI instead of a silent `npm run build` that would heal away the control.
…d log The importer answers 200 "Import successful" while having silently skipped individual schemas: ImportHandler catches a per-schema Exception, logs it and continues. Run 30796295073 imported 14 of DocuDesk's 20 schemas that way, and the only record of why lives in data/nextcloud.log.
OpenRegister's importer answers HTTP 200 "Import successful" while
skipping any schema that fails validation — ImportHandler catches the
per-schema exception, logs it, and continues. On a fresh install only
14 of DocuDesk's 20 schemas were written. Two causes:
1. `x-openregister-archival.retention` declared as a bare ISO-8601 STRING
on correspondence, signingRequest, signerRecord, signingAuditEntry and
batchCorrespondenceJob. ArchivalAnnotationValidator requires an object
`{ default: <duration>, rules?: [] }` and rejects the string with
"x-openregister-archival.retention is required and must be an object".
anonymizationLink and financialExtraction already shipped the object
form, which is why they imported and the other five did not.
2. publicationProhibition declared an authorization action `write`, which
is not in OpenRegister's CRUD vocabulary — Schema::validateAuthorizationRules()
accepts only create/read/update/delete. Expanded to create/update/delete,
preserving the documented intent (read by the consent group, write by
docudesk-policy-admins).
Impact: signing was entirely non-functional on a fresh install —
signingRequest, signerRecord and signingAuditEntry did not exist, so every
SigningService write targeted a schema that was not there. Found by the new
e2e seed gate, which verifies every slug the definition declares.
No property, slug or retention DURATION changed. Config version 7.6.2 ->
7.6.3 so the version-guarded boot import re-applies on existing installs.
37 of 66 specs failed on run 30797589151 with selector timeouts. The page
under test was not DocuDesk at all: it was PHP's own 404 —
heading "Not Found"
The requested resource /apps/docudesk was not found on this server.
The shared workflow serves Nextcloud with `php -S 0.0.0.0:8080` and no router
script, so nothing performs the .htaccess rewrite of /apps/* onto index.php.
The built-in server resolves the path against the document root, finds no
file, and answers its own error page.
That page is a near-perfect impostor for the checks these specs make: it has
a <body>, it is not /login, and it returns HTTP 404 which no spec inspects.
So the failures pointed at #header, at empty tables, and at
"CSRF request-token must be harvestable" — never at the URL. Worse, every
admin-settings spec PASSED against it, because those only assert a visible
<body> and a non-/login URL.
/index.php/apps/... is served correctly both with and without URL rewriting,
so it is the portable form — the same reasoning that already put index.php in
workflows/_fixtures.ts's API constant. The in-app router agrees: main.js uses
createWebHistory(generateUrl('/apps/docudesk')), and generateUrl emits the
index.php prefix whenever the front controller is inactive.
No assertion weakened: every toHaveURL(/\/apps\/docudesk…/) still matches.
… NOT NULL
`publicationConsent` listed `documentId` in the schema-level `required`
array, so OpenRegister created the backing column NOT NULL. A scope=entity
record — a standing consent — is document-less by definition, so:
- all 12 seeded standing consents failed to insert, with
'null value in column "document_id" of relation
"oc_openregister_table_15_18" violates not-null constraint'
(12 MagicMapper errors in run 30797589151's Nextcloud log, while the
import still answered HTTP 200);
- PolicyController::createStandingConsent hits the same constraint at
runtime, so the whole "Publish always" surface cannot persist anything.
The condition is per RECORD, not per schema, and the app's own canonical
spec already says so — openspec/specs/consent-management/spec.md, 'Requirement:
documentId MUST be required only for scope=document records'. It is enforced
where a per-record condition can be: ConsentScopeValidator::assertValid()
rejects a scope=document record WITHOUT a documentId and a scope=entity record
WITH one. Removing it from the schema's `required` array restores the
specified behaviour and leaves nothing unvalidated.
Config version 7.6.3 -> 7.6.4.
…paced code (#360) Adds ignore-namespaces=true to the DevelopmentCodeFragment rule, without which the rule is dead code: PDepend resolves an unqualified call inside a namespaced file to the current-namespace-qualified image (OCA\MyApp\Service\var_dump), which never matches the unwanted-functions list. All production PHP here is namespaced. Also drops the "if [ -f vendor/bin/phpmd ]" guard from the composer phpmd script, which made the check exit 0 whenever phpmd was absent. Config-only. Verified behaviour-neutral before merge: the phpmd job reaches the same conclusion on the merge-base and on this head, the phpmd finding sets are identical, and DevelopmentCodeFragment reports zero findings on both sides. The repo's pre-existing phpmd debt in other rules is left visible and untouched. See ConductionNL/openregister#2286.
…issions Point the wrapper at ConductionNL/.github (the org "Conduction" does not exist), cancel superseded runs per ref, and declare least-privilege permissions (CodeQL actions/missing-workflow-permissions).
…esk never shipped
src/manifest.json bound both type:index pages to register slug "docudesk":
Templates -> register "docudesk", schema "template"
SigningRequests -> register "docudesk", schema "signingRequest"
lib/Settings/docudesk_register.json has never declared a register with that
slug. At a406583 — the Phase 8 commit that introduced these two index pages
and wrote "docudesk" into them — the definition already declared
consent / document / dossier / signing / templates, and the whole history of
the file contains no "docudesk" register at all.
So both pages have listed objects from a register that holds none of them
since the day they were written, while every write went somewhere else:
TemplatesController resolves `template_register` from app config (the
`templates` register) and SigningService resolves `signingRequest_register`
(the `signing` register). Symptom on a clean instance: "No items found" on
both pages with three seeded templates present in the database, and
templates-crud's create -> appears-in-the-UI-list leg failing.
Also corrects the two walkthrough `advanceOn.object-created` triggers, which
carried the same wrong slug and could therefore never fire.
Fixes the read path to match both the shipped register definition and the
write path. Found by the e2e suite (run 30801457803).
Also in this commit:
- versions.spec.ts branches on version ROWS instead of the table element. The
table always renders its header row, so the populated branch was entered on
an instance where fileId=1 does not resolve and asserted a Download button
no row could offer; the empty branch now accepts either surfaced reason
(files_versions disabled, or "Document not found") but still requires one.
- ci-seed.sh probes WebDAV (PROPFIND/MKCOL/PUT/DELETE as admin) and prints the
codes. The workflow specs seed real files through DAV and MKCOL returned 404
with nothing in nextcloud.log; this makes the environment's DAV state a
printed fact rather than a fixture-shaped accusation. Informational only.
…260820093739 chore(release): 0.0.38-unstable.20260820093739
…260820095005 chore(release): 0.0.38-unstable.20260820095005
…260820100609 chore(release): 0.0.38-unstable.20260820100609
…260820102210 chore(release): 0.0.38-unstable.20260820102210
Two user-visible defects are fixed by this bump. TWO AI-COMPANION HEXES ON EVERY PAGE. The companion singleton landed in 2.7.0. Below that the host app's own companion never stands down, so any page of this app rendered a second hex 8px from hermiq's. THE DETAIL PAGE RECLOSED ITS SIDEBAR WHILE HYDRATING. CnDetailPage set sidebarSeeded and never read it, so 'open' was re-applied on every sync. Fixed in nextcloud-vue#711. Lockfile only — the existing caret already allowed this.
…260820103532 chore(release): 0.0.38-unstable.20260820103532
…260820105835 chore(release): 0.0.38-unstable.20260820105835
chore(deps): take @conduction/nextcloud-vue 2.8.2 (was 2.3.0)
…260820111321 chore(release): 0.0.38-unstable.20260820111321
…260820113535 chore(release): 0.0.38-unstable.20260820113535
…260820115321 chore(release): 0.0.38-unstable.20260820115321
…260820120316 chore(release): 0.0.38-unstable.20260820120316
…260820121250 chore(release): 0.0.38-unstable.20260820121250
…260820122212 chore(release): 0.0.38-unstable.20260820122212
…260820124138 chore(release): 0.0.38-unstable.20260820124138
…260820125103 chore(release): 0.0.38-unstable.20260820125103
Automated PR to sync development changes to beta for beta release.
Merging this PR will trigger the beta release workflow.