Skip to content

Release: merge development into beta - #22

Open
github-actions[bot] wants to merge 314 commits into
betafrom
development
Open

Release: merge development into beta#22
github-actions[bot] wants to merge 314 commits into
betafrom
development

Conversation

@github-actions

@github-actions github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Automated PR to sync development changes to beta for beta release.

Merging this PR will trigger the beta release workflow.

Reminder: Add a major, minor, or patch label to this PR to control the version bump. Default is patch.

juanclaude-conduction and others added 30 commits July 7, 2026 14:05
…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
…lay + job @SPEC fixes' (#121) from feat/widget-library-consumption 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
…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.
…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
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/launchpad @ 220ba5a

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>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/launchpad @ ffc89aa

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>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/launchpad @ 77d08da

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.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/launchpad @ d24a89d

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.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/launchpad @ 7910ac9

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
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/launchpad @ 00d0994

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.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/launchpad @ d1e0634

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.

rubenvdlinde and others added 3 commits August 16, 2026 14:59
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>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/launchpad @ 6e5730c

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
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/launchpad @ 0fd2186

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.

@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/launchpad @ 32828a3

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>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/launchpad @ e8c4968

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.
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/launchpad @ 7aeb926

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>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/launchpad @ 1924880

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants