Release: merge development into beta - #22
Conversation
…ard-accessible-widget-repositioning)
…ots' (#131) from fix/appstore-metadata into development Reviewed-on: https://codeberg.org/Conduction/launchpad/pulls/131
…dd-dashboard-sharing-e2e-coverage)
…ompulsory widgets Adds an acknowledgement read-gate on compulsory dashboard widgets with per-user read receipts and an admin read-receipt report. Local-first (ADR-049 widgets-as-config), no OpenRegister install dependency. Includes Acknowledgement entity/mapper/migration, AcknowledgementService + Controller, AcknowledgementPrompt + report modal, activity integration, PHPUnit + vitest + e2e specs, EN/NL i18n.
…ad rename
The "LaunchPad" menu tile opened OpenBuild: the nav <route> was
mydash.page.index, which never resolved (NC registers the app under its
folder name "launchpad") and fell through to OpenBuild's dashboard
catch-all route. The frontend also called /apps/mydash/api/manifest (404).
- appinfo/info.xml: nav route mydash.page.index → launchpad.page.index;
<id> mydash → launchpad
- backend: route names (launchpad.kiosk.render, launchpad.publicShare.show),
log contexts, and the two straggler occ commands (launchpad:storage:*)
- frontend: i18n domain t('launchpad'), loadState, and /apps/launchpad URLs
across 36 files; rebrand two user-visible "MyDash" strings to "LaunchPad"
- add src/publicPath.js (generateFilePath) imported first in main.js/admin.js
so dynamically-imported chunks load from the served js dir — fixes
"Refused to execute script (MIME text/html)" on launchpad-cn-manifest-validator.js
- add PublicShareContext::isBearer() getter (test expected it)
- reconcile kiosk/demo-showcase unit test assertions to the launchpad app id
DB tables (oc_mydash_*), migrations, and cache prefixes are intentionally
left as mydash_ — they are the physical schema created by already-run
migrations.
…ed js dir
Dynamically-imported chunks (e.g. launchpad-cn-manifest-validator.js) were
requested from /apps/launchpad/js/ — a path Nextcloud answers with the
app-shell HTML — because webpack's default 'auto' publicPath does not match
where NC serves the app's JS (e.g. /custom_apps/launchpad/js/ in a dev
checkout). Result: "Refused to execute script (MIME type text/html)" and the
chunk never loads.
Add src/publicPath.js which sets __webpack_public_path__ via
generateFilePath('launchpad', 'js', ''), resolved against NC's per-app web
root (OC.appswebroots) so it is correct for both apps/ and custom_apps/
layouts. Imported first in main.js and admin.js so it runs before any lazy
import(). Verified live: validator chunk → 200 text/javascript, 0 console
errors.
# Conflicts: # appinfo/info.xml # docs/migration/widget-library-to-ncvue.md # lib/Service/DashboardService.php # lib/Service/PublicShareContext.php # src/components/Dashboard/__tests__/IconPicker.spec.js # src/components/Widgets/Renderers/CalendarWidget.vue # src/components/Widgets/Renderers/FilesWidget.vue # src/components/Widgets/Renderers/LinkButtonWidget.vue # src/components/Widgets/Renderers/NewsWidget.vue # src/components/Widgets/Renderers/PeopleWidget.vue # src/components/Widgets/Renderers/__tests__/FilesWidget.spec.js # src/components/Widgets/VisibilityRulesModal.vue # src/components/Widgets/WidgetContextMenu.vue # src/components/admin/AdminDemoData.vue # src/components/admin/AdminSettings.vue # src/components/admin/RoleLayoutDefaultsSection.vue # src/components/admin/tabs/TemplatesPage.vue # src/dialogs/PublicSharePasswordDialog.vue # src/stores/__tests__/kioskPlaylists.spec.js # src/stores/kioskPlaylists.js # src/stores/publicShares.js # src/views/DashboardPublicShareView.vue # src/views/Views.vue
…RLGenerator' (#124) from fix/launchpad-default-tile-logo-path into development
# Conflicts: # package-lock.json # package.json
…0.0-beta.134' (#116) from chore/bump-ncvue-beta134 into development
# Conflicts: # lib/Db/WidgetPlacement.php
# Conflicts: # src/publicPath.js
…de fix + consolidation' (#126) from ci/canonical-test-workflow into development
…s load (MIME text/html fix)' (#132) from fix/launchpad-lazy-chunk-publicpath into development
…_ICONS The tile picker + widget style editor resolved NL Design icons as /…/nldesign/img/icons/*.svg URLs, which 404 (34 broken images + console spam) whenever the nldesign app is disabled. Consume the self-contained data-URI pack from @conduction/nextcloud-vue instead (guarded with ?? [] for older lib versions), so the icons work with no nldesign app installed.
…ets render
beta.155's dist exports CnNcWidgetWidget but omits its registerDashboardWidget
('nc-widget',…) self-registration, so nc-widget placements fell to the broken
legacy callback path and rendered as blank cards. Register the renderer app-side
(idempotent guard) so proxied Nextcloud dashboard widgets render again.
Add Version002005 to rename the pre-rename long-named role tables (launchpad_role_feature_perms / _layout_defaults) to the abbreviated names the mappers query (launchpad_role_feat_perms / _layout_def) — Version001007 was already recorded so its rename never reached instances carrying the old tables, 500'ing every Roles & Permissions admin call. No-op on fresh installs; preserves rows. Also remove the orphaned IconPicker.spec.js (broke the vitest suite) and dead LinkButtonWidget.vue (broken IconRenderer import; superseded by LinkButtonHost).
…ken} page
Complete the public-share feature that shipped backend-only:
- Sharing tab (DashboardConfigModal): create, list, copy and revoke anonymous
read-only links, with optional password + expiry.
- /s/{token} now serves an HTML page (page#publicShare, renderAs public) that
boots a standalone SPA (src/public.js → DashboardPublicShareView) and renders
the shared dashboard read-only — tiles + static custom widgets; native NC
widgets show a 'signed-in only' placeholder (they can't render anonymously).
The JSON render API moves to /s/{token}/data; placements are added to the
render payload so the view can lay out the grid.
- 02: '+ Add dashboard' forks the current dashboard (no create modal).
- 05/06: widget menu is Edit/Delete; style folded into the Edit modal.
- 11: public-share now has a create UI + rendered /s/{token} page.
…les 500 fix, public-share UI' (#133) from feat/icons-nc-widget-roles-public-share into development
Replace the launchpad-local nldesign-app icon resolution with the CC0/EUPL icon sets now bundled in @conduction/nextcloud-vue (RVO / Gemeente / Den Haag). - nlDesignIcons.js imports NL_DESIGN_ICON_GROUPS from the library and exposes nlDesignIconGroups() (grouped), plus the flat nlDesignTileIcons() / nlDesignStyleIcons() for existing call-sites. - TileEditor passes :url-icon-groups to CnIconBrowser, so the Custom tab shows a searchable sub-tab per set instead of one flat grid. Drops the runtime dependency on the (proprietary Amsterdam) nldesign app; the icons are self-contained data: URIs, so they work whether or not it is installed. Requires @conduction/nextcloud-vue with the src/icons catalogues.
…L icon catalogues
…catalogues (drop nldesign dep)' (#134) from feat/consume-ncvue-nl-icons into development
The source moved to NL_DESIGN_ICON_GROUPS (three CC0/EUPL NL-government sets; nc-vue beta.166) but the vitest stub + util spec still referenced the old flat NL_DESIGN_ICONS, leaving the suite red. Export NL_DESIGN_ICON_GROUPS from the stub and test nlDesignIconGroups()/nlDesignTileIcons()/nlDesignStyleIcons() against the grouped shape.
…ed catalogues' (#135) from feat/nl-gov-icon-catalogues into development
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 536/536 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-15 20:06 UTC
Download the full PDF report from the workflow artifacts.
…#120) * perf(workspace): only touch the widget registry when the bridge is on IManager::getWidgets() is not a getter. It calls loadLazyPanels(), which calls load() on EVERY dashboard widget of every app enabled for the user, ignoring the dashboard layout — and load() is where widgets call Util::addScript(). Merely enumerating widgets therefore injects every widget bundle in the instance. index() reached the registry twice per render: once through loadWidgetScripts() and once through getAvailableWidgets() for the initial state. The explicit `foreach ($widgets as $widget) { $widget->load(); }` loop in loadWidgetScripts() was dead weight — getWidgets() had already called load() on all of them before the loop ran — so removing only that loop would have changed nothing. Measured on the workspace page, bridge on vs off: bridge on 161.47 MB JS, 72 files, 24 widget bundles, 36,776 ms bridge off 42.90 MB JS, 21 files, 0 widget bundles, 6,184 ms That is 118.6 MB and ~30s of widget code the workspace never renders, every one of which used to throw because OCA.Dashboard exists only on /apps/dashboard. The registry is now read only when legacyWidgetBridgeEnabled is set. The SPA is unaffected when it is off: Views.vue already fetches the available widget list from GET /api/widgets on boot, and loadInitialState defaults `widgets` to [], so the REQ-INIT-002 reader still never returns undefined. IManager was the only user of the dashboardManager constructor argument, so both the argument and the import go with it. With the bridge on the cost is unchanged — that path is bounded by Nextcloud core, which offers no way to read widget metadata without loadLazyPanels() injecting every script. Fixing that needs an upstream change to OC\Dashboard\Manager. * perf(workspace): default the legacy widget bridge to off Enabling the bridge makes the workspace read the Nextcloud widget registry, and IManager::getWidgets() injects the scripts of every widget of every enabled app. Measured on the workspace page that is 118.6 MB of JS and ~30s of load for widgets the workspace never renders, so the expensive path should be opt-in rather than the default. With no stored setting the workspace now renders in 4,988 ms with 42.90 MB of JS, 21 files and zero widget bundles, against 36,776 ms / 161.47 MB / 24 bundles when the bridge is on. Instances that rely on bridged placements switch it back on in Beheer; the flag is read on every render, so it takes effect immediately. The stored-value test asserted false, which is the new default and would therefore have passed without reading storage at all. It now stores true — the opposite of the default — so it still discriminates. * refactor(workspace): extract resolveBridgedWidgets() to satisfy phpmd CI caught what the local run did not: phpmd ExcessiveMethodLength, because index() went from 88 lines on development to 108 with this PR's guard and its explanatory comment. The threshold is 100. The comment was carrying most of the weight, and it documents the bridge decision rather than the render flow, so it belongs with the logic it explains. Extracting resolveBridgedWidgets() takes both out of index(), which is now 90 lines. No behaviour change: same setting, same default (off), same call to getAvailableWidgets() only when the bridge is on. Verified: phpmd reports no PageController violation, phpcs unchanged at 0 errors for this file, phpstan [OK] No errors, phpunit 1570 tests 0 failures. Worth recording why this was missed — the local sweep ran lint, phpcs, phpstan, psalm and phpunit, but not phpmd, so "gate green" was a claim about a smaller set of checks than CI runs. * test(workspace): cover the legacy-widget-bridge decision CI's coverage guard failed the previous commit — not a failing test, the ratchet: Coverage current: 51.68% (11645/22533 statements) Coverage merge base: 51.68% (11645/22532 statements) FAIL: This change adds 1 statements. Adding code without tests drops coverage. Extracting resolveBridgedWidgets() added one statement and covered none of it, because PageController had no test at all. The percentage did not move — only the counts — which is exactly the shape that reads as flake. The decision deserved a test regardless. The assertion that matters is `expects($this->never())` on getAvailableWidgets: the point of this change is not that an empty array comes back, it is that the widget registry is never touched, so no widget's load() runs and no scripts are injected. Asserting on the return value alone would still pass if the registry were read and discarded. Three cases: bridge on reads the registry, bridge off never touches it, and a missing setting defaults to off (the fail-safe direction). Verified load-bearing by mutation rather than by passing: flipping the default from `?? false` to `?? true` fails the third test with "getAvailableWidgets(): array was not expected to be called"; restoring it returns to green. phpunit 1573 tests 0 failures (3 added), phpmd 0 PageController violations, phpcs 0 errors on its configured scope (lib/ — tests/ is deliberately out of scope, matching every existing test file's positional-argument style). --------- Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 536/536 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-15 23:02 UTC
Download the full PDF report from the workflow artifacts.
2.3.0 is released and the `vue3` and `latest` dist-tags have CONVERGED on it — the separate 2.2.0-vue3.N line is over. The exact 2.2.0-vue3.16 pin left this app five releases behind and made a caret range impossible to express, because the -vue3.N prereleases do not order the way callers expect. A caret also unblocks local library development: the sibling-checkout guard validates ../nextcloud-vue against this app's declared range, so an exact pin refused every sibling that was not byte-identical, newer ones included. Verified rather than assumed: npm install resolves 2.3.0, npm ci reproduces it (run explicitly, because npm install does not re-run postinstall hooks for an already-present version — that is how vue-demi ends up on the wrong shim), the production build compiles clean with 0 errors, and the app renders in the browser with cache disabled. All 17 apps in this sweep render. Note for reviewers: 2.3.0 declares the same peerDependencies as 2.2.0-vue3.16 — dexie, marked, dompurify, gridstack, @vueuse/core, @nextcloud/capabilities and friends. Those app-level declarations are REQUIRED by the library, not redundant, and must not be pruned as "unused". Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 525/525 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-16 00:08 UTC
Download the full PDF report from the workflow artifacts.
* chore(security): enable the npm supply-chain cooldown on npm 11 Sets `min-release-age=2` and `min-release-age-exclude[]=@conduction/*`, raises `engines.npm` to ^11.0.0, and regenerates the lockfile under npm 11. The .npmrc comment here has described a cooldown for months and it has never been in effect. `min-release-age` does not exist in npm 10 — `npm config get min-release-age` answers `undefined` — and every Node 22 release bundles npm 10, so the setting was read by nothing. Most repos also had it at 0, which disables it outright. @conduction/* is exempt because without the exemption the cooldown does not fail loudly, it silently resolves backwards: measured 2026-08-15, an install of @conduction/nextcloud-vue on release day picked 2.0.7 instead of 2.3.0 and exited 0. The lock is regenerated under npm 11 and iterated to a fixed point. Where the tree changed rather than its metadata, that is npm 10 -> 11 reconciling a lock shaped by the older resolver, not the cooldown — verified by regenerating with the cooldown enabled and disabled and getting identical trees. Verified: npm ci exit 0 under npm 11.19.0, @conduction/nextcloud-vue resolves to 2.3.0, gate-84 conformance passes. * ci: re-run against the merged shared workflow `gh run rerun` replays the workflow version resolved when the run was created, so a reusable workflow referenced as @main is NOT re-resolved — every re-run after ConductionNL/.github#469 merged still executed Node 22 with npm 10.9.8, where `min-release-age` does not exist and `npm ci` cannot read an npm-11 lockfile. Only a new run picks up the merged workflow. This empty commit is that trigger.
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 525/525 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-16 01:35 UTC
Download the full PDF report from the workflow artifacts.
…er a file-wide no-undef (#125) * chore(security): enable the npm supply-chain cooldown on npm 11 Sets `min-release-age=2` and `min-release-age-exclude[]=@conduction/*`, raises `engines.npm` to ^11.0.0, and regenerates the lockfile under npm 11. The .npmrc comment here has described a cooldown for months and it has never been in effect. `min-release-age` does not exist in npm 10 — `npm config get min-release-age` answers `undefined` — and every Node 22 release bundles npm 10, so the setting was read by nothing. Most repos also had it at 0, which disables it outright. @conduction/* is exempt because without the exemption the cooldown does not fail loudly, it silently resolves backwards: measured 2026-08-15, an install of @conduction/nextcloud-vue on release day picked 2.0.7 instead of 2.3.0 and exited 0. The lock is regenerated under npm 11 and iterated to a fixed point. Where the tree changed rather than its metadata, that is npm 10 -> 11 reconciling a lock shaped by the older resolver, not the cooldown — verified by regenerating with the cooldown enabled and disabled and getting identical trees. Verified: npm ci exit 0 under npm 11.19.0, @conduction/nextcloud-vue resolves to 2.3.0, gate-84 conformance passes. * ci: re-run against the merged shared workflow `gh run rerun` replays the workflow version resolved when the run was created, so a reusable workflow referenced as @main is NOT re-resolved — every re-run after ConductionNL/.github#469 merged still executed Node 22 with npm 10.9.8, where `min-release-age` does not exist and `npm ci` cannot read an npm-11 lockfile. Only a new run picks up the merged workflow. This empty commit is that trigger. * fix(lint): clear the last tranche-A suppression — require.context under a file-wide no-undef `require.context()` is a WEBPACK build-time API the bundler rewrites at compile time, so eslint is right that no runtime `require` exists and the code is right too. The file-wide `no-undef` suppression that recorded this also switched the rule off for every OTHER identifier in the file, so a genuine typo there would have been silent. Scoped to `/* global require */`. Same shape found in 9 apps across the fleet; all cleared the same way. Verified: eslint 0 errors, build exit 0, tests pass.
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 525/525 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-16 05:54 UTC
Download the full PDF report from the workflow artifacts.
v1.7.3's runner stops at gate-64, so gate-66 openregister-dependency-shape -- which this repo's Code Quality run reports on -- could not be executed from the vendored copy. v1.8.0 also ships OCA\OpenRegister\Contract\ (the ADR-084 interfaces), which v1.7.3 does not carry at all. Lock-only: the constraint is already ^1.0 and no other package moved.
chore(deps): raise hydra-gates to v1.8.0 so gates 65-84 exist locally
Every tag points at a requirement that exists; verified with the gate's own check_spec_anchors.py, which reports 0 dangling anchors across all 11 files. check_spec_coverage.py goes 18 -> 0. Fifteen got a real anchor (metadata-fields REQ-MDFL-004, deeplinking REQ-DDL-003, reactions REQ-RXN-003, calendar REQ-CAL-003, news REQ-NEWS-003, link-button REQ-LBN-003/004, footer REQ-FTR-006, live tile REQ-LIVETILE-002, tiles REQ-TILE-002, dashboards REQ-DASH-015). Three carry '@SPEC exclude' with a stated reason rather than a tag, because there is no requirement they implement: two build-time manifest helpers for ADR-037 (a hydra-wide ADR with no spec file in this repo -- only adr-001 and adr-023 live here, so an anchor would dangle), and BeheerTabs' in-component tab routing. Worth recording while adjacent: gate-16's checker is honest, but the anchor checker takes a LOG PATH as argv[1], not an app dir. Passing a directory makes it die with IsADirectoryError and exit 1, and a caller that only greps stdout reads that crash as 'no dangling anchors'. I ran a positive control -- a deliberately bogus anchor -- to prove it can fail before trusting a clean run.
docs(spec): close gate-16 — tag the 18 untagged methods
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 525/525 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-16 11:55 UTC
Download the full PDF report from the workflow artifacts.
gate-7 reports both of these as NoAdminRequired with no recognised guard. An
IDOR needs an attacker-supplied identifier, and neither method accepts one:
VisibilityPreviewController::preview takes a candidate rule set and an
evaluation context from the request body, loads nothing by id, persists
nothing, and evaluates against $this->userId.
DashboardShareApiController::searchSharees takes a search string, reads no
dashboard and no share, and returns the same directory the core share
picker already exposes to any authenticated user.
Both null-check $this->userId. searchSharees additionally excludes the caller
from its own results, blocks single-character sweeps as a directory
enumeration guard, and bounds both searches to 10 rows.
Using the exemption the gate provides rather than widening its guard patterns:
its own comments record that widening 'would let gate-7 clear real IDORs', and
that is a worse trade than two documented waivers. The reason is required and
stated.
Verified the waiver is load-bearing, not decorative: with it, gate-7 reports
zero on this repo; displace the docblock and the finding comes straight back.
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 525/525 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-16 12:20 UTC
Download the full PDF report from the workflow artifacts.
gate-26 — TemplatesPage.vue was reported as having no visual proof while tests/e2e/admin-templates-page.spec.ts tests exactly that page. The gate matches a page against its component stem, the spec only ever wrote 'TemplatesPage' in comments, and the corpus strips comments (a comment is not a baseline, .github#358). The selector is now a named constant, so the link between spec and component is executable rather than prose. Verified load-bearing: rename the constant and the finding returns. gate-57 — DashboardVersionService::deleteVersionsForDashboard() had no production caller anywhere. VersionsListener IS registered on DashboardDeletedEvent and does the same single mapper call directly, so the cascade has always worked and the service method was dead. Removed. The interesting part is what the coverage was doing: the dead wrapper had a unit test, and VersionsListener — the live path — had none. So the tested path was the one nothing called. VersionsListener now has its own test, including that an unrelated event deletes nothing (without which the listener could fire on any event and the happy-path test would still pass) and that a mapper failure is logged rather than rethrown, which REQ-CSC-006 requires because a throw would abort the sibling listeners on the same event. gate-35 — the inert news card's thumbnail now says aria-hidden="true" alongside alt="". The headline beside it carries the meaning and the card is inert, so there is no link for the image to name; the attribute states that explicitly instead of leaving alt="" to imply it. gate-37 (aria-hidden-focusable) re-checked and still clear — an img is not focusable.
docs(security): waive gate-7 on the two endpoints that address no object
…126) Adopts the canonical script from ConductionNL/.github (quality-config/coverage-guard.php). The whole-project comparison fires on measurement noise. doriath#240 was a PR whose entire diff was `webpack.config.js` — no PHP at all — and the guard failed it: identical denominator (13723), both runs reporting exactly `Tests: 948, Assertions: 3051, Skipped: 1`, and six covered statements of run-to-run xdebug variance between them. The measured `--against` floor cancels driver variance (xdebug vs pcov), as its header says. It does not cancel run-to-run variance within one driver, and the ratchet has no tolerance. Scoping the comparison to the PHP a change actually touches keeps full strength where a regression matters and makes the noise unreachable by construction — a diff with no PHP cannot fail. New `changed-files` capability; the shared workflow PROBES for it rather than assuming, so an un-updated copy keeps the previous behaviour instead of silently accepting and ignoring the flag. Script only — no behaviour change until the workflow passes `--changed-files`. Byte-identical to the canonical copy (md5 5be122aad209da030c79b22a133232fb). Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 525/525 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ❌ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-16 13:14 UTC
Download the full PDF report from the workflow artifacts.
The suite runs with strict coverage, so constructing the event the listener handles counted as executing code the test never declared — reported as RISKY, which this config treats as a failure. 1575 tests passed; nothing was actually broken. @uses declares the event.
test: declare DashboardDeletedEvent as used by VersionsListenerTest
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 525/525 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ❌ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-16 13:30 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 525/525 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-16 13:51 UTC
Download the full PDF report from the workflow artifacts.
gate-57 orphaned-write-capability reported 1 finding over 82 lib/Service files: DashboardVersionService::deleteVersionsForDashboard(). It was a literal pass-through to DashboardVersionMapper::deleteByDashboardUuid() with zero callers. The live cascade path is Listener\VersionsListener, which is registered against DashboardDeletedEvent in Application.php per dashboard-cascade-events REQ-CSC-002/REQ-CSC-003 and calls the mapper directly, inside the log-and-continue envelope REQ-CSC-006 requires. Routing the listener through a second wrapper would add a hop and nothing else, so the wrapper is removed rather than wired. The dashboard-versioning spec claimed the wrapper was 'wired and ready for the sibling cascade-events listener; the dashboard delete path itself does not yet invoke them'. That was stale — the listener has shipped. The spec now records the real path and states that there is deliberately no service-layer wrapper. Before: 1 finding over 82 files. After: 0 findings over 82 files. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 525/525 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-16 20:15 UTC
Download the full PDF report from the workflow artifacts.
The shared quality gate maps `phpcs` exit 1 to success, so these have been shipping silently. Raw `composer phpcs` exit goes 1 -> 0. Both are PEAR.Commenting.FunctionComment.WrongStyle, and both are the same shape: a real `/** */` docblock, then a `//` block explaining the `#[AnonRateLimit]` choice, then the attribute, then the function. Because the `//` block is what immediately precedes the function, PHPCS reads THAT as the function comment and rejects its style. Fixed by folding the rate-limit rationale into the docblock it belongs to and keeping the attributes contiguous. No prose is deleted -- every sentence is preserved, as a "Rate-limit rationale:" paragraph. Side effect, deliberate and verified: this also removes 2 CustomSniffs.Commenting.SpecTag.MissingMethodSpec warnings (470 -> 468), and drops the same two methods off `composer lint:spec-annotations`. Those reports were false. Both methods already carried an `@spec` tag; the stray `//` block sat between the docblock and the function and hid it. `grep -c '@SPEC'` is identical before and after in both files -- no tag was added, and none was suppressed. A warning set-diff confirms 0 warnings added. Documentation only -- no executable statement is touched. Verified against a second pristine clone of development: lint, lint:initial-state, lint:licenses, phpmd, psalm, phpstan and phpunit are unchanged (1575 tests, identical failing-test-name set). lint:spec-annotations stays red on its pre-existing Db/Migration backlog, which this branch does not touch.
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 525/525 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-17 04:33 UTC
Download the full PDF report from the workflow artifacts.
Composer had no package-ecosystem entry at all, so composer dependencies got no release-age cooldown whatsoever, unlike npm which has had one for a while. Adds cooldown.default-days: 2 with a conduction/* exclude, matching the fleet-wide floor gate-93 (composer-cooldown-config) enforces. See ConductionNL/hydra openspec/changes/composer-dependency-cooldown and ADR-093 (proposed, ConductionNL/hydra#591). Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 525/525 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-17 11:05 UTC
Download the full PDF report from the workflow artifacts.
Automated PR to sync development changes to beta for beta release.
Merging this PR will trigger the beta release workflow.