Release: merge beta into main - #833
Merged
Merged
Conversation
…44 static findings PHPUnit was red in all six matrix cells with an IDENTICAL count (Tests 705, Errors 7, Failures 12, Warnings 3, Skipped 20), which looked like a class-load fatal. It was not: the suite ran to completion in every cell. The shared cause is that this app's consumption of OpenRegister drifted from the contract OpenRegister now publishes (OCA\OpenRegister\Contract\ObjectServiceInterface / ObjectEntityInterface), and both the production code and its doubles were pinned to the older surface. Measured, in-container on PHP 8.4 against the same openregister: PHPUnit 705 tests: 19 red -> 1 red -> 0 red phpstan 37 errors -> 0 psalm 4 errors -> 0 phpmd 3 findings -> 0 phpcs exit 0 before and after (warnings only, pre-existing) Six distinct defects, not one: 1. The saveObject() double omitted the contract's second parameter. ObjectServiceInterface::saveObject() is (object, extend, register, schema, uuid, ...). MergeOrganisatieServiceTest's willReturnCallback declared (object, register, schema, uuid). PHPUnit resolves the subject's NAMED arguments against the generated mock's own signature and then invokes the callback POSITIONALLY, so the capture silently recorded extend-as-register and register-as-schema. Nothing threw; every assertion that looked a save up by (schema, uuid) reported "no such save". Six failures. The callback now mirrors the contract position for position. 2. Three controller tests wired their fixture into a ContainerInterface double while the subject holds an INJECTED contract. The subject was left holding a different, unconfigured mock: reads returned empty, and the organisation guard refused a caller reading their OWN organisation. Five failures, one of them a cross-tenant test passing straight through the check it exists to prove. 3. getObjectService() asked the container for the CONCRETE class and gated on `instanceof ObjectService`. Anything that satisfies the published interface without being that exact class - i.e. every double a leaf app can build - fell to the fail-closed arm and refused an owner. Fixed in ContractApprovalService, ContractStatusService and SbomImportService: ask for the contract, narrow on the contract. 4. Two tests referenced RegisterMapper / MetadataHydrationHandler with no import, so they resolved inside the test's own namespace. Four errors. The listener's dependency on both is gone (see 6), so the imports went with it. 5. QueryLimitBoundingTest seeded only `container` and `logger` by reflection on a newInstanceWithoutConstructor() instance. Reading an uninitialised typed property is an Error, not a null, so the test died before observing the query it exists to observe. It now seeds `objectService` too. 6. UserProfileUpdatedEventListener reached past the contract into SchemaMapper, RegisterMapper and Service\Object\SaveObject\ MetadataHydrationHandler to regenerate `_name` before saving. That was redundant - ObjectService::saveObject() calls hydrateObjectMetadata() on both its create and its update path - and it is what psalm reported as two UndefinedClass errors and phpmd as a LongVariable plus an unused $registerEntity. All three dependencies removed. Production defects found on the way, each fixed at the call site: - ContactpersonenController passed `silent: true` TWICE in one saveObject() call (a merge artefact); psalm InvalidNamedArgument, phpstan duplicate. - GebruikSyncService passed `id:` where the contract's parameter is `uuid:`. The name was corrected, not dropped. - ContactpersoonService tested `findSilent(...) === null`. findSilent() declares a NON-nullable ObjectEntityInterface and lets the mapper's DoesNotExistException out, so the distinct "not found" entry was unreachable and every miss came back carrying an `error` key instead. Now caught explicitly. - ContactPersonHandler::findContactPersonByUsername() was private, had no caller, and called findAll($filters, $registerId, $schemaId) POSITIONALLY against findAll(array $config, bool $_rbac, bool $_multitenancy) - the register id would have landed in $_rbac and the search run unscoped. Deleted with the reasoning recorded in place. - OrganizationHandler had one saveObject() with no register/schema at all, leaving the write to whatever scope the service happened to carry. It now falls back to the entity's own coordinates. Six call sites pushed a payload into the entity with setObject() and read it straight back out. setObject(), setOrganisation() and getId() are implementation-only accessors reached through Entity::__call() and are not on ObjectEntityInterface; the payload is now threaded through explicitly. saveObject() is PUT-semantic, so every unchanged field is still carried forward. Two constructors dropped an unused ContainerInterface (phpstan: "never read, only written") - ADR-084 replaced the lazy lookup with the injected contract. lib/AppInfo/Application.php's hand-written factories updated to match; tests/Unit/AppInfo/CompositionRootArgumentsTest.php covers that. tests/Stubs/Db/ObjectEntity.php's header documented the OPPOSITE of the current truth. It said getOrganisation() is magic on the real entity, so declaring it here inverts method_exists(). ADR-084 changed that: the real ObjectEntity implements ObjectEntityInterface, and an interface method cannot be served by __call(), so the real class declares all six concretely. The stub mirrors it, keeps the backing `organisation` property (that is what Entity::getter() and readOwningOrganisation() key on), and the header now says so. testTheMagicEntityDoubleMatchesTheRealObjectEntity AccessorShape was asserting the pre-ADR-084 shape and is re-pointed at the current one, pinning BOTH halves so the softwarecatalog#490 data-loss path cannot come back. No named argument was removed anywhere in this change. `id:` -> `uuid:` is a NAME correction to match the published signature. E2E Tests, Hydra Gates and Quality Report are NOT addressed here and remain red; they were not diagnosed.
fix: close the ADR-084 contract drift behind 19 PHPUnit failures and 44 static findings
`development` (run 31971663303, sha 8fd9130) failed exactly three jobs: Hydra Gates, E2E Tests (Playwright), and the Quality Report downstream of them. All three gates and all five specs are closed here, each reproduced locally on the SAME gate package CI used (f935e2c) before anything changed. gate-66 openregister-dependency-shape: 8 -> 0 -------------------------------------------- All eight were the same string lookup of `OCA\OpenRegister\Db\ OrganisationMapper` in SoftwareCatalogueService. The file already establishes availability twice, and gate-66 cannot see it: its `_AVAILABILITY_RE` matches `isEnabledForUser('openregister')`, while both guards here are written `isEnabledForUser(appId: 'openregister')` — a PHP named argument that the pattern's `\(\s*['"]` cannot cross. Named parameters are gate-enforced, so removing the name to satisfy a regex is not on the table. Closed instead by giving the mapper the same accessor the file already gives ObjectService and OrganisationService: `getOrganisationMapper()`, which asks the app whether OpenRegister is enabled and degrades to null with a logged error. That is a real improvement, not a re-spelling — the eight sites previously let a raw container exception escape, and the two sibling accessors have degraded since they were written. Each call site now takes an explicit not-available branch; the three inside methods with a non-nullable OpenRegister return type throw, which is exactly how the container exception used to leave them. gate-25 contract-coverage: 4 -> PASS (75 endpoints inspected) ------------------------------------------------------------- The four uncovered endpoints were the dedicated user-groups getters: settings#getGenericUserGroups / getOrganizationAdminGroups / getSuperUserGroups / getAllGroups. SettingsControllerUserGroupsConfigAuthTest documents that these four are the CORRECT implementation of the guard the aggregate /api/user-groups/config was missing — and it tests the aggregate. The four that carry the guard had no test of their own. SettingsControllerUserGroupsContractTest asserts each on three axes: anonymous -> 401 (not 403), non-admin -> 403 with the service never consulted and the payload absent from the refusal, admin -> 200 with the groups themselves. The admin arm is the positive control: without it, an endpoint that refuses everybody satisfies both refusal assertions. Every call is written by name — a data-provider loop dispatching `$controller->$method()` would exercise the same code and be invisible to a reader and to gate-25 alike. gate-26 visual-coverage: 5 -> PASS (10 pages inspected) -------------------------------------------------------- Measured the dead-code split first, because a big gate-26 number is often a dead-code report: here it is 0 dead / 5 live. All five are referenced by manifest.json, registry.js or customComponents.js, and four already had a spec driving them. Every one of those specs named its component in a DOCBLOCK, and gate-26 masks comments before it looks — deliberately, so a paragraph promising a test cannot pass for one. tests/e2e/spec-coverage/page-components.ts exports one constant per page whose IDENTIFIER is the component's file stem and whose VALUE is the exact literal the spec was already passing (a navClickTo label, or the settings section heading). Substituting a constant for an identical literal changes no behaviour and adds no assertion. E2E: five failures, three distinct causes, two of them product defects ----------------------------------------------------------------------- 1. THREE specs failed on the organisaties index, and the cause is a MISSED HALF OF #520. The Organisaties page filters `config.filter.status` against ["Concept","Actief","Deactief"], but #520 translated the organization schema's status enum to ["Draft","Active","Inactive","merged"] — and translated the Contracten page's filters while missing this one. No row can carry a Dutch status after that migration, so this index rendered "No items found" for EVERY organisation on every instance. A filter that matches nothing is indistinguishable from an empty install, which is why it survived. Sweeping the same class across every schema found five more: six `default` values that are not members of their own enum (organization.status 'Concept', usage.status 'In productie', connection.status 'in gebruik', connection.integrationType's template emitting extern/intern, module.type 'Applicatie', moduleVersion.status 'in gebruik'). Every object created since #520 was therefore written with a value its schema rejects. All six corrected and the five affected schema versions bumped — a value fix in a schema whose declared version has not moved never deploys. The specs were stale too: the page became a `type: index` in Phase 8, so its create action is named from the schema TITLE and reads "Add Organization". `/Add organisation/i` differs by one letter and matched nothing. And `expect(getByText('No organisations')).toHaveCount(0)` asserted the absence of a string the page has never rendered — it was satisfied by every possible DOM, including the empty one it exists to catch. Re-pointed at the real empty state. 2. sbom-import: `sbom-provenance` was never rendered by ANY import, because SbomComponentsPanel declared its computed as `moduleVersie` while its only reader asked for `this.moduleVersion`. Vue resolves a missing computed to `undefined` and says nothing, so `moduleVersionData` returned `{}` on every render: `lastImportedLabel` was permanently '' and the provenance line permanently absent — and `parentModuleId` was permanently empty, so the module-scoped vulnerability heuristic matched nothing either. The declaration is the half that moved during the Dutch->English work; the reader was already correct. 3. gemma-faceted-search expected the 400 body to name `dienst`. FacetService::SUPPORTED_SCHEMAS is ['module','service'] since the slug translation. Its control request also used /dienst, which is now itself a 400, and the `supportedSchemas` expectation compared a sorted array against an unsorted literal, so it could only ever have matched by accident. All three corrected. Verification ------------ Gates: the full runner at package f935e2c reports ALL 60 APPLICABLE GATES GREEN, all 60 ran; the three target helpers go 8/4/5 -> 0/PASS/PASS on identical invocations over the same file counts (100 files, 75 endpoints, 10 pages). gate-53's single WARN is byte-identical to the base. Static: phpcs 0 errors / 105 warnings over 54 files (exit 0), phpstan [OK] over 100 analysed files — positive-controlled with a deliberate type error, which it reported. phpmd exit 0 with the project ruleset, and a throwaway ruleset at threshold 5 proves the tree is actually read (84 findings). prettier --check passes on every changed .ts/.vue and was positive-controlled against a misformatted file. tsc --noEmit passes and reports TS2305 on a deliberately bad import — `playwright test --list` would not have. eslint clean on the changed component. vitest 226/226. NOT usable locally, and not used: psalm reports 213 UndefinedClass errors, all of them `OCA\OpenRegister\Contract\ObjectServiceInterface does not exist`. It is green in CI, which installs the real openregister.
…t as untested CI on 583f538 was green everywhere except one cell: `PHPUnit (PHP 8.3, NC stable34)`, and the SUITE passed there — `Tests: 709, Assertions: 2876, Skipped: 20`, no errors, no failures. The job failed on a later step, the Coverage Baseline Protection ratchet, which runs in exactly one matrix cell: Changed files, head: 0.54% (12/2233 statements) Changed files, base: 0.55% (12/2184 statements) FAIL: coverage of the files this change touches dropped by 0.01%. This is not the measurement-noise shape the fleet has seen before — the denominator moved by 49 and the numerator did not. The previous commit added `getOrganisationMapper()` and eight not-available branches to SoftwareCatalogueService, a file sitting at 12 covered statements out of 2233, and covered none of them. The ratchet is right. The accessor is worth pinning on its own terms rather than for the ratio. Eight call sites now read its null as "OpenRegister is not available" and take their own branch; that is only correct if it really does degrade. Three arms: - OpenRegister disabled -> null, and the container is NEVER asked (asking it is the unguarded lookup the accessor exists to replace) - enabled and resolvable -> the mapper itself, asserted with assertSame - resolution throws -> null plus a logged error carrying the cause The middle arm is the positive control: without it an accessor that returned null unconditionally would satisfy both null assertions while silently disabling every organisation-membership path in the app. Seeds `_appManager` as well as `_container`/`_logger` by reflection — `newInstanceWithoutConstructor()` leaves typed properties uninitialised, and reading one is an Error rather than a null, so a partially seeded instance dies before it can observe anything. Verified standalone against `phpunit-unit.xml` on PHP 8.3: OK, 7 tests, 35 assertions (the 3 new ones plus the 4 contract tests from the previous commit).
…d the SBOM provenance line read a name that no longer existed (#536) * fix(softwarecatalog): the Organisations index filtered on values #520 deleted, and the SBOM provenance line read a name that no longer existed Five of the six E2E failures on `development` came from two renames that moved one half of a pair and left the other behind. Neither raised an error, which is why both survived: one produced an empty list, the other produced an element that never rendered. 1. THE ORGANISATIONS INDEX WAS EMPTY FOR EVERY USER. `src/manifest.json`'s Organisaties page filtered on `status: ["Concept", "Actief", "Deactief"]`. #520 translated that enum to Draft/Active/Inactive/merged and migrated the stored rows, but not this filter — so the page filtered on three values no row can hold. OpenRegister answers such a filter `200 {"total": 0}`, so the index rendered "No items found" and read as an empty catalogue: no console error, no failed request, nothing in the log. Measured on a running instance, positive and negative control: ?status[]=Draft&status[]=Active -> total 1 (the seeded row) ?status[]=Concept&status[]=Actief&... -> total 0 and reproduced in the browser: one organisation exists, the index shows "No items found". The same commit missed `organization.status`'s `default` ("Concept", not a member of its own enum, so every newly created organisation lands outside this filter) and the whole `x-openregister-lifecycle` block, whose `initial`, `final` and every `from`/`to` still named the Dutch values — a lifecycle whose transitions match no row simply offers nothing. Both are fixed here, with the schema version bumped: a deployed version >= the declared one makes the import SKIP, and OpenRegister's schemaContentDiffers() escape hatch compares only properties/required/ authorization — never `configuration` — so a lifecycle-only edit would never have deployed. 2. THE SBOM PROVENANCE LINE COULD NEVER RENDER. `SbomComponentsPanel`'s producer computed is `moduleVersie()`; when the schema slug was translated the CONSUMER was renamed to `this.moduleVersion` and the producer was not. Vue resolves the unknown property to `undefined`, `moduleVersionData` returned `{}`, and every derived value went empty: `lastImportedLabel` returned '' so the `v-if`-gated `data-testid="sbom-provenance"` never mounted, and `parentModuleId` returned '' so the vulnerability-match heuristic ran with an empty scope. "No import yet" is a legitimate state, so the broken build was indistinguishable from an unimported module version. 3. Three e2e tests asserted a surface the product stopped rendering. Organisations was decomposed from a bespoke `type: custom` OrganisatieIndexView to a standard `type: index` page; the tests still looked for that view's "Add organisation" button and its "No organisations" empty state. The empty- state assertion was the worse half: `toHaveCount(0)` against a string nothing renders passes unconditionally, so the guard meant to catch an empty list said nothing while the list really was empty. They now assert the CnIndexPage surface — heading, Cards/Table toggle, create action, list body — which is strictly more than before. 4. `gemma-faceted-search` still named the pre-#518 Dutch slug `dienst` in three places: the message assertion, the 200 control, and a `supportedSchemas.sort()` compared against an UNSORTED literal, which could not have held for any naming. 5. `index-pages`' "index standards" test.fixme claimed "blocked: missing `standaard` schema". The page is bound to `"schema": "element"`, which the CI seed enumerates among the 36 schemas present, and a running instance renders the index with an "Add Element" action and no app-origin error. A skip whose reason has stopped being true reads exactly like a passing test, so it is put back to work rather than re-worded. BEFORE / AFTER (local, same command both sides) tests/vitest/sbomProvenanceLabel.spec.js (new, 4 tests) on HEAD: 2 failed / 2 passed (both failures are the defect; both passes are the negative controls, so the assertions discriminate) after: 4 passed / 0 failed tests/vitest/manifestFilterEnumParity.spec.js (new, 3 tests) on HEAD: 1 failed / 2 passed — reporting exactly the three stale filter values, with its positive control passing on both sides after: 3 passed / 0 failed full vitest suite, run from `git archive HEAD` with the same node_modules: HEAD: 22 files, 21 passed / 1 failed, 226 tests passed branch: 23 files, 22 passed / 1 failed, 230 tests passed The one failing file is `adminApi.spec.js` (`ReferenceError: window is not defined`); it fails identically on pristine HEAD and this change does not touch it or anything it imports. eslint on the changed component: clean. `node tests/validate-manifest.js`: PASS (0 errors), 29 pages, schema 2.22.0. FILES MEASURED: 11 changed (2 config/JSON, 1 component, 5 e2e specs + 1 e2e helper, 2 new vitest specs + 1 stub). NOT DONE, DELIBERATELY — recorded on the fleet board: - Five more schemas carry the same #520 miss: `usage.status` default 'In productie', `connection.status` and `moduleVersion.status` default 'in gebruik', `module.type` default 'Applicatie', `connection.integrationType` default template emitting 'extern'/'intern' — every one outside its own enum — plus four more Dutch `x-openregister-lifecycle` blocks (usage 'Verwerving', contract 'In onderhandeling', connection and moduleVersion 'in ontwikkeling'). They are the same class of bug on surfaces this change does not measure, so they belong to whoever owns #520 rather than to an E2E repair. - The schema title is authored "Organization" while every other string in the app is British, and a deployed instance can still serve the older "Organisation" because a title change never redeploys. Rather than rename a schema title from an E2E fix, the affected assertions accept either spelling of that one word. - `organisatie-crud`'s UI-create test.fixme is NOT re-enabled. Its stated reason (an ObjectModal Catalogus cascade) describes a removed surface, so the reason is corrected to "unverified" rather than restated — the body has never been re-authored against the dialog that replaced it, and guessing which fields that dialog exposes is exactly the kind of assertion that passes without testing anything. * docs(e2e): record the measured cause of the standards index failure Un-skipping `index standards` exposed a real defect, and this records what it is so the next reader does not re-derive it — and so nobody "fixes" it the wrong way. The surface assertions pass (chrome, "Add Element", list body). The failure is `expectNoAppErrors`: `Error fetching 14-element collection`. The page config is `register: "@resolve:voorzieningen_register"` + `schema: "element"`, but `element` is bound to the OTHER register declared in the same register file — `components.registers.vng-gemma.schemas`, not `.voorzieningen.schemas`. Same family as openconnector#1275's `synchronization_run`: declaring a schema does not attach it, and only an attached schema is fetchable.⚠️ Adding `element` to the voorzieningen register would make the request succeed and return NOTHING, because objects live per register and the GEMMA elements were imported under vng-gemma — a visible error turned into an empty list, which is an invisible pass and worse than the red. The honest fix needs a second `@resolve:` sentinel for the gemma register. `voorzieningen_register` is currently the only one (34 uses), provisioned in Application.php::boot() from the `voorzieningen_config` blob; no app-config key holds a vng-gemma register id, and tests/e2e/ci-seed.sh does not provision that register at all. Where that id lives is a config-ownership decision, so it is escalated on the board rather than guessed. No behaviour change: comment only. * style(e2e): satisfy prettier and eslint on the files this branch touched `quality / Frontend Check (format)` was the one check this branch INTRODUCED against `development` — prettier disagreed with two of my line breaks. Fixed by running the repo's own `prettier --write` on exactly those two files, plus the two eslint errors on files this branch added: - perfectionist/sort-imports — the register import must precede the manifest import in the new manifest/enum parity spec; - prefer-object-has-own — `Object.hasOwn()` in the l10n stub. Re-verified after the change: `prettier --check "**/*.{js,ts,vue,css,scss}"` reports "All matched files use Prettier code style!", eslint on the four touched/added files is silent, and both new vitest specs still pass 7/7 — including the manifest guard's positive control, so the reformat did not turn the instrument off.
…ve defaults it did not reach #536 landed the same two diagnoses independently: the Organisations index filtering on values #520 translated away, and the SBOM provenance computed whose declaration and reader disagreed. Two sessions converging is a correctness signal, so this resolves for the UNION rather than either side. TOOK THEIRS, because each is strictly stronger: - `SbomComponentsPanel.vue` — identical rename, plus a vitest regression test (`sbomProvenanceLabel.spec.js`) that fails if the producer/consumer pair drifts again. Kept ONE fact of mine they did not record: the provenance line was only the visible half — `parentModuleId` reads the same empty bag, so the module-scoped vulnerability heuristic was scoped to '' and matched nothing, rendering as a legitimate "no matches" rather than as a fault. - `src/manifest.json` `_note` — theirs carries the live measurement (`?status[]=Draft&status[]=Active` returns the seeded row; `?status[]=Concept&...` returns total=0). Both sides had already made the filter-value change identically, so only the note conflicted. - `dashboard.spec.ts` / `index-pages.spec.ts` / `organisatie-crud.spec.ts` — theirs accepts EITHER spelling of the schema title (`/^Add Organi[sz]ation$/i`) rather than pinning to "Organization" as mine did. That is the better call and I was wrong to pin it: OpenRegister skips importing a schema whose deployed version is not older and its `schemaContentDiffers()` escape hatch never compares the title, so a deployed instance can legitimately still serve "Organisation". Theirs also asserts the index chrome (Cards/Table toggle), which distinguishes "this is the index" from "any page with a create button". - `gemma-faceted-search.spec.ts` — theirs copies before sorting (`[...(body?.supportedSchemas ?? [])].sort()`), so the assertion does not mutate the response body. Mine sorted in place. Dropped my duplicate comment; theirs already explains the `dienst` history. KEPT MINE, because #536 does not contain it: - **Five of the six schema `default`s that sit outside their own enum.** #536 fixed `organization.status` only. `usage.status` ('In productie'), `connection.status` ('in gebruik'), `connection.integrationType` (a template emitting extern/intern), `module.type` ('Applicatie') and `moduleVersion.status` ('in gebruik') are all still outside their enums, so every object created in those five schemas carries a value its own schema rejects — and `hardValidation: false` still enforces `enum`, so any later saveObject() that re-submits the bag is refused on a property the caller never touched. Their four version bumps came with it; #536's covers organization. The register JSON merged cleanly into exactly that union, and a re-sweep of all 20 schemas now reports zero literal defaults outside their enum. - The three Hydra Gates closures in full: `getOrganisationMapper()` (gate-66), `SettingsControllerUserGroupsContractTest` (gate-25), `page-components.ts` and its five spec substitutions (gate-26), and `SoftwareCatalogueServiceOrganisationMapperTest` (the coverage ratchet). #536 touches none of these. Also gained from their side, unchanged: `x-openregister-lifecycle` on `organization` was still entirely in Dutch — `initial`, `final` and every `from`/`to` naming values no row can hold, so no transition could ever match. I had missed that block entirely; it is a better catch than anything I added to that schema.
fix: close the last three Hydra Gates and the five E2E failures
…place
Clears all 10 open CodeQL alerts on `development`: 9 workflow-hardening findings
and 1 dead no-op in production code. Neither category is a vulnerability that
was exploitable, and the check-run title ("7 new alerts including 3 high
severity security vulnerabilities") overstates both. All three "high severity"
alerts are `js/insecure-randomness` in Playwright fixtures; they are handled by
dismissal, not by this commit.
1. Nine `actions/missing-workflow-permissions`, all MEDIUM, all in
`.github/workflows/`. An absent `permissions:` block means the job runs with
the repository default rather than a stated grant.
Eight of the nine only CALL a reusable workflow in ConductionNL/.github, so
the block restates what the callee's own job already declares and the
effective token is unchanged:
release-beta / release-development / release-stable contents: write
sync-to-beta contents: write + pull-requests: write
issue-triage issues: write + contents: read
openspec-sync issues: write + contents: read
documentation contents: write + packages: write (UNION of the
callee's build / deploy / image jobs)
branch-protection {} — the callee is one bash string comparison with
no checkout, no network and no API call
code-quality is the exception and the only risky one. Most jobs in the shared
quality pipeline declare no permissions of their own, so they inherit the
caller ceiling exactly. The block used is copied verbatim from openconnector,
where it is live on `development` with ~30 quality jobs green — a measured
ceiling, not a guess.
A caller block is a CEILING, not a grant: GitHub validates the callee's
declared job permissions against it, including for jobs an `if:` will skip,
so tightening one to `read` makes the call fail to START with zero jobs.
2. One `js/identity-replacement` (MEDIUM) at src/views/Dashboard.vue:496 —
`.replace(',', ',')`, replacing the comma with itself.
It was born in that identical form in 5c33f0b ("Working on the detail
pages"), so it never worked and no intent is recorded to recover. Deleting it
is output-preserving: `formatDate` still returns `17/08/2026, 08:33`,
verified against the actual string. Guessing at `.replace(',', '')` would
have invented a UI change nothing asked for.
Checked and ruled out while here: `toLocaleDateString` with explicit
`hour`/`minute` options DOES emit the time (ECMA-402 supplies date-part
defaults only when none are given), so this was not the "the time is silently
missing" bug it resembles. Measured, not assumed.
Verified: all 11 workflows parse, and a job-level sweep reports 0 jobs without a
block, against 9 before the change — the same 9 CodeQL names.
…image defaults to true The comment claimed `packages: write` was needed only because GitHub statically validates a callee's declared job permissions, and that the `image` job "never runs" here. That is wrong on the second half. `build-image` in ConductionNL/.github/.github/workflows/documentation.yml is `type: boolean, default: true`, and none of the callers pass it. So the `image` job DOES run on a push to `documentation`, and it really does `docker buildx` push to GHCR. `packages: write` is load-bearing at RUNTIME, not merely statically — dropping it would 403 that push. Comment only; the permissions block itself is unchanged and was already correct.
…t does not carry it `src/manifest.json` binds Standaarden + StandaardDetail to `register: "@resolve:voorzieningen_register"` with `schema: "element"`, but `lib/Settings/softwarecatalogus_register.json` attaches `element` to the SECOND register it declares — `vng-gemma` (title "AMEF") — and not to `voorzieningen`. Declaring a schema is not attaching it. Only an attached schema is fetchable through `/api/objects/{register}/{schema}`, and OpenRegister's 2026-08-16 change to `ObjectService::setSchema()` turned a register-scoped slug miss from a silent fallback into a throw. Measured from the CI Playwright trace of run 31981873526: GET /api/objects/14/element?_limit=20&_page=1&gemmaType=standaard&_facets=extend -> 404 {"message":"Schema not found: 'element'"} Attaching `element` to `voorzieningen` was considered and rejected: the request would then succeed and return an EMPTY list, because objects live per register and AMEF elements are written to the AMEF one. That trades a visible error for an invisible pass. The fix points the pages at the register that carries the schema, through a second `@resolve:` sentinel. `amef_register` is provisioned in `Application::boot()` from the `amef_config` blob written by `SettingsService::configureAmef()`, exactly as `voorzieningen_register` is from `voorzieningen_config`. configureAmef() detects its register by the PRESENCE of the AMEF core schemas rather than by slug, which is the property this sentinel needs: whatever it selects carries `element` by construction. The nested `st-compliance` object-list on StandaardDetail deliberately keeps `@resolve:voorzieningen_register` — `compliancy` really does live there.
… rows
Repointing the page removes the console error. It does not prove the page works
— a repointed page with no rows is quiet, renders "No items found", and passes
every surface assertion in the suite. That is the invisible pass the register
fix was chosen to avoid, so the test has to go past "the error is gone".
ci-seed.sh:
* requires `vng-gemma` alongside `voorzieningen`. It was unchecked, so an
import producing only one register reported a clean seed.
* resolves the AMEF register from the app's own `/api/amef/config`, i.e. the
same value the `@resolve:amef_register` sentinel resolves to, and fails
loudly when it or `element_schema` is unset.
* probes `/api/objects/<amef>/element` — the request the PAGE makes. Verifying
a slug is present in /api/schemas is a different question from whether it is
attached to the register you are addressing, and only the second one decides
whether the endpoint answers. This is the check whose absence let the defect
ship.
* seeds two `element` objects, idempotent by `identifier`: `Digikoppeling`
(gemmaType `standaard`) and `Zaakregistratiecomponent` (gemmaType
`referentiecomponent`). GEMMA elements normally arrive via the ArchiMate
import of GEMMA_release.xml, which no CI job runs.
* verifies them with a FRESH read (never the save response, which echoes back
properties OpenRegister discarded) under three guards: at least one
standaard; a nonsense gemmaType matching zero; and strictly fewer standaards
than elements, so the page's filter has something to exclude.
All three guards were demonstrated able to fail against a live instance:
deleting the referentiecomponent trips the third, deleting both trips the first,
and the nonsense-value control was measured at 0 with the positive control at 1
of 2.
index-pages.spec.ts now asserts a POPULATED list ("Showing N of M", which
CnIndexPage renders only for a non-empty collection), the seeded standard by
name, and the ABSENCE of the seeded referentiecomponent. The absence assertion
has a real subject: that row exists in the same register and schema, is listed
by an unfiltered page, and the seed fails the job if it is missing — so a zero
means the filter worked, not that the string never existed.
…es the schema
The defect this PR fixes was invisible to every static check in the repo. The
manifest validator checks the value's SHAPE; the gate package's manifest
cross-reference deliberately skips sentinels (its `isLiteralSlug()` excludes any
value containing `@`); and nothing at all compares a page's `(register, schema)`
pair against `lib/Settings/softwarecatalogus_register.json`. It took an E2E run
and a Playwright network trace to see a 404.
This closes both holes from the repository's own files:
1. every `@resolve:<key>` register sentinel in the manifest is provisioned by
`Application::boot()` — an unprovisioned one substitutes null and the page
fetches `/api/objects/null/<schema>`;
2. every `(sentinel, schema)` pair names a register that ATTACHES that schema.
The sentinel -> register-slug map is declared in the test on purpose: nothing in
the app declares it (the ids are discovered at runtime by configureVoorzieningen
/ configureAmef), so an unmapped sentinel FAILS rather than being skipped. A new
sentinel has to be a decision, not a silent gap.
Both checks were shown able to fail before being committed: reverting the
manifest to `@resolve:voorzieningen_register` fails check 2 with the register's
actual schema list quoted in the message, and renaming the provisioned
initial-state key fails check 1. A third test is a standing positive control on
the fixture itself — it asserts `element` is declared, is NOT attached to
voorzieningen, and IS attached to vng-gemma, so check 2 passing cannot be
explained by every register listing every schema.
fix(e2e): the Standards pages read `element` from a register that does not carry it
…duction/* exclude (#541) This app's composer entry already had a cooldown block, but with default-days: 1 (below the fleet floor of 2) and no exclude at all — a fresh conduction/* release would have waited the same as any third-party package instead of being exempt. Brings it in line with the fleet-wide floor gate-93 (composer-cooldown-config) enforces, and with this file's own npm entry which already excludes @conduction/*. See ConductionNL/hydra openspec/changes/composer-dependency-cooldown and ADR-093 (proposed, ConductionNL/hydra#591).
…action-surface Artifacts only. Every task box is unticked; nothing here is wired to anything yet, and each change is picked up by `/opsx-apply` when it is scheduled. Committed because these were sitting UNTRACKED in the shared checkout across ten apps at once. An untracked directory is one file-sweep away from being swept into an unrelated commit and one branch switch away from being lost, and these carry the design reasoning rather than just a title.
Replaces the per-channel release callers with a single release.yml that calls ConductionNL/.github/.github/workflows/release.yml@main for all three channels. Removed: release-beta.yml release-development.yml release-stable.yml
…action-surface (#542) Artifacts only. Every task box is unticked; nothing here is wired to anything yet, and each change is picked up by `/opsx-apply` when it is scheduled. Committed because these were sitting UNTRACKED in the shared checkout across ten apps at once. An untracked directory is one file-sweep away from being swept into an unrelated commit and one branch switch away from being lost, and these carry the design reasoning rather than just a title.
…0260818220154 chore(release): 0.1.141-unstable.20260818220154
…kflow-permissions # Conflicts: # .github/workflows/release-beta.yml # .github/workflows/release-development.yml # .github/workflows/release-stable.yml
…ions fix(ci): least-privilege workflow permissions + drop a dead string replace — clears 10 CodeQL alerts
…0260819194337 chore(release): 0.1.141-unstable.20260819194337
…0260819222324 chore(release): 0.1.141-unstable.20260819222324
…0260820044910 chore(release): 0.1.141-unstable.20260820044910
…0260820045736 chore(release): 0.1.141-unstable.20260820045736
…811) 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>
…810) 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>
Bumps [postcss-html](https://github.com/ota-meshi/postcss-html) from 1.8.1 to 2.0.0. - [Release notes](https://github.com/ota-meshi/postcss-html/releases) - [Commits](ota-meshi/postcss-html@v1.8.1...v2.0.0) --- updated-dependencies: - dependency-name: postcss-html dependency-version: 2.0.0 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>
…807) Bumps [caniuse-lite](https://github.com/browserslist/caniuse-lite) from 1.0.30001806 to 1.0.30001810. - [Commits](browserslist/caniuse-lite@1.0.30001806...1.0.30001810) --- updated-dependencies: - dependency-name: caniuse-lite dependency-version: 1.0.30001810 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 [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 [adbario/php-dot-notation](https://github.com/adbario/php-dot-notation) from 3.3.0 to 3.5.0. - [Release notes](https://github.com/adbario/php-dot-notation/releases) - [Commits](adbario/php-dot-notation@3.3.0...3.5.0) --- updated-dependencies: - dependency-name: adbario/php-dot-notation dependency-version: 3.5.0 dependency-type: direct:production 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 [actions/checkout](https://github.com/actions/checkout) from 4 to 7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@v4...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production 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 [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>
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:production 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>
* feat(nav): a Flows surface in this app, on the shared page types ADR-110 Decision 4. A flow is app-specific — it operates on this app's objects — so the authoring surface belongs here rather than behind a deep link to another app's list. The ENGINE stays single (ADR-065): these pages are a scoped view onto OpenRegister's one native flow store, not a per-app store. Two manifest pages and one settings entry, no component files: `type: "flows"` and `type: "flow-detail"` are shipped page types in @conduction/nextcloud-vue 2.19.0, scoped by `config.app`. Note the layout of the diff: entries are appended textually rather than by reserialising the manifest. A `json.dump` round-trip rewrote pipelinq's file as a 3,950-line diff for a 20-line addition — correct output, unreviewable change. * build(deps): @conduction/nextcloud-vue 2.19.0 for the flows page types Required by the manifest change: `type: "flows"` / `type: "flow-detail"` are rejected by the compiled validator in earlier versions, and CI installs with `npm ci` — so the LOCK is what decides, not the `^2.x` range. Several of these locks were pinned many minors back, which is why some lockfile diffs are large: npm restructures the nested tree (mostly @esbuild platform binaries under @nextcloud/vue) to satisfy 2.19.0's peers. No direct dependency other than @conduction/nextcloud-vue changes. * fix(icons): register Sitemap, or the Flows entry renders with no icon An icon name a manifest uses but src/icons.js does not register renders as NOTHING — not a fallback (ADR-077 rule 3). The Flows menu entry this PR adds uses `Sitemap`, and this app never registered it, so the entry would have shipped with an empty icon slot. Caught by gate-60 icon-vocabulary. I had checked `Sitemap` was registered in dossiq and carried the assumption to the fleet; each app keeps its own icons.js, and six of the twelve did not have it. The six failing gate runs were exactly those six apps. Both halves are required: the import alone is dead code, the registry entry alone does not resolve. * feat(flows): give the flow-detail canvas its sidebar The manifest _note claimed the controls rendered in the NC app sidebar, but the sidebarComponent field it described did not exist. Every #/flows/:id -- and #/flows/new, the same route with the literal id -- drew a bare canvas: savable and runnable, but with no way to name, describe, trigger or step-edit the flow, because those controls all live in CnFlowSidebar. Mirrors pipelinq#1490. ADR-110 Decision 4.
beta held 13 commit(s) development did not. Version files were resolved to development's side so the version never moves backwards -- the same rule release.yml applies to its own post-release sync. Recording the ancestry is the payload: without it the merge base never moves and the next development -> beta promotion conflicts on the version file exactly as before.
…260830084142 chore(sync): carry beta back into development
…2608300839 chore(sync): record beta's ancestry on development
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Dexie refuses to run twice in one page: it throws "Two different versions of Dexie loaded in the same app". Nextcloud loads openregister's global integration script and hermiq's agent leaf on every page, alongside whichever leaf app you are in, so all three have to agree on one dexie. After the dependabot sweep on 2026-08-30 they did not. openregister resolved 4.4.4 while hermiq resolved 4.4.5, and the throw happened before the leaf app mounted, so every app page rendered as bare Nextcloud chrome with no content. This pins the floor at ^4.4.5 and regenerates the lock, matching the apps that were already there. Verified in the browser: the Dexie error is gone from the console and app pages render their navigation and content again.
…829) * fix(users): compare IUser and IGroup against null, not false `IUserManager::get()`, `IGroupManager::get()` and `createGroup()` all return `?IUser` / `?IGroup`. They never return `false`. Nine guards compared against `false`, so the comparison was ALWAYS true and the guard never fired -- a missing user fell straight through to $user->isEnabled() // on null $group->inGroup($user) // on null which is a fatal, not a skipped iteration. The activate/deactivate loops walk usernames read out of contact-person objects, so any username that no longer resolves to an account crashes the whole sweep instead of passing over that one entry. PHPStan flagged four of these (StackiqService 2271, 2281, 2379, 2389). Fixing only those would have left five identical defects in place that it happens not to narrow -- StackiqService 2095 and 2187, and ContactPersonHandler 790, 916 and 1417. All nine are the same class and all nine are fixed here. Also drops two dead comparisons in OrganizationHandler: `getLastLogin()` returns `int`, so `!== null` and `!== false` after `!== 0` can never be anything but true. Behaviour is unchanged; the `!== 0` test is the only one that ever did anything. Verified locally on the same commit CI failed on (deb9fa7): before: exit 1, "[ERROR] Found 9 errors" after: exit 0, "[OK] No errors" * fix(deps): take typescript 6, because typescript-eslint cannot parse TS 7 `Vue Quality (eslint)` has been red on development since 07:59 today, when #812 bumped typescript 5.9.3 -> 7.0.2. ESLint does not report lint findings; it refuses to start: Error: typescript-eslint does not support TS 7.0. at node_modules/typescript-eslint/dist/index.js:52:11 So the whole `eslint src` run aborts and nothing in src/ is linted at all. Upstream tracks TS >= 7.1 support in typescript-eslint#10940; it is not released. 6.0.3 is the newest release typescript-eslint can parse, so the app keeps a current compiler rather than being pinned back to the 5.x line it came from. Reverting to 5.9.3 would also work and gives up more. thematiq took the same bump and is unaffected -- its lint script is the literal no-op `echo 'No JavaScript to lint - thematiq is CSS/PHP only'`, so nothing there ever loads typescript-eslint. Those are the only two fleet apps on TS 7, so this is the single instance. Verified locally on deb9fa7: typescript 7.0.2 -> exit 1, "does not support TS 7.0", 0 files linted typescript 6.0.3 -> exit 0, 217 problems (0 errors, 217 warnings)
) * fix(sidebar): render the manifest page's sidebar alongside our own This app fills CnAppRoot's `#sidebar` slot, and Vue only renders a slot's fallback when the slot is ABSENT. So filling it suppressed `pages[].sidebarComponent` silently: no warning, no error, no sidebar. The ADR-110 flow sidebar was declared in the manifest, registered in registry.js and present in the bundle, and still never rendered. Nine apps in the fleet fill this slot and all nine were affected. The five that do not fill it rendered the flow sidebar correctly, which is what identified the cause. CnAppRoot now passes the resolved component to the slot (nextcloud-vue#857), so this renders both: our own rail, and whatever the routed manifest page asks for. Verified: npm run build exits 0. * chore(deps): @conduction/nextcloud-vue 2.24.3, which carries the sidebar slot prop 2.24.3 is the release that passes the resolved `pages[].sidebarComponent` into CnAppRoot's `#sidebar` slot. Without it the App.vue change in this branch is a no-op, because the slot prop it reads does not exist yet. Verified on filinq in the browser against the dev instance: the flow rail (Flow, Steps, Runs, Version, Publish, the trigger list) now renders next to the canvas, and the app's own sidebar still mounts alongside it.
Dependabot bumped `@vitest/coverage-v8` to 4 on its own in several apps and left `vitest` and `@vitest/ui` on 3. coverage-v8 4 peers vitest 4.1.11 exactly, so a split trio cannot resolve at all: that is what took launchpad's npm ci from green to red. The three move together here, to 4.1.11, which is the current published version of all of them. Verified: npm install and the app's own test script both exit 0.
The 'render the manifest page's sidebar alongside our own' commit landed
unformatted, and quality / Frontend Check (format) has been red on
development ever since:
prettier --check "**/*.{js,ts,vue,css,scss}"
[warn] src/App.vue
Eight apps took the same change and eight went red together. This is
prettier --write over the affected files and nothing else.
Verified: npm run format exits 0.
* fix(phpstan): guard against a null user, not against false IUserManager::get() returns IUser|null. Six call sites guarded it with $user !== false, which is ALWAYS TRUE for that type -- so the guard let a null straight through to $user->isEnabled() and $maintainerGroup->inGroup($user). A username that does not resolve would fatal, and the code reads as if it had been checked. phpstan reported it as 'Strict comparison using !== between OCP\IUser|null and false will always evaluate to true'. That is not a style complaint: the guard does not guard. StackiqService.php 6 sites (2095, 2187, 2271, 2281, 2379, 2389) Stackiq/ContactPersonHandler 1 site (1417) phpstan named three of them; grepping the type found six, all assigned from $userManager->get($username) a few lines above. OrganizationHandler also compared getLastLogin(), an int, against null and false. Both are always true and are removed; only !== 0 carries meaning. Verified: php -l clean on all three files. phpstan itself is not installed locally -- and note its composer script echoes 'PHPStan not installed, skipping...' rather than failing, so a local green there would have proved nothing. CI runs it for real. * style: Prettier the sidebar change here too Merged development in, which carried the unformatted src/App.vue from the sidebar commit. Same one-file fix as #836; whichever lands first makes the other a no-op.
…its class (#840) Three Playwright tests fail in suite-wizard.spec.ts (95 passed, 3 failed): Error: locator.fill: Element is not an <input>, <textarea>, <select> or [contenteditable] locator resolved to <div class="input-field input-field--label-outside vs__search"> The selector assumed ".vs__search" identifies vue-select's search input. The component library now also puts that class on a wrapper div, so .first() resolves to the wrapper and fill() correctly refuses it. The app never applies the class itself -- nothing in src/ mentions vs__search -- so this is the library's markup moving, not a defect here. The locator now demands an actual input and accepts the class sitting either on it or on an ancestor, so it survives the markup moving again: .suite-wizard-step2 input.vs__search, .suite-wizard-step2 .vs__search input Only the two .fill() sites were affected. catalog-ratings.spec.ts:176 uses the same ".vs__search" selector but only clicks it, which a wrapper div accepts, so it is left alone rather than changed on speculation. Why this surfaced only now: E2E runs on promotions into beta and main, not on pull requests into development, so a development-only change carrying this could not have shown it. Verified: prettier clean, and tsc reports no errors for this file.
* fix(e2e): .vs__search is a wrapper now, not the input
development is red on E2E with three failures, all the same error:
locator.fill: Element is not an <input>, <textarea>, <select>
or [contenteditable]
> 181 | await picker.fill(APP_A)
> 126 | await picker.fill(name)
@nextcloud/vue 9.10 reworked NcSelect -- 'fix(NcSelect): floating label
design using NcTextField' (#8570) -- and NcTextField renders a wrapper.
Observed on a live 9.11 build rather than inferred:
.vs__search -> <div class="input-field vs__search">
parent: div.vs__selected-options
inputInside: true
It used to BE the <input>; it is now a div that CONTAINS one. Targeting
the input inside restores the old meaning and reads correctly against
either version.
catalog-ratings.spec.ts is fixed too. It was not among the three
failures -- its test may not have reached that line -- but it holds the
identical selector and would fail the same way. Fixing the instance and
leaving the class is how this comes back.
Verified: the DOM shape was measured against a seeded launchpad-demo
instance on :8605 running a build against 9.11, not read off a
changelog.
* style: Prettier the selector change
* fix(l10n): regenerate all 38 browser catalogues, and add the check (#755)
* fix(l10n): regenerate all 38 browser catalogues, and add the check
Every locale catalogue was stale: l10n/<locale>.json is read server-side by PHP
`$l->t()`, while the browser only ever sees `OC.L10N.register(...)` from
l10n/<locale>.js, and a raw .json is not served from an app directory at all.
A key added to the JSON and forgotten in the JS renders in English for every
browser with nothing reporting it.
Ported keepiq's generator, which reads the app id from appinfo/info.xml rather
than hard-coding it — a catalogue registered under a stale id is silently
ignored by `t()`, which matters in a fleet that renames apps.
Backfilled first, then generated. This app had only 4 keys living solely in
.js, but the same step run blind cost opencatalogi 21,662 translations and
integriq 5,240, so the order is now fixed rather than judged per app.
Verified with the assertion that matters: comparing every rebuilt .js against
its pre-change version, keys DROPPED = 0. Not "no locale has fewer keys than
its json" — that comparison is blind to this failure, which is how dossiq
silently lost 631 real translations before I went back and re-checked.
nl.js carries 691 keys, de.js 278. check:l10n-js exits 0 after the build.
* fix(l10n): translate the 59 untranslated manifest strings
The catalogue fix in the previous commit made the browser able to READ Dutch.
This gives it Dutch to read.
59 strings: the getting-started tour, the nav, and the organisation, contract,
module and compliance surfaces. Domain terms as this catalogue's users have
them: leverancier, moduleversie, compliance-claim, onderbouwend bewijs,
audittrail.
GEMMA stays GEMMA, and the two catalogue descriptions keep the Dutch word the
domain actually uses: "Blader door de dienstencatalogus, gefilterd op
GEMMA-architectuurdimensie." The English original wrote "the service (dienst)
catalogue" precisely because dienst is the term of record.
Compliance stays Compliance. It is the word on the page in Dutch
organisations, and "naleving" would read as a translation of a label nobody
calls that.
Built on the same branch rather than a fresh one, because the generator these
translations need is in this PR and not yet on development.
Verified: 0 manifest strings left without Dutch, keys DROPPED = 0, nl.js
registers under "stackiq" with 750 keys and resolves "Organisations" ->
"Organisaties", check:l10n-js PASS.
* chore(release): 0.1.141-unstable.20260827025647 (#754)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* ci: run check:l10n-js, so the browser catalogues cannot drift again (#760)
This app already has the generator and the `check:l10n-js` script; it just
never ran them in CI. That is the whole difference between an app that stays
translated and one that quietly stops.
Adding a key to l10n/<locale>.json and forgetting the .js is invisible without
this check: the server renders Dutch, the browser renders English, and every
other check passes. `l10n/<locale>.json` is read server-side by PHP; the
browser only ever sees `OC.L10N.register(...)` from `l10n/<locale>.js`, loaded
as a script tag.
Measured across the fleet today: the apps running this check had zero drift,
while the four without it had accumulated 142, 329, 257 and 1,090 unreachable
entries between them. Same code, same generator. The check was the difference.
It also caught a translation PR that merged green having changed nothing a
browser loads, which is how the whole thing started.
Appended to the existing frontend-checks list rather than replacing it, so
every check this repo already runs still runs.
Verified before pushing: the workflow YAML still parses, and
`node scripts/build-l10n-js.js --check` exits 0 on this tree, so the new leg is
green on arrival rather than red for someone else to clean up.
* fix(deps): development cannot npm install (#762)
* fix(deps): development cannot npm install
* fix(deps): rebase the lock on development's, not a from-scratch resolve
The previous commit deleted package-lock.json before installing. That
turns a five-package pin into a full re-resolution: on pipelinq it moved
172 package versions, added 64 and removed 132, when five were intended.
One of those unintended moves broke boot. dexie went 4.4.4 -> 4.4.5, and
@conduction/nextcloud-vue's published dist BUNDLES its own dexie copy, so
the app loaded two and Dexie throws at module load:
pageerror: Two different versions of Dexie loaded in the same app:
4.4.5 and 4.4.4
The E2E boot gate caught it -- "the bundle loaded but rendered nothing" --
while build, lint, stylelint and unit tests were all green. A passing
build says nothing about whether the app mounts.
Starting from development's lock and letting npm move only what the
manifest forces cuts the change to 85/17/46 and leaves dexie alone.
Control: development's own E2E run is 309 passed / 1 failed with no
dexie pageerror and no boot-gate failure, so the breakage was mine.
---------
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
* perf(ci): one Code Quality run per commit, not two (#770)
* chore: untrack the build logs that were triggering full CI runs (#771)
Untracked:
build.log
changed.tsv
inst.log
lint.log
stylelint.log
t2.log
test_unit.log
* fix(release): name softwarecatalog as this app's previous App Store id (#764)
The App Store keys everything on the app id, so renaming <id> to stackiq made
it a brand new store entry starting from nothing. The release workflow
derives its version baseline from the git tags and info.xml of THIS repo,
neither of which knows anything about what shipped as softwarecatalog -- so the
version line restarts below it. filinq was about to publish 0.0.40 while
docudesk sits at 0.1.0-beta.3 on the store.
The store has no version ordering rule (_check_permission validates
existence and ownership only), so that uploads with a 200 and is then
never offered to anyone already on the higher version.
previous-app-id folds the old entry's published versions into the
baseline, so the renamed app picks the line up instead of restarting it.
* feat(demo): generated demo data for every schema (ADR-111) (#769)
* feat(demo): generated demo data for every schema (ADR-111 rules 1-2)
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 file gate-99 also runs:
`vendor/conduction/hydra-gates/scripts/lib/generate_mock_register.py`.
`--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:
occ openregister:descriptors:list --app=<app> --import=<register>
The setup-wizard step offering this on first run (ADR-111 rule 4, gate-100)
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 zero findings.
* fix(demo-data): attribute the descriptor to the app id, not the directory
`x-openregister.app` is what the descriptor inventory resolves a register to
an app by, and the generator was writing the CHECKOUT DIRECTORY name into it —
naming an app that does not exist. A cross-app id is a runtime lookup: it
finds nobody rather than erroring. The file is renamed to match and the
directory-named one removed, so exactly one mock descriptor remains.
---------
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
* chore(deps): hydra-gates 1.10, so the E2E skip-discipline gate can run (#777)
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
* fix(e2e): move the three fixme reasons where the gate can read them (#779)
The skip-discipline gate now runs here (hydra-gates 1.10.0, #777) and
reports three V3 findings — exclusions with no reason recorded:
1 workflows/crud-persistence.spec.ts
1 workflows/org-export-workflow.spec.ts
1 workflows/organisatie-crud.spec.ts
All three DO have a reason. None of them are where a machine can see it:
- crud-persistence : a twelve-line comment above the test
- org-export : in the test title, in parentheses
- organisatie-crud : in the test title, in parentheses
The gate reads report.json, and `test.fixme(title, fn)` records no
description there. A title is not an annotation, and a comment is
invisible to every tool.
So each reason moves into `test.fixme(true, '<reason>')` and the titles
lose the parenthetical, which is what a title should look like anyway.
Nothing about what runs changes: the same three tests are still excluded,
for the same documented reasons. They are simply attributable now, which
is the whole point of turning the gate on.
Verified: npm ci rc=0, npm run lint rc=0 (0 errors), prettier clean, and
`playwright test --list` compiles all 14 tests across the three files.
Part of ConductionNL/.github#609.
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
* refactor(manifest): the flow pages are an index and a flow (#775)
* 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.
* fix(deps): nextcloud-vue 2.20.1, whose manifest schema knows the flow page type
check:manifest failed on this PR with
[validate-manifest] schema.version: 2.25.0
Ajv validation: FAIL
- /pages/N/type must be equal to one of the allowed values (keyword=enum)
This PR declares a `type: "flow"` page. That type entered the manifest
schema at 2.26.0, which ships in @conduction/nextcloud-vue 2.20.1
(published today 20:33). The app locked 2.19.0, which carries schema
2.25.0 and has no `flow` in its enum.
Both files move. A caret alone changes nothing — package-lock.json is
what npm ci installs, and it pinned 2.19.0.
Verified by diffing this manifest's page types against each schema enum:
against 2.26.0 nothing is rejected; against 2.25.0 exactly the new type
is, which is the failure above.
* 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.
* test(e2e): Edit lands on the detail page for a schema that has one
nextcloud-vue 2.21 brings #806: a record whose schema has a same-schema DETAIL
page is edited there, not in a modal launched from the table — the modal renders
only the schema's flat scalars and cannot express a record whose related rows
live elsewhere. CnPageRenderer sets `editOpensDetail` from
`detailPageByRegisterSchema`.
Both edit blocks in this spec waited on a dialog that no longer opens from the
index. They now go through one helper that BRANCHES: dialog if it opens
directly, otherwise follow the navigation and click the detail page's header
Edit. Which route applies is a property of the schema, not of the test, so
branching is the accurate shape rather than a relaxed one — the helper still
returns a real, visible edit dialog and every assertion after it is unchanged.
---------
Co-authored-by: Ruben van der Linde <juan.claude@conduction.nl>
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
* chore(release): 0.1.143-unstable.20260828092138 (#781)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat(walkthrough): show where flows are edited, without asking anyone to build one (#765)
* feat(walkthrough): show where flows are edited, without asking anyone to build one
This app ships a Flows page and its getting-started tour never mentions it, so
the automation surface is reachable only by someone who already knows it is
there. Measured across the fleet: 19 apps declare a walkthrough, 12 ship a
flows page, and exactly one tour mentioned flows at all.
The stop is deliberately view-only. `allowManualNext` with a `route-match`
advance and `optional: true` means it points at the surface and lets the user
walk past it — nothing here gates the tour on having built a flow, which is the
difference between showing someone where something lives and making them do it
before they may continue.
`manifest.version` gets a minor bump because that is what `sinceVersion` is
compared against: a returning user whose recorded seen-version equals the old
manifest version would otherwise never be shown the new step.
* chore(l10n): translate the three strings the Flows walkthrough stop adds
Every string the tour puts on screen is user-facing. Dutch is written rather
than machine-produced; "Flows" stays "Flows", which is what the existing
catalogue already does for the term.
Worth noting for whoever picks this up: this app's `tests/l10n/check-l10n.js`
does NOT scan `src/manifest.json`, so it reported OK for these keys before they
existed in any catalogue. The gate passed because it did not look, not because
the strings were covered — every menu label, page title, setup step and tour
line in the manifest is currently outside its scope. Only dossiq's copy of the
checker reads the manifest.
* chore(l10n): rebuild the browser catalogues so the new strings actually ship
`l10n/*.json` is the source; `l10n/*.js` is what the browser loads. Adding the
three tour strings to the JSON left the built catalogues stale, so the strings
existed in the repo and reached no user — the exact shape of the 2026-08-24
finding where nine apps shipped 8,137 translations no browser ever received.
`npm run check:l10n-js` catches it (`Stale browser catalogue: l10n/en.js,
l10n/nl.js`), which is why the gate exists. This is `npm run l10n:build` and
nothing else.
* fix(walkthrough): target the flows entry by ROUTE, which is what resolves
CnWalkthrough.resolveTarget() looks a nav-item target up as
`[data-cn-route="<ref>"]`, and CnAppNav sets that attribute from `item.route`.
The step was authored with the MENU id (FlowsMenu), which matches nothing, so
it fell back to a centred anchorless coachmark instead of pointing at the entry.
Easy to miss because every tour step in this fleet that works today targets an
entry whose menu id happens to EQUAL its route (Cases, MyWork). FlowsMenu ->
Flows is the first place they differ.
Verified against the live DOM: [data-cn-route="Cases"] resolves,
[data-cn-route="FlowsMenu"] does not.
* fix(copy): no em-dash in the tour copy, per voice.md section 8
gate-96 (manifest-copy-style) caught it: "Em-dashes and double-dashes are AI
tells. Replace with a period, a comma, or a colon." The Flows stop's body ended
"...read and edit them — nothing to build now."; it now ends with a full stop
and a short sentence, which is what the rule asks for and reads no worse.
The English string is the l10n KEY, so the catalogues are re-keyed in the same
change and the browser .js rebuilt. Leaving the key behind would have made the
string untranslated in every locale while the catalogue still claimed to cover
it. The Dutch value drops its em-dash too, for the same reason the English one
does.
Verified per app with the script that app's own CI runs (test:l10n or
check:l10n), plus check:l10n-js, plus schema validation of the manifest.
* build(deps): take @conduction/nextcloud-vue 2.21.0 so the Flows stop anchors
The `see-flows` stop added by this PR targets a nav item in the SETTINGS
section. CnAppNav emitted `data-cn-route` on its main, child and footer
loops but not the settings one, so the stop resolved nothing — and
CnWalkthrough.armStep() SKIPS an optional step whose target is absent,
with no console error and nothing on screen:
const el = this.resolveTarget(this.step)
if (!el) { if (this.step.optional) { this.wt.skip(); return } }
`optional: true` is exactly what keeps the stop from forcing anyone to
build a flow, so the friendly authoring choice is also the one that fails
silently. Without this bump the step ships and reaches nobody.
The caret range does not decide this: `npm ci` installs from
package-lock.json, and that was pinned at 2.19.0, which predates the fix
(nextcloud-vue#811). 2.21.0 was verified by unpacking the published
tarball — data-cn-route appears 4 times, one inside the
`v-for="item in settingsItems"` template.
* test(e2e): Edit opens the detail page, so the dialog is one click further
@conduction/nextcloud-vue 2.21.0 makes the index row's Edit action navigate
to the record's detail page instead of opening a modal over the list. That
is the intended fleet rule: a record with its own detail page is edited
there, where its nested collections are reachable, rather than through a
dialog that shows only the schema's flat scalars.
These two tests asserted the old shape and so failed with "element(s) not
found" on `getByRole('dialog')` — the dialog was never going to appear,
because the click now routes. The edit form still exists; it is reached
from the detail page's Edit button (`cn-detail-page-edit`, gated on
canEditRecord).
Everything the tests actually check — the title field, the absence of a
scheduledDate format alert, the save round-trip — is unchanged.
* test(e2e): return to the index before asserting the edit landed in the list
Follow-up to the detail-page edit route. The first of these two tests
passed once the extra click was added; this one still failed, for a
different reason.
It edits the record and then asserts the new job title is rendered as a
row in the Table view. That assertion assumed the edit happened in a modal
OVER the list, so the list was still on screen when it ran. It is not: the
row's Edit action routes to the record's detail page, the save happens
there, and the page never goes back on its own.
Without the return trip the assertion runs against the detail page and
fails as "row not found" — which reads like the save not persisting rather
than the test standing on the wrong page.
---------
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Co-authored-by: Ruben van der Linde <juan.claude@conduction.nl>
* chore(release): 0.1.144-unstable.20260828095409 (#782)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* docs: add a local demo environment (#783)
Adds `stackiq-compose.yaml` and a setup page describing it.
The compose brings up Postgres and Nextcloud, installs openregister (required),
thematiq and integriq (optional) and stackiq 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>
* feat(setup): a wizard that offers the demo data this app already ships (#785)
* feat(setup): a wizard that offers the demo data this app already ships
This app ships lib/Settings/*_mock_register.json - a dataset generated from its
own schemas, conformant by construction, validated by the generator's --check -
and had no way for an operator to reach it. There was no setup wizard at all.
welcome -> demo-data -> done. Nothing app-specific is invented: the only action
is the demo-data import the descriptor already supports. A wizard that asked
questions the app does not act on would be worse than none, which is why there
are no configuration steps here yet.
completed is TRUE and the demo-data step is optional, so setup never gates the
app. skip-demo-data records its outcome just as installing does: since
nextcloud-vue 2.21 an OUTSTANDING OPTIONAL step opens the wizard over every
page (nextcloud-vue#806), so a step that can never be marked done is a dialog
that never closes - the defect buildiq was failing 37 E2E specs on.
Verified: manifest validates against schema 2.26.0, gate-100 PASS, routes.php
and both PHP files parse. The template was checked on launchpad against phpcs,
phpstan, psalm and phpmd - all clean.
* fix(setup): declare the endpoints' auth, and translate the wizard's strings
Two gate findings on the previous push.
gate-5 route-auth — status() and runAction() carried no auth attribute. The
docblock said 'admin-only by Nextcloud's default for an un-attributed method',
which is true and is not a declaration: the gate exists because a missing
attribute silently makes an endpoint unreachable, and a comment cannot be
checked by middleware. Both now carry
#[AuthorizedAdminSetting(Application::APP_ID)], placed DIRECTLY above the
declaration - gate-5 walks upward from the method and a long docblock between
attribute and declaration costs the attribute its visibility, which the gate
documents as a false FAIL it had to repair.
gate-102 manifest-l10n-coverage — the wizard's title and body strings had no
l10n/nl.json key, so a Dutch user would read them in English. Added, and the
browser catalogue rebuilt where the app ships one: nl.json alone is not enough,
because the browser reads nl.js.
The catalogue edit is insertions only, proven against the same change applied
structurally - an earlier attempt on another app re-serialised the whole file
(410 lines) before being reverted.
* fix(setup): authorize against the admin settings class, and test what it guards
`AuthorizedAdminSetting` takes a `class-string<IDelegatedSettings>`, not an
app id, so `Application::APP_ID` — a plain string — was rejected by phpstan.
The apps where this shipped green (larpinq, shillinq) already pass their admin
settings class; match them.
gate-47 and the coverage ratchet were both right to fail this. The change adds
an admin-authorized endpoint pair and ~364 lines of PHP with nothing behind
them. Two assertions are worth naming:
- a FAILED install must leave the step UNDECIDED. Recording the decision in
the catch block would close the step for an operator who asked for demo data
and received none.
- the object count comes from the FILE, not the importer's reply, so the
number reported is the number ASKED FOR.
Both verified by mutation on openregister: reversing each behaviour fails
exactly the test that claims to guard it. The e2e spec issues both calls from
inside the logged-in admin page, which is the only place that middleware can
be observed admitting a real session.
---------
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
* fix(e2e): settle the demo-data decision so the wizard stops masking clicks (#787)
The ADR-111 setup step is OPTIONAL, and CnAppRoot opens the non-gating wizard
as a full modal mask while any optional non-info step is reported not-done —
in every fresh browser context, so once per spec. Merging the setup wizard
therefore turned this app's whole E2E suite red without touching a single
spec: the call log reads "locator resolved to <button ...> - attempting click
action" with <ol class="cn-wizard-dialog__progress"> named as the interceptor.
The element was found; the click never landed.
SKIPPED rather than installed, because recording the DECISION is what closes
the wizard. Installing would push the app's demo dataset into every list the
suite asserts on, which changes what the other specs measure.
`demo-data-setup-step.spec.ts` exercises the install deliberately, in
isolation.
Uses the workflow's own exported credentials rather than this script's
internals, and is tolerant of a non-200: an app whose wizard has no demo-data
step answers 400, and that is not a seeding failure.
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
* chore(deps): @conduction/nextcloud-vue 2.21.0 -> 2.22.1 (#790)
2.22.1 carries the theme app-id fix (nextcloud-vue#840). CnAppRoot calls
useScopedTheme() with no slug, so this app resolved theme tokens, the
token-set catalogue and the contrast check through a hardcoded 'nldesign'
app id. thematiq is renaming to 'thematiq', and every path in that
composable degrades to default styling by design — so once a renamed build
is installed this app would render unthemed with nothing in any log.
The LOCK is what moves here. A caret range alone changes nothing, because
npm ci installs what package-lock.json pins.
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
* test(e2e): seed the walkthrough marker so the tour cannot intercept clicks (#793)
@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>
* chore(release): 0.1.145-unstable.20260829094614 (#791)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* ci(docs): publish from development, and retire the old hostname (#792)
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>
* chore(release): 0.1.146-unstable.20260829125943 (#795)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* chore(deps): @conduction/nextcloud-vue 2.22.1 -> 2.24.1 (#796)
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
* chore(deps): @conduction/nextcloud-vue 2.24.1 -> 2.24.2 (#798)
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
* chore(deps-dev): bump stylelint-config-html from 1.1.0 to 2.0.0 (#820)
Bumps [stylelint-config-html](https://github.com/ota-meshi/stylelint-config-html) from 1.1.0 to 2.0.0.
- [Release notes](https://github.com/ota-meshi/stylelint-config-html/releases)
- [Changelog](https://github.com/ota-meshi/stylelint-config-html/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ota-meshi/stylelint-config-html/compare/v1.1.0...v2.0.0)
---
updated-dependencies:
- dependency-name: stylelint-config-html
dependency-version: 2.0.0
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>
* chore(deps): bump webpack from 5.109.2 to 5.110.1 (#819)
Bumps [webpack](https://github.com/webpack/webpack) from 5.109.2 to 5.110.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Changelog](https://github.com/webpack/webpack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack/compare/v5.109.2...v5.110.1)
---
updated-dependencies:
- dependency-name: webpack
dependency-version: 5.110.1
dependency-type: direct:production
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>
* chore(deps-dev): bump @vitest/coverage-v8 from 3.2.7 to 4.1.11 (#818)
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>
* chore(deps-dev): bump @types/node from 20.19.43 to 26.4.0 (#817)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.19.43 to 26.4.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)
---
updated-dependencies:
- dependency-name: "@types/node"
dependency-version: 26.4.0
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>
* chore(deps): bump node-polyfill-webpack-plugin from 4.0.0 to 4.1.0 (#815)
Bumps [node-polyfill-webpack-plugin](https://github.com/Richienb/node-polyfill-webpack-plugin) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/Richienb/node-polyfill-webpack-plugin/releases)
- [Commits](https://github.com/Richienb/node-polyfill-webpack-plugin/compare/v4.0.0...v4.1.0)
---
updated-dependencies:
- dependency-name: node-polyfill-webpack-plugin
dependency-version: 4.1.0
dependency-type: direct:production
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>
* chore(deps-dev): bump nextcloud/ocp from 34.0.2 to 34.0.3 (#814)
Bumps [nextcloud/ocp](https://github.com/nextcloud-deps/ocp) from 34.0.2 to 34.0.3.
- [Commits](https://github.com/nextcloud-deps/ocp/compare/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>
* chore(deps): bump vue-draggable-plus from 0.2.7 to 0.6.1 (#813)
Bumps [vue-draggable-plus](https://github.com/Alfred-Skyblue/vue-draggable-plus) from 0.2.7 to 0.6.1.
- [Release notes](https://github.com/Alfred-Skyblue/vue-draggable-plus/releases)
- [Commits](https://github.com/Alfred-Skyblue/vue-draggable-plus/commits/0.6.1)
---
updated-dependencies:
- dependency-name: vue-draggable-plus
dependency-version: 0.6.1
dependency-type: direct:production
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>
* chore(deps-dev): bump typescript from 5.9.3 to 7.0.2 (#812)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.3 to 7.0.2.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.9.3...v7.0.2)
---
updated-dependencies:
- dependency-name: typescript
dependency-version: 7.0.2
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>
* chore(deps-dev): bump squizlabs/php_codesniffer from 3.13.6 to 4.0.4 (#811)
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](https://github.com/PHPCSStandards/PHP_CodeSniffer/compare/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>
* chore(deps-dev): bump phpcsstandards/phpcsextra from 1.5.0 to 1.5.1 (#810)
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](https://github.com/PHPCSStandards/PHPCSExtra/compare/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>
* chore(deps-dev): bump postcss-html from 1.8.1 to 2.0.0 (#809)
Bumps [postcss-html](https://github.com/ota-meshi/postcss-html) from 1.8.1 to 2.0.0.
- [Release notes](https://github.com/ota-meshi/postcss-html/releases)
- [Commits](https://github.com/ota-meshi/postcss-html/compare/v1.8.1...v2.0.0)
---
updated-dependencies:
- dependency-name: postcss-html
dependency-version: 2.0.0
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>
* chore(deps-dev): bump caniuse-lite from 1.0.30001806 to 1.0.30001810 (#807)
Bumps [caniuse-lite](https://github.com/browserslist/caniuse-lite) from 1.0.30001806 to 1.0.30001810.
- [Commits](https://github.com/browserslist/caniuse-lite/compare/1.0.30001806...1.0.30001810)
---
updated-dependencies:
- dependency-name: caniuse-lite
dependency-version: 1.0.30001810
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>
* chore(deps-dev): bump phpstan/phpstan from 2.2.8 to 2.2.9 (#806)
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>
* chore(deps): bump adbario/php-dot-notation from 3.3.0 to 3.5.0 (#805)
Bumps [adbario/php-dot-notation](https://github.com/adbario/php-dot-notation) from 3.3.0 to 3.5.0.
- [Release notes](https://github.com/adbario/php-dot-notation/releases)
- [Commits](https://github.com/adbario/php-dot-notation/compare/3.3.0...3.5.0)
---
updated-dependencies:
- dependency-name: adbario/php-dot-notation
dependency-version: 3.5.0
dependency-type: direct:production
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>
* chore(deps): bump actions/checkout from 4 to 7 (#804)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v7)
---
updated-dependencies:
- dependency-name: actions/checkout
dependency-version: '7'
dependency-type: direct:production
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>
* chore(deps-dev): bump phpmetrics/phpmetrics from 2.9.1 to 2.11.0 (#803)
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](https://github.com/phpmetrics/PhpMetrics/compare/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>
* chore(deps): bump twig/twig from 3.27.0 to 3.28.0 (#801)
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](https://github.com/twigphp/Twig/compare/v3.27.0...v3.28.0)
---
updated-dependencies:
- dependency-name: twig/twig
dependency-version: 3.28.0
dependency-type: direct:production
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>
* feat(flows): give the flow-detail canvas its sidebar (#789)
* feat(nav): a Flows surface in this app, on the shared page types
ADR-110 Decision 4. A flow is app-specific — it operates on this app's objects —
so the authoring surface belongs here rather than behind a deep link to another
app's list. The ENGINE stays single (ADR-065): these pages are a scoped view
onto OpenRegister's one native flow store, not a per-app store.
Two manifest pages and one settings entry, no component files: `type: "flows"`
and `type: "flow-detail"` are shipped page types in @conduction/nextcloud-vue
2.19.0, scoped by `config.app`.
Note the layout of the diff: entries are appended textually rather than by
reserialising the manifest. A `json.dump` round-trip rewrote pipelinq's file as
a 3,950-line diff for a 20-line addition — correct output, unreviewable change.
* build(deps): @conduction/nextcloud-vue 2.19.0 for the flows page types
Required by the manifest change: `type: "flows"` / `type: "flow-detail"` are
rejected by the compiled validator in earlier versions, and CI installs with
`npm ci` — so the LOCK is what decides, not the `^2.x` range. Several of these
locks were pinned many minors back, which is why some lockfile diffs are large:
npm restructures the nested tree (mostly @esbuild platform binaries under
@nextcloud/vue) to satisfy 2.19.0's peers. No direct dependency other than
@conduction/nextcloud-vue changes.
* fix(icons): register Sitemap, or the Flows entry renders with no icon
An icon name a manifest uses but src/icons.js does not register renders as
NOTHING — not a fallback (ADR-077 rule 3). The Flows menu entry this PR adds
uses `Sitemap`, and this app never registered it, so the entry would have
shipped with an empty icon slot.
Caught by gate-60 icon-vocabulary. I had checked `Sitemap` was registered in
dossiq and carried the assumption to the fleet; each app keeps its own icons.js,
and six of the twelve did not have it. The six failing gate runs were exactly
those six apps.
Both halves are required: the import alone is dead code, the registry entry
alone does not resolve.
* feat(flows): give the flow-detail canvas its sidebar
The manifest _note claimed the controls rendered in the NC app sidebar,
but the sidebarComponent field it described did not exist. Every
#/flows/:id -- and #/flows/new, the same route with the literal id -- drew
a bare canvas: savable and runnable, but with no way to name, describe,
trigger or step-edit the flow, because those controls all live in
CnFlowSidebar. Mirrors pipelinq#1490. ADR-110 Decision 4.
* chore(release): 0.1.147-unstable.20260830083652 (#822)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix(deps): align dexie on 4.4.5 so only one copy loads per page (#826)
Dexie refuses to run twice in one page: it throws "Two different versions of
Dexie loaded in the same app". Nextcloud loads openregister's global
integration script and hermiq's agent leaf on every page, alongside whichever
leaf app you are in, so all three have to agree on one dexie.
After the dependabot sweep on 2026-08-30 they did not. openregister resolved
4.4.4 while hermiq resolved 4.4.5, and the throw happened before the leaf app
mounted, so every app page rendered as bare Nextcloud chrome with no content.
This pins the floor at ^4.4.5 and regenerates the lock, matching the apps that
were already there.
Verified in the browser: the Dexie error is gone from the console and app pages
render their navigation and content again.
* fix: null guards on IUser/IGroup, and typescript 6 so eslint can run (#829)
* fix(users): compare IUser and IGroup against null, not false
`IUserManager::get()`, `IGroupManager::get()` and `createGroup()` all
return `?IUser` / `?IGroup`. They never return `false`. Nine guards
compared against `false`, so the comparison was ALWAYS true and the guard
never fired -- a missing user fell straight through to
$user->isEnabled() // on null
$group->inGroup($user) // on null
which is a fatal, not a skipped iteration. The activate/deactivate loops
walk usernames read out of contact-person objects, so any username that
no longer resolves to an account crashes the whole sweep instead of
passing over that one entry.
PHPStan flagged four of these (StackiqService 2271, 2281, 2379, 2389).
Fixing only those would have left five identical defects in place that it
happens not to narrow -- StackiqService 2095 and 2187, and
ContactPersonHandler 790, 916 and 1417. All nine are the same class and
all nine are fixed here.
Also drops two dead comparisons in OrganizationHandler: `getLastLogin()`
returns `int`, so `!== null` and `!== false` after `!== 0` can never be
anything but true. Behaviour is unchanged; the `!== 0` test is the only
one that ever did anything.
Verified locally on the same commit CI failed on (deb9fa7a):
before: exit 1, "[ERROR] Found 9 errors"
after: exit 0, "[OK] No errors"
* fix(deps): take typescript 6, because typescript-eslint cannot parse TS 7
`Vue Quality (eslint)` has been red on development since 07:59 today,
when #812 bumped typescript 5.9.3 -> 7.0.2. ESLint does not report lint
findings; it refuses to start:
Error: typescript-eslint does not support TS 7.0.
at node_modules/typescript-eslint/dist/index.js:52:11
So the whole `eslint src` run aborts and nothing in src/ is linted at
all. Upstream tracks TS >= 7.1 support in typescript-eslint#10940; it is
not released.
6.0.3 is the newest release typescript-eslint can parse, so the app keeps
a current compiler rather than being pinned back to the 5.x line it came
from. Reverting to 5.9.3 would also work and gives up more.
thematiq took the same bump and is unaffected -- its lint script is the
literal no-op `echo 'No JavaScript to lint - thematiq is CSS/PHP only'`,
so nothing there ever loads typescript-eslint. Those are the only two
fleet apps on TS 7, so this is the single instance.
Verified locally on deb9fa7a:
typescript 7.0.2 -> exit 1, "does not support TS 7.0", 0 files linted
typescript 6.0.3 -> exit 0, 217 problems (0 errors, 217 warnings)
* fix(sidebar): render the manifest page's sidebar alongside our own (#831)
* fix(sidebar): render the manifest page's sidebar alongside our own
This app fills CnAppRoot's `#sidebar` slot, and Vue only renders a slot's
fallback when the slot is ABSENT. So filling it suppressed
`pages[].sidebarComponent` silently: no warning, no error, no sidebar. The
ADR-110 flow sidebar was declared in the manifest, registered in registry.js
and present in the bundle, and still never rendered.
Nine apps in the fleet fill this slot and all nine were affected. The five that
do not fill it rendered the flow sidebar correctly, which is what identified
the cause.
CnAppRoot now passes the resolved component to the slot (nextcloud-vue#857), so
this renders both: our own rail, and whatever the routed manifest page asks
for.
Verified: npm run build exits 0.
* chore(deps): @conduction/nextcloud-vue 2.24.3, which carries the sidebar slot prop
2.24.3 is the release that passes the resolved `pages[].sidebarComponent` into
CnAppRoot's `#sidebar` slot. Without it the App.vue change in this branch is a
no-op, because the slot prop it reads does not exist yet.
Verified on filinq in the browser against the dev instance: the flow rail
(Flow, Steps, Runs, Version, Publish, the trigger list) now renders next to the
canvas, and the app's own sidebar still mounts alongside it.
* chore(deps): adopt vitest 4 (#834)
Dependabot bumped `@vitest/coverage-v8` to 4 on its own in several apps and
left `vitest` and `@vitest/ui` on 3. coverage-v8 4 peers vitest 4.1.11 exactly,
so a split trio cannot resolve at all: that is what took launchpad's npm ci
from green to red.
The three move together here, to 4.1.11, which is the current published version
of all of them.
Verified: npm install and the app's own test script both exit 0.
* style: run Prettier over the sidebar change (#836)
The 'render the manifest page's sidebar alongside our own' commit landed
unformatted, and quality / Frontend Check (format) has been red on
development ever since:
prettier --check "**/*.{js,ts,vue,css,scss}"
[warn] src/App.vue
Eight apps took the same change and eight went red together. This is
prettier --write over the affected files and nothing else.
Verified: npm run format exits 0.
* fix(phpstan): guard against a null user, not against false (#828)
* fix(phpstan): guard against a null user, not against false
IUserManager::get() returns IUser|null. Six call sites guarded it with
$user !== false, which is ALWAYS TRUE for that type -- so the guard let
a null straight through to $user->isEnabled() and
$maintainerGroup->inGroup($user). A username that does not resolve
would fatal, and the code reads as if it had been checked.
phpstan reported it as 'Strict comparison using !== between OCP\IUser|null
and false will always evaluate to true'. That is not a style complaint:
the guard does not guard.
StackiqService.php 6 sites (2095, 2187, 2271, 2281, 2379, 2389)
Stackiq/ContactPersonHandler 1 site (1417)
phpstan named three of them; grepping the type found six, all assigned
from $userManager->get($username) a few lines above.
OrganizationHandler also compared getLastLogin(), an int, against null
and false. Both are always true and are removed; only !== 0 carries
meaning.
Verified: php -l clean on all three files. phpstan itself is not
installed locally -- and note its composer script echoes 'PHPStan not
installed, skipping...' rather than failing, so a local green there would
have proved nothing. CI runs it for real.
* style: Prettier the sidebar change here too
Merged development in, which carried the unformatted src/App.vue from the
sidebar commit. Same one-file fix as #836; whichever lands first makes
the other a no-op.
* fix(e2e): target the picker's input, not the wrapper that now shares its class (#840)
Three Playwright tests fail in suite-wizard.spec.ts (95 passed, 3 failed):
Error: locator.fill: Element is not an <input>, <textarea>, <select>
or [contenteditable]
locator resolved to <div class="input-field input-field--label-outside
vs__search">
The selector assumed ".vs__search" identifies vue-select's search input.
The component library now also puts that class on a wrapper div, so
.first() resolves to the wrapper and fill() correctly refuses it. The app
never applies the class itself -- nothing in src/ mentions vs__search --
so this is the library's markup moving, not a defect here.
The locator now demands an actual input and accepts the class sitting
either on it or on an ancestor, so it survives the markup moving again:
.suite-wizard-step2 input.vs__search,
.suite-wizard-step2 .vs__search input
Only the two .fill() sites were affected. catalog-ratings.spec.ts:176 uses
the same ".vs__search" selector but only clicks it, which a wrapper div
accepts, so it is left alone rather than changed on speculation.
Why this surfaced only now: E2E runs on promotions into beta and main, not
on pull requests into development, so a development-only change carrying
this could not have shown it.
Verified: prettier clean, and tsc reports no errors for this file.
* fix(e2e): close the .vs__search class in catalog-ratings too (#841)
* fix(e2e): .vs__search is a wrapper now, not the input
development is red on E2E with three failures, all the same error:
locator.fill: Element is not an <input>, <textarea>, <select>
or [contenteditable]
> 181 | await picker.fill(APP_A)
> 126 | await picker.fill(name)
@nextcloud/vue 9.10 reworked NcSelect -- 'fix(NcSelect): floating label
design using NcTextField' (#8570) -- and NcTextField renders a wrapper.
Observed on a live 9.11 build rather than inferred:
.vs__search -> <div class="input-field vs__search">
parent: div.vs__selected-options
inputInside: true
It used to BE the <input>; it is now a div that CONTAINS one. Targeting
the input inside restores the old meaning and reads correctly against
either version.
catalog-ratings.spec.ts is fixed too. It was not among the three
failures -- its test may not have reached that line -- but it holds the
identical selector and would fail the same way. Fixing the instance and
leaving the class is how this comes back.
Verified: the DOM shape was measured against a seeded launchpad-demo
instance on :8605 running a build against 9.11, not read off a
changelog.
* style: Prettier the selector change
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Ruben van der Linde <rubenvdlinde@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Co-authored-by: Ruben van der Linde <juan.claude@conduction.nl>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Comment on lines
+17
to
+44
| uses: ConductionNL/.github/.github/workflows/documentation.yml@main | ||
| # A reusable workflow receives NO secrets by default. Without this block the | ||
| # callee's publish step finds CF_API_TOKEN empty, skips itself on its own | ||
| # `if:` guard, and the run finishes GREEN having changed nothing -- the | ||
| # failure that left the fleet's docs sites on May builds. The names are the | ||
| # same on both sides; the org secrets really are CF_API_TOKEN/CF_ACCOUNT_ID. | ||
| secrets: | ||
| CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} | ||
| CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} | ||
| with: | ||
| cname: stackiq.conduction.nl | ||
|
|
||
| # softwarecatalog.conduction.nl is the retired hostname. It stays in docs-hosts so | ||
| # existing links keep resolving, and canonical-host below turns it into a | ||
| # 301 rather than a second live copy of every page. | ||
| # EVERY host this worker answers on, in FULL: wrangler reconciles the | ||
| # worker's triggers against this list, so a host left out is REMOVED and | ||
| # goes dark. | ||
| docs-hosts: softwarecatalog.conduction.nl,stackiq.conduction.nl | ||
| # The ONE hostname this site is reached on. Every other host in | ||
| # docs-hosts answers 301 to the same path here. Before this, both hostnames | ||
| # served identical content and the retired name stayed as discoverable | ||
| # as the current one. | ||
| canonical-host: stackiq.conduction.nl | ||
| # PINNED. Deriving the name is how a deploy goes green and reaches | ||
| # nobody: wrangler creates the derived worker and publishes there while | ||
| # the custom domains keep routing to the real one. | ||
| worker-name: softwarecatalog-docs |
main held 7 commit(s) beta did not. Merged with -s ours: beta's tree is kept BYTE FOR BYTE and only the ancestry is recorded, so the beta -> main promotion stops conflicting on files where beta is simply newer. Not brought over -- beta is hundreds of commits ahead of main, so these are the OLDER copies, and several are dead Forgejo/Codeberg CI that development deliberately removed: .github/dependabot.yml .github/workflows/beta-release.yaml .github/workflows/code-quality.yml .github/workflows/documentation.yml .github/workflows/pr-check.yaml .github/workflows/pull-request-from-branch-check.yaml .github/workflows/pull-request-lint-check.yaml .github/workflows/push-development-to-beta.yaml .github/workflows/release-beta.yaml .github/workflows/release-stable.yaml .github/workflows/release-unstable.yaml .github/workflows/release-workflow.yaml .github/workflows/sync-beta.yaml .github/workflows/sync-dev.yaml .github/workflows/unstable-release.yaml appinfo/info.xml lib/Portal/PortalContributionProvider.php openspec/changes/portal-contribution/.openspec.yaml openspec/changes/portal-contribution/design.md openspec/changes/portal-contribution/proposal.md openspec/changes/portal-contribution/specs/portal-contribution/spec.md openspec/changes/portal-contribution/tasks.md openspec/config.yaml openspec/schemas/conduction/schema.yaml openspec/schemas/conduction/templates/contract.md openspec/schemas/conduction/templates/design.md openspec/schemas/conduction/templates/discovery.md openspec/schemas/conduction/templates/migration.md openspec/schemas/conduction/templates/proposal.md openspec/schemas/conduction/templates/spec.md openspec/schemas/conduction/templates/tasks.md openspec/schemas/conduction/templates/test-plan.md openspec/specs/README.md openspec/specs/portal-contribution/spec.md tests/Unit/Portal/PortalContributionProviderTest.php
Release: merge development into beta
Contributor
Quality Report — ConductionNL/stackiq @
|
| 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 17:05 UTC
Download the full PDF report from the workflow artifacts.
Contributor
Quality Report — ConductionNL/stackiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-vue-demi | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 130/130 | |||
| npm | ✅ | ✅ 713/713 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-30 17:33 UTC
Download the full PDF report from the workflow artifacts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stable release:
betaholds 1026 commit(s)maindoes not.Merged with
--merge, never--squash. Squashing a promotion rewrites the carried commits into onebetadoes not contain, so the branches diverge again immediately and main's own commits read as reverted.A failing
… / releasecheck on this pull request is the App Store publish step, not a quality gate. Eight fleet apps cannot publish today: seven have no signing key, and thematiq's certificate carries its old app id (Nextcloud issues one certificate per id, CN = the id). The GitHub release and tag are still created. Every other check must be green for this to merge.