Release: merge development into beta - #351
Conversation
#349) The PHPUnit cell that runs the coverage ratchet fails with "scripts/coverage-guard.php predates merge-base comparison (it does not report the 'against' capability)". That message names a plausible cause rather than the measured one, but the underlying fact is simple: this copy is the 17,288-byte version and the workflow is written against the 35,733-byte canonical one, which additionally reports `deletion-neutral`. Without that capability, deleting well-tested dead code still reads as a coverage drop. Measured across the 21-app fleet: 8 apps already carry the canonical copy and their ratchets run and report normally; 11 carry a stale variant. This is vendored-copy drift, the same shape as the six drifted app-manifest schemas. Copied verbatim from ConductionNL/.github at quality-config/coverage-guard.php rather than patched locally. Reimplementing a gate has cost us before: a hand-rolled version found 1 finding where the real gate found 13.
…353) This app declares schemas and shipped no demo data, so it opened on an empty list: the person evaluating it had to author objects by hand against a schema they did not know yet. Fleet-wide, 562 of 598 schemas were in that state. 🔴 GENERATED, NOT WRITTEN. Every value is derived from the schema that will validate it — `enum` picks from the enum, `pattern` is satisfied, `format` drives the shape, `minimum`/`maxLength` are honoured, `required` is always populated. Hand-written demo data is wrong in a way nobody sees until the demo (a status outside its own enum, a required field omitted) and it fails at import, in front of whoever asked for the demo. Produced and validated by the single copy that gate-99 also runs: `vendor/conduction/hydra-gates/scripts/lib/generate_mock_register.py`. Regenerate with that file; `--keep` preserves curated objects and tops up only what is short. 🔴 IT DOES NOT INSTALL ITSELF (ADR-111 rule 3). `x-openregister.type: mock` is imported ON DEMAND — sample data appearing on a production instance because somebody upgraded is a data-integrity incident, not a convenience. Install it with: occ openregister:descriptors:list --app=<app> --import=<register> The setup-wizard step that offers this during first-run install (ADR-111 rule 4) follows once OpenRegister's shared installer lands — deliberately not twenty-one copies of the same logic. Verified: `--check` re-validates every object against its own schema with jsonschema and reports clean. 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 | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 538/538 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-27 15:45 UTC
Download the full PDF report from the workflow artifacts.
…eet (#354) `e2e-skip-blocking` exists in the shared quality workflow and is enabled in ZERO of the 21 fleet apps. It has never been on anywhere. Its own description says to flip it per app once that app is worked down, so that it "can then only stay clean". launchpad is the app to start with: its last E2E run on development was 137 passed, 0 skipped. Why it matters, measured today: - The CI log prints skip COUNTS with no reasons. A runtime `test.skip(cond, reason)` is visible ONLY in the Playwright report, which is exactly what this check reads. So today a broken fixture and a legitimate skip are indistinguishable after the fact. - 178 skipped specs across five apps: buildiq 62, decidiq 46, dossiq 37, shillinq 21, integriq 12. - decidiq's integration-registry.spec.ts skips 21 tests on `test.skip(!first, 'no meeting objects on this instance')`. The CI seeder deliberately provisions registers and schemas only, so that condition means "no earlier spec happened to create a meeting" -- a seeding-order problem, reported as 21 passes. This is opened as a PR rather than pushed, so the gate runs against launchpad here and reports its verdict before it can redden development. 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 | ⏭️ | ||||
| composer | ⏭️ | ⏭️ | |||
| npm | ⏭️ | ⏭️ | |||
| app:check-code | ⏭️ | ||||
| info.xml | ⏭️ | ||||
| REUSE | ⏭️ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-27 17:03 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 | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 538/538 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-27 20:32 UTC
Download the full PDF report from the workflow artifacts.
) launchpad is the only app in the fleet with e2e-skip-blocking: true, and until now that flag did nothing: the E2E job printed ::warning::check_e2e_skips.py not found (hydra-gates not vendored here yet) - skip discipline NOT measured on this run and went green. The app looked like the first in the fleet enforcing skip discipline while measuring nothing. Two causes, both now fixed upstream: 1. check_e2e_skips.py existed only on .github main and was in NONE of the last 12 release tags. Cut as v1.10.0. 2. The workflow looked for it WITHOUT the inner hydra-gates/ path segment that composer actually installs it under. Fixed in ConductionNL/.github#595, merged. #595 makes a missing gate a hard FAILURE for an opted-in app rather than a warning, so without this bump launchpad's next run would go red -- the correct behaviour, and this is the fix it is asking for. Verified locally: composer update resolves v1.10.0 and the file lands at exactly the path the workflow probes: vendor/conduction/hydra-gates/hydra-gates/scripts/lib/check_e2e_skips.py 14546 bytes launchpad's last E2E run was 137 passed, 0 skipped, so the gate is expected to pass on real data -- which is the whole point of turning it on here first. 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 | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 538/538 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-27 22:29 UTC
Download the full PDF report from the workflow artifacts.
…user (#364) * fix(l10n): the eight manifest strings that render English to a Dutch user gate-99 manifest-l10n-coverage fails on development: eight strings the manifest renders have no key in l10n/nl.json, so a Dutch user sees the English source. Nothing else reports it -- check:l10n-js compares nl.json to nl.js, and a string absent from BOTH is in sync. Flow and Flows are kept untranslated, which is what opencatalogi, dossiq, openregister and integriq all already do. Both files are updated together: adding to nl.json alone would trade this gate for check:l10n-js. * fix(l10n): the eight manifest strings that render English to a Dutch user gate-99 manifest-l10n-coverage fails on development: eight strings the manifest renders have no key in l10n/nl.json, so a Dutch user sees the English source. Nothing else reports it -- check:l10n-js compares nl.json to nl.js, and a string absent from BOTH is in sync. Flow and Flows are kept untranslated, which is what opencatalogi, dossiq, openregister and integriq all already do. Both files are updated together: adding to nl.json alone would trade this gate for check:l10n-js.
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 538/538 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-28 07:38 UTC
Download the full PDF report from the workflow artifacts.
…#366) #354 turned this on. #356 removed it 16 minutes later while rewriting this file's triggers -- not a revert, just two edits landing together and one line not surviving. Worth restoring now for a better reason than the first time. When #354 merged, the gate was a NO-OP: it could not find check_e2e_skips.py and warned instead of failing, so launchpad looked like it was enforcing skip discipline while measuring nothing. Three fixes later it genuinely runs: - hydra-gates v1.10.0 cut, containing the script (#359 vendored it) - ConductionNL/.github#595 fixed the lookup path, which had omitted the inner hydra-gates/ segment composer installs under, and made a missing gate a hard failure instead of a warning On development's current head the gate EXECUTED and reported: 0/137 tests skipped (0.0%) across 30 spec files V1 spec files executing ZERO tests : 0 V2 skips deferring to a deploy state CI decides: 0 V3 skips/fixmes with no reason recorded : 0 Every skip names a real absence and every spec file ran something. That answers the open question from #354: tests/e2e/docs-screenshots.spec.ts does NOT trip the zero-test rule. So this is measured, not hoped for. launchpad passes on real data, and the flag can only keep it that way -- which is exactly what the input's own description asks for: flip it per app once that app is worked down. 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 | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 538/538 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-28 08:42 UTC
Download the full PDF report from the workflow artifacts.
.github#597 set cancel-in-progress on the shared quality.yml, but a
caller-level concurrency cancels the whole run before the called
workflows setting can apply -- so that fix reached only the apps that
declare no concurrency of their own.
Measured 2026-08-28 over push runs on development since #597 merged:
caller silent 0 of 11 cancelled
caller says true 7 of 13 cancelled (54%)
This repo is in the second group. pull_request keeps cancelling, where
superseding really is correct.
* refactor(manifest): the flow pages are an index and a flow `flows` and `flow-detail` are deprecated aliases. `flows` predates named index sources: a flow lives in OpenRegister's native flow table rather than a register/schema pair, so an object-backed index had nothing to bind to and the list needed a page type of its own. `config.entitySource` closes that, so the list is an ordinary `index` and only the EDITOR still needs its own type, now named `flow`. Behaviour is unchanged - both aliases resolve to the same components. The list page's `_note` is updated in the same commit: it asserted the old rationale (that an index "cannot address" a flow), which this change disproves. `config.app` is untouched and is load-bearing: the editor stamps it on a flow created there and the index filters on it. Requires @conduction/nextcloud-vue 2.21, where a named source's columns and create button are actually read. * chore(deps): bump @conduction/nextcloud-vue to ^2.21.0 The flow pages need 2.21.0: earlier releases DECLARE a named index source's columns, create button and row actions without reading them, so the migrated page renders a columnless table with no working create action. The lock is the part that matters. CI installs with `npm ci`, which honours package-lock.json and ignores how permissive the caret is — bumping the range alone would change nothing about what actually installs. --------- 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 | ⏭️ | ||||
| composer | ⏭️ | ⏭️ | |||
| npm | ⏭️ | ⏭️ | |||
| app:check-code | ⏭️ | ||||
| info.xml | ⏭️ | ||||
| REUSE | ⏭️ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-28 09:06 UTC
Download the full PDF report from the workflow artifacts.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…e shell shows (#361) * fix(dashboards): ship a dashboard, and let the API resolve the one the shell shows A fresh install of launchpad had no dashboard at all. `/apps/launchpad/` rendered "No dashboards available, contact your administrator", `GET /api/dashboards` answered `{"items": []}` with HTTP 200, and `oc_launchpad_dashboards` was empty. Two separate defects, and the first one hides the second. 1. NOTHING EVER CREATED A DASHBOARD. `allowUserDashboards` defaults to `false` on purpose (REQ-ASET-003: an admin opts in before users may create personal dashboards). That default closes the only branch of `tryCreateFromTemplate()` that WRITES, so on a brand-new instance no dashboard was created by the app or by the user, and every rung of `resolveActiveDashboard()` returned null. The default widget bundle (the Conduction, Sendent and Nextcloud tiles plus a Files widget) had shipped and had unit tests. Nothing ever called it, because nothing ever created the dashboard it seeds. `SeedDefaultDashboard` now provisions it on install and after every upgrade, idempotently. THE SHAPE IS LOAD-BEARING. This app has two resolution chains and they do not agree. `getEffectiveDashboard()` consults admin templates; the page shell calls `resolveActiveDashboard()`, which reads only `getVisibleToUser()` and never looks at a template. A default ADMIN TEMPLATE therefore seeds four placements the workspace still will not render. Measured, not reasoned: the first cut of this fix seeded exactly that, and the app kept showing its empty state. The shape the shell resolves is a `group_shared` row on the reserved `default` group (REQ-DASH-012) with `isDefault = 1`, created `published` because `filterByPublicationState()` hides drafts from every non-owner and this row has none. 2. THE TWO CHAINS DISAGREED, SILENTLY. With the row seeded, the sidebar listed "DEFAULT / Dashboard" and the grid drew its empty state on top of it: `GET /api/dashboard` answered `{"error": "No dashboard available"}` for the same user the shell had just resolved. `getEffectiveDashboard()` had no group-shared rung at all. With no preference set it went straight from owned dashboards to shares and templates. It now resolves the `default` sentinel the same way `resolveActiveDashboard()` steps 3 and 5 do. Only the sentinel. A per-group default needs the caller's primary group, which this signature does not carry, and guessing one would make the two chains disagree in a new way. Verified on a running instance: `occ maintenance:repair` seeds the row (`group_shared` / `default` / `isDefault=1` / `published`) and `/apps/launchpad/` renders Conduction, Sendent, Nextcloud and Files. 1577 unit tests pass; phpcs, psalm and phpstan clean on the changed files. * test(repair): cover SeedDefaultDashboard, which shipped with none `Guard coverage baseline` was red on the scoped ratchet, not on the suite — PHPUnit itself passed (1577 tests, 4059 assertions): Surviving code, head: 75.05% (716/954 statements) Surviving code, base: 76.83% (693/902 statements) FAIL: coverage of the code this change KEEPS or ADDS dropped by 1.78%. The PR added 52 statements and covered 23 of them. `lib/Repair/SeedDefaultDashboard.php` (125 lines) arrived with no test at all; DashboardService got one, the repair step did not. Five tests over all four paths: the step's name, a seed that provisions, a seed that finds one already there, a seed that throws, and the container failing to resolve the service at all. The two failure tests are the ones worth having. A repair step that lets an exception escape aborts `occ upgrade` for the WHOLE instance — a missing demo dashboard is cosmetic, a failed upgrade is not — so the step swallows and warns, and without a test that asserts the swallow a later refactor could turn the one into the other silently. Both info branches are asserted on their MESSAGE, not just their call count: they both call $output->info(), so a count-only test would pass with the two swapped. Local: OK (5 tests, 17 assertions). * refactor(dashboards): extract the default-group rung, which took the method past phpmd `PHP Quality (phpmd)` was red on one violation: lib/Service/DashboardService.php:435 ExcessiveMethodLength The method getEffectiveDashboard() has 104 lines of code. Current threshold is set to 100. The group-shared rung this PR added is what pushed it over, so that is what comes out — into resolveDefaultGroupDashboard(), carrying its own explanation of why the rung exists at all (the page shell and the API resolved differently, silently, so the sidebar listed a dashboard the grid drew an empty state over). Behaviour is unchanged: same resolveGroupSharedDashboard() call with the same DEFAULT_GROUP_ID sentinel, in the same position in the chain, returning the same built result. The early-return is inverted only to keep the extracted method flat. Verified locally: php -l clean, phpmd exit 0 on BOTH legs (the ruleset and the unused-params ruleset, as CI runs them), PHPUnit OK (1582 tests, 4076 assertions). --------- Co-authored-by: Conduction Release Bot <release-bot@conduction.nl> Co-authored-by: Ruben van der Linde <juan.claude@conduction.nl>
Quality Report — ConductionNL/launchpad @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 538/538 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-28 10:14 UTC
Download the full PDF report from the workflow artifacts.
) Three specs assert "No dashboards available". #361 (c9e5808) exists to make that message impossible: SeedDefaultDashboard provisions an INSTANCE-WIDE, group-shared dashboard on install and after every upgrade, because a fresh install greeting a user with an empty shell was the defect. So the specs are not wrong about the app; the app deliberately removed the state they describe. They have been red on development since that commit. Handled three different ways, on purpose: 1. active-dashboard-resolution.spec.ts — INVERTED, not deleted. The valuable assertion is the opposite of what it said: a brand-new account, which owns no dashboard of its own, must still resolve to the seeded one. That is exactly the guarantee #361 introduced, and nothing was testing it. 2. runtime-shell-canEdit.spec.ts x2 — excluded WITH a reason. These assert the presence/absence of a Create CTA *inside* the empty state, so inverting them is not possible: the container is gone. The rule underneath (REQ-ASET-003 — allowUserDashboards gates whether a user may create a dashboard) is still worth covering and now needs an assertion that does not route through the empty state. The reason says so, names the commit, and reaches the Playwright report — so the skip-discipline gate reads it as attributed rather than silent. 3. fixtures/secondary-user.ts — the docblock said a brand-new account has zero dashboards "personal, group, or default", and that this was how the empty-state scenarios reached their state. That premise is now false and would have sent the next person down the same path. Verified: npm ci rc=0, prettier clean, `playwright test --list` compiles 11 tests across both files. Refs #372. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Adds `launchpad-compose.yaml` and a setup page describing it. The compose brings up Postgres and Nextcloud, installs openregister (required), thematiq and integriq (optional) and launchpad from release tarballs, and enables them in dependency order. Nothing is bind-mounted: Nextcloud installs an app by deleting its directory and extracting an archive over it, so pointing that at a checkout deletes the working tree — measured on a development machine on 2026-08-27, where an app-store update fired on a container restart and removed every top-level file including .git. Release tarballs rather than a clone for a second reason: a tarball is a complete app carrying vendor/ and the built js/, and an app with no vendor/ does not fail loudly — it warns once and keeps loading, so it looks installed while every service needing a dependency is absent. The openregister dependency is not declared in appinfo/info.xml — no app in the fleet declares an <app> dependency — so the compose encodes what the manifest does not. Verified: docker compose config parses and interpolates; the same generated file was booted end to end for portaliq, which produced 17 registers, 86 schemas and 13 magic tables for its own register, with the portal content API returning a real site rather than an empty shell. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Two defects, both measured against a booted demo rather than inferred from
the code.
The verification step told you to run
curl -s -o /dev/null -w '%{http_code}' http://localhost:PORT/apps/APP/
and described a pass. That request is unauthenticated, and a Nextcloud app
page requires a login, so it prints 401 on a perfectly healthy demo. A reader
following the page would conclude the demo was broken. The command now sends
the demo credentials and says in words that a bare 401 is expected.
Thematiq additionally has no app route at all — it declares
<admin-section>theming</admin-section> — so /apps/thematiq/ answers 404 even
authenticated. Its page now opens Settings -> Administration -> Theming.
Verified on two booted demos: the corrected commands return 200.
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
… state (#380) The last red spec on launchpad's development. #362 fixme'd two empty-state tests when #361 (c9e5808) made that state unreachable; this third one used the same locator and was missed, so it kept failing: Error: expect(locator).toBeVisible() failed Locator: locator('.workspace-shell__empty-cta') Error: element(s) not found SeedDefaultDashboard now provisions an instance-wide, group-shared dashboard on install and after every upgrade, so no account resolves to zero dashboards and no user ever sees the empty-state CTA. The CTA was never the claim. The claim is REQ-ASET-003 — an ordinary user may create a dashboard when allowUserDashboards is on — and that is what actually regressed once: `dashboard.create` 403'd and the user was stranded on the empty state. The sibling fixmes say so themselves: "the underlying rule is still worth covering, but it needs an assertion that does not route through the empty state." This is that assertion. It POSTs to the endpoint as a throwaway non-admin and requires 200/201. Three details carried over from the file's own hard-won conventions, rather than reinvented: - Basic-Auth + OCS-APIRequest via the existing `userApi` helper. A cookie-authenticated POST with no `requesttoken` is rejected by Nextcloud's CSRF check before reaching the controller, which would fail this test for a reason unrelated to authorization. - `expect([200, 201]).toContain(status)`, NOT `not.toBe(403)`. The weaker form also passes on 401 and 404, neither of which proves an ordinary user may create anything. That exact false green is documented two tests above. - The route is `dashboardApi#create`, `POST /api/dashboard`, read from appinfo/routes.php. The same file records an earlier draft that used a made-up path, got 404, and would have passed a weaker check. `create()` takes only optional parameters and is `#[NoAdminRequired]`, so a bare POST reaches `denyCreate()` — which is precisely the gate under test. Verified: npm ci rc=0, npm run lint rc=0, prettier clean, and `playwright test --list` compiles all 8 tests in the file. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
…licks (#393) @conduction/nextcloud-vue 2.22.x made the product walkthrough actually open. A `placement: "center"` welcome step used to be parked in `_pendingAutoTour` and never shown; the library now correctly starts it on any route. Its `cn-walkthrough__dim--full` layer is a `role="dialog" aria-modal="true"` overlay, so every spec that clicks behind it times out, and `getByRole('dialog').first()` resolves to the dim layer rather than the modal under test. The marker is per USER, not per test, so leaving it unseeded also makes the suite order-dependent: whichever spec runs first wears the tour. Seeds the same marker dossiq's global-setup already seeds, with a sentinel above any real app version so the tour composes to an empty step set. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
The walkthrough-marker seed added to this file is not Prettier-clean, so `Frontend Check (format)` fails on development — and the E2E tier with it. `prettier --write` only, no behaviour change. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Two independent faults, either of which alone stops the docs site updating. TRIGGER. This listened on a branch called `documentation`. Nobody has pushed to one since 2026-05-25, so every docs change merged to `development` passed review and published nothing. SECRETS. A reusable workflow receives no secrets by default. With none mapped, the callee's publish step finds CF_API_TOKEN empty and skips itself on its own guard, and the run finishes GREEN having changed nothing. Fixing only the trigger would have produced exactly that. The worker name is now pinned. Deriving it is the documented way to get a green run that reaches nobody: wrangler creates the derived worker and publishes there while the custom domains keep routing to the real one. Where the app was renamed, `canonical-host` turns the retired hostname from a second live copy of every page into a 301 to the same path on the current one. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Bumps [stylelint](https://github.com/stylelint/stylelint) from 15.11.0 to 17.14.1. - [Release notes](https://github.com/stylelint/stylelint/releases) - [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md) - [Commits](stylelint/stylelint@15.11.0...17.14.1) --- updated-dependencies: - dependency-name: stylelint dependency-version: 17.14.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@vue/test-utils](https://github.com/vuejs/test-utils) from 2.4.11 to 2.5.0. - [Release notes](https://github.com/vuejs/test-utils/releases) - [Commits](vuejs/test-utils@v2.4.11...v2.5.0) --- updated-dependencies: - dependency-name: "@vue/test-utils" dependency-version: 2.5.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.4.13 to 3.4.14. - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](cure53/DOMPurify@3.4.13...3.4.14) --- updated-dependencies: - dependency-name: dompurify dependency-version: 3.4.14 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) from 3.2.7 to 4.1.11. - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.11/packages/coverage-v8) --- updated-dependencies: - dependency-name: "@vitest/coverage-v8" dependency-version: 4.1.11 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
) Bumps [@cyclonedx/cyclonedx-npm](https://github.com/CycloneDX/cyclonedx-node-npm) from 5.0.0 to 6.0.1. - [Release notes](https://github.com/CycloneDX/cyclonedx-node-npm/releases) - [Changelog](https://github.com/CycloneDX/cyclonedx-node-npm/blob/main/HISTORY.md) - [Commits](CycloneDX/cyclonedx-node-npm@v5.0.0...v6.0.1) --- updated-dependencies: - dependency-name: "@cyclonedx/cyclonedx-npm" dependency-version: 6.0.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [vue](https://github.com/vuejs/core) from 3.5.41 to 3.5.42. - [Release notes](https://github.com/vuejs/core/releases) - [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md) - [Commits](vuejs/core@v3.5.41...v3.5.42) --- updated-dependencies: - dependency-name: vue dependency-version: 3.5.42 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [phpmetrics/phpmetrics](https://github.com/phpmetrics/PhpMetrics) from 2.9.1 to 2.11.0. - [Release notes](https://github.com/phpmetrics/PhpMetrics/releases) - [Changelog](https://github.com/phpmetrics/PhpMetrics/blob/master/CHANGELOG.md) - [Commits](phpmetrics/PhpMetrics@v2.9.1...v2.11.0) --- updated-dependencies: - dependency-name: phpmetrics/phpmetrics dependency-version: 2.11.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…408) Bumps [phpcsstandards/phpcsextra](https://github.com/PHPCSStandards/PHPCSExtra) from 1.5.0 to 1.5.1. - [Release notes](https://github.com/PHPCSStandards/PHPCSExtra/releases) - [Changelog](https://github.com/PHPCSStandards/PHPCSExtra/blob/develop/CHANGELOG.md) - [Commits](PHPCSStandards/PHPCSExtra@1.5.0...1.5.1) --- updated-dependencies: - dependency-name: phpcsstandards/phpcsextra dependency-version: 1.5.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…407) Bumps [squizlabs/php_codesniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer) from 3.13.6 to 4.0.4. - [Release notes](https://github.com/PHPCSStandards/PHP_CodeSniffer/releases) - [Changelog](https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/4.x/CHANGELOG-3.x.md) - [Commits](PHPCSStandards/PHP_CodeSniffer@3.13.6...4.0.4) --- updated-dependencies: - dependency-name: squizlabs/php_codesniffer dependency-version: 4.0.4 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [twig/twig](https://github.com/twigphp/Twig) from 3.27.0 to 3.28.0. - [Release notes](https://github.com/twigphp/Twig/releases) - [Changelog](https://github.com/twigphp/Twig/blob/3.x/CHANGELOG) - [Commits](twigphp/Twig@v3.27.0...v3.28.0) --- updated-dependencies: - dependency-name: twig/twig dependency-version: 3.28.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan-phar-composer-source) from 2.2.8 to 2.2.9. - [Commits](https://github.com/phpstan/phpstan-phar-composer-source/commits) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-version: 2.2.9 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [nextcloud/ocp](https://github.com/nextcloud-deps/ocp) from 34.0.2 to 34.0.3. - [Commits](nextcloud-deps/ocp@v34.0.2...v34.0.3) --- updated-dependencies: - dependency-name: nextcloud/ocp dependency-version: 34.0.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…260830084126 chore(sync): carry beta back into development
beta held 19 commit(s) development did not. Merged with -s ours: development's tree is kept BYTE FOR BYTE and only the ancestry is recorded. That is the payload -- without it the merge base never moves and the next development -> beta promotion conflicts on the version file exactly as before. 13 of 19 promotion PRs were CONFLICTING for this reason. Nothing is silently imported. What beta holds and development does not, and which this deliberately does NOT bring over: lib/Settings/launchpad_register.json Those are dead Forgejo/Codeberg CI (removed from development on 2026-08-24/25 by 'chore(ci): remove dead Forgejo/Codeberg CI configuration'), generated Docusaurus build output, and community-health files that never existed on development. Each can be added deliberately if wanted; resurrecting them as a side effect of a sync is how a merge silently undoes a decision.
…0841 chore(sync): record beta's ancestry on development
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Two dependabot bumps landed on development on 2026-08-30 without CI, and each leaves the tree unresolvable: - @vitest/coverage-v8 went to 4.1.11 while vitest and @vitest/ui stayed on ^3.2.7, so coverage-v8 peers vitest 4.1.11 against @vitest/ui's vitest 3.2.7 - stylelint went to 17.14.1, which @nextcloud/stylelint-config 2.4.0 cannot peer: it wants ^15.6.0 Both are reverted to the version the rest of their own ecosystem is on, rather than bumping the ecosystem, because a vitest 3 to 4 move is a separate change that deserves its own testing. Verified with npm ci --dry-run from the committed lock: exit 0. 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 | ⏭️ | ||||
| composer | ⏭️ | ⏭️ | |||
| npm | ⏭️ | ⏭️ | |||
| app:check-code | ⏭️ | ||||
| info.xml | ⏭️ | ||||
| REUSE | ⏭️ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-30 09:06 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 | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 544/544 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-30 10:17 UTC
Download the full PDF report from the workflow artifacts.
…426) * fix(deps): hoist @vue/server-renderer so the unit suite can run Every one of the 63 frontend test files failed, with no tests executed at all: Error: Cannot find package '@vue/server-renderer' imported from node_modules/@vue/test-utils/dist/vue-test-utils.cjs.js The package was installed, but NESTED at node_modules/vue/node_modules/@vue/server-renderer rather than hoisted. @vue/test-utils declares it as a PEER dependency ('3.x'), and a peer is resolved upward from the importing package's own directory -- so @vue/test-utils looked for node_modules/@vue/server-renderer, which did not exist. Nothing was missing from the lockfile; it was in the wrong place. Declaring it directly pins it at the top level, which is where a peer has to be. Version tracks vue itself (^3.5.42). Verified locally: reproduced the failure first (1 file, 'no tests'), then after the change the full suite runs -- 63 files passed, 682 tests passed, 0 failed. * fix(stylelint): extend the config the app actually declares stylelint exited 78 -- a CONFIGURATION failure, not a lint finding: Could not find "stylelint-config-recommended". stylelint.config.js extended 'stylelint-config-recommended-vue', which was never declared in package.json. It was present only transitively, and the config it in turn extends, stylelint-config-recommended, was not installed at all. launchpad already declares @nextcloud/stylelint-config ^2.4.0, and that is what openregister, opencatalogi, dossiq and shillinq all extend. This points the config at the package the app declares and the fleet uses, rather than adding two more dependencies to prop up an outlier. That made stylelint RUN, which surfaced 44 real violations the configuration error had been hiding. 42 were auto-fixable (rule-empty-line-before, plus a few over-indented selector continuation lines) and were fixed with --fix; the diff outside css/ is whitespace only. The last two were a genuine CSS bug in css/header-override.css: background-color: #ffffff !important; background-image: none !important; background: #ffffff !important; <- discards both of the above The shorthand alone already sets the colour and resets background-image to none, so keeping only it preserves the computed result EXACTLY. Keeping the longhands instead would not have, because the shorthand also resets the other background sub-properties. Verified: stylelint now exits 0. * fix(stylelint): let Prettier own whitespace, and stop the fixer loop The previous commit made stylelint run, and it and Prettier then disagreed about the same six lines. Prettier indents a wrapped selector list; stylelint's `indentation` rule demanded 0 tabs there. Running either fixer broke the other check: npm run stylelint:fix -> Frontend Check (format) fails npm run format:fix -> Vue Quality (stylelint) fails Both `indentation` and `string-quotes` are DEPRECATED in stylelint 15 -- it prints a deprecation warning for each on every run -- precisely because formatters do this better. Turning them off resolves the conflict in favour of the tool that owns formatting and leaves stylelint judging what only it can judge: CSS semantics. The three files are re-formatted to Prettier's shape. Verified: stylelint exit 0 AND prettier exit 0 together, with the two deprecation warnings gone. --------- 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 | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 544/544 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-30 11:06 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.