Skip to content

Commit aaab4f3

Browse files
github-actions[bot]rubenvdlindeConduction Release Botjuanclaude-conductiondependabot[bot]
authored
Release: merge development into beta (#758)
* 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>
1 parent 76e0668 commit aaab4f3

77 files changed

Lines changed: 26061 additions & 13119 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/code-quality.yml

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,36 +27,47 @@ on:
2727
# `enable-coverage-guard` was switched on here in the previous commit; without
2828
# this trigger its push-side half would have been dead on arrival.
2929
push:
30-
# An ALLOW-LIST of branch prefixes is a gate with a hole in it, and the
31-
# hole is SILENT: a branch matching nothing gets no CI at all, and its last
32-
# visible status is whatever it inherited — indistinguishable, on every
33-
# dashboard, from a branch that passed.
30+
# DEFAULT BRANCHES ONLY. `pull_request` below carries every other branch.
3431
#
35-
# Two live examples, both found 2026-08-14: `perf/**` was uncovered in
36-
# openconnector, where a merge carrying unresolved conflict markers and 84
37-
# failing tests was pushed and nothing ran; and `feat/**` was uncovered in
38-
# openregister — note the list said `feature/**`, so every branch anyone
39-
# named `feat/...` had been running unchecked.
32+
# This was an allow-list of branch prefixes, and that was a gate with a
33+
# SILENT hole: a branch matching nothing got no CI at all, and its last
34+
# visible status was whatever it inherited — indistinguishable, on every
35+
# dashboard, from a branch that passed. Two live examples, both found
36+
# 2026-08-14: `perf/**` was uncovered in openconnector, where a merge
37+
# carrying unresolved conflict markers and 84 failing tests was pushed and
38+
# nothing ran; and `feat/**` was uncovered in openregister, because the
39+
# list said `feature/**`.
4040
#
41-
# Prefixes are added rather than replaced with `**` because this workflow is
42-
# expensive (PHPUnit matrix, Newman, Playwright). The fast structural checks
43-
# DO run on `**` — see merge-hygiene.yml, added in the same change.
41+
# The comment that stood here said adding prefixes was not the durable fix,
42+
# and that the durable fix was to let the pull_request trigger gate it.
43+
# THIS IS THAT CHANGE.
4444
#
45-
# ⚠️ Adding prefixes is not the durable fix; the next invented one is
46-
# uncovered again. The durable fix is branch protection requiring a PR into
47-
# development, which the pull_request trigger below already gates correctly.
45+
# What forced it now: a push to a branch with an open PR ran the SAME 34
46+
# jobs TWICE on the same commit. `concurrency` cannot dedupe them — the
47+
# group is suffixed by event name deliberately (.github#540: a
48+
# default-branch push carries jobs a PR run does not, and a dispatch must
49+
# not be cancellable by a standing release PR), so the two events sit in
50+
# different lanes BY DESIGN and both run to completion. Measured fleet-wide
51+
# 2026-08-25..27, 659 of 2,106 Code Quality runs were that duplicate — 31%
52+
# of the fleet's most expensive workflow, re-deciding a commit another run
53+
# was already deciding. The account ceiling is 60 concurrent jobs (Team
54+
# plan); the fleet was measured at 53 running with 1,528 jobs queued behind
55+
# them, the oldest run 7 hours old and not yet started.
56+
#
57+
# NO BRANCH LOSES ITS FLOOR. merge-hygiene.yml runs on `'**'` — every
58+
# branch anyone pushes, no prefix list to forget — and it is the check
59+
# `development` actually requires. That is the smoke alarm; this workflow
60+
# is the fire brigade and belongs on the PR. Of 668 feature-branch push
61+
# runs in that window, only NINE were on a branch with no PR run beside
62+
# them.
63+
#
64+
# The default branches STAY: their push runs are not duplicates, they are
65+
# the only carrier of Coverage Baseline Check, SBOM and Features Extract,
66+
# none of which run on a pull_request event.
4867
branches:
4968
- main
5069
- beta
5170
- development
52-
- feature/**
53-
- feat/**
54-
- bugfix/**
55-
- hotfix/**
56-
- perf/**
57-
- refactor/**
58-
- chore/**
59-
- fix/**
6071
pull_request:
6172
branches: [main, beta, development]
6273
# Same family of defect as the missing `push:` above, one step further along:
@@ -226,7 +237,7 @@ jobs:
226237
# renders the English source inside an otherwise translated form, silently.
227238
# The fleet had 30,459 such strings, so this records the current count and
228239
# fails only when it GROWS — burning it down stays an ordinary PR.
229-
frontend-checks: '["check:manifest", "check:vue-demi", "test:l10n", "format", "check:schema-l10n"]'
240+
frontend-checks: '["check:manifest", "check:vue-demi", "test:l10n", "format", "check:schema-l10n", "check:l10n-js"]'
230241

231242
# ── Coverage ratchet ─────────────────────────────────────────────────
232243
# `enable-coverage-guard` defaults to FALSE, which is why both
Lines changed: 25 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,44 @@
11
name: Documentation
22

3+
# Publishes the docs site to the Cloudflare Worker that serves it.
4+
#
5+
# TRIGGERS ON `development`, NOT ON A `documentation` BRANCH. This file used to
6+
# listen on a branch called `documentation`; nobody has pushed to one since
7+
# 2026-05-25, so the site simply stopped being rebuilt while every docs change
8+
# merged to development satisfied its review and published nothing.
39
on:
4-
# `development` is where the work lands. This used to trigger on a
5-
# `documentation` branch that exists but nobody updates — so the workflow
6-
# was green and idle while the live site aged. Measured today:
7-
# softwarecatalog.conduction.nl and stackiq.conduction.nl both still serve
8-
# the pre-rename "SoftwareCatalog" title while docs/docusaurus.config.js
9-
# says 'Stackiq'.
1010
push:
1111
branches: [development]
1212
pull_request:
1313
branches: [development]
1414

1515
jobs:
1616
deploy:
17-
# Permission CEILING for the called documentation workflow, not a grant.
18-
# The callee has three jobs and this block is their UNION, which is what the
19-
# token already resolves to today:
20-
# build contents: read
21-
# deploy contents: write (peaceiris/actions-gh-pages pushes to gh-pages)
22-
# image contents: read, packages: write (buildx push to GHCR)
23-
#
24-
# ⚠️ `packages: write` is load-bearing at RUNTIME, not merely statically:
25-
# the callee's `build-image` input DEFAULTS TO TRUE, so the `image` job
26-
# really does run on a push to `development` and really does push to
27-
# GHCR. Dropping it 403s that push.
28-
#
29-
# It would be required even if that job were disabled, because GitHub
30-
# validates the callee's DECLARED job permissions against this ceiling
31-
# before dispatch — including for jobs an `if:` will skip — so a too-low
32-
# ceiling makes the whole call fail to start rather than run with less.
33-
permissions:
34-
contents: write
35-
packages: write
3617
uses: ConductionNL/.github/.github/workflows/documentation.yml@main
37-
# A reusable workflow receives NO secrets by default, and the
38-
# `permissions:` block above governs the TOKEN, not the secrets. Without
39-
# this block `secrets.CF_API_TOKEN` is empty inside the callee, its
40-
# "Publish to the Cloudflare Worker" step skips itself on its own guard,
41-
# and the run finishes GREEN having written only gh-pages — which nothing
42-
# serves. The live site never changes and no check goes red to say so.
43-
#
44-
# Mapped explicitly rather than `secrets: inherit`, because `inherit`
45-
# hands the callee EVERY secret this repo holds — signing cert and key,
46-
# appstore token, deploy keys — for the sake of two Cloudflare values.
47-
# This way only those two cross the boundary.
48-
#
49-
# The exposure above is the ONLY reason for the explicit mapping. The
50-
# names are the same on both sides: the org secrets really are
51-
# `CF_API_TOKEN` / `CF_ACCOUNT_ID` — the names ConductionNL/.github's own
52-
# deploy-docs.yml reads directly, and the names the callee declares under
53-
# `workflow_call.secrets`.
54-
#
55-
# This block first read `secrets.CLOUDFLARE_API_TOKEN` /
56-
# `secrets.CLOUDFLARE_ACCOUNT_ID`, which are not secrets anywhere in this
57-
# org. Mapping from a name that does not exist is NOT an error — it
58-
# yields an empty string — so the callee's publish step would have skipped
59-
# itself on its own guard and the run would still have finished green.
60-
# Measured on planninq run 32760529026, where that spelling did merge:
61-
# "Publish to the Cloudflare Worker" SKIPPED, the log showing
62-
# `CF_API_TOKEN:` with no value.
18+
# A reusable workflow receives NO secrets by default. Without this block the
19+
# callee's publish step finds CF_API_TOKEN empty, skips itself on its own
20+
# `if:` guard, and the run finishes GREEN having changed nothing -- the
21+
# failure that left the fleet's docs sites on May builds. The names are the
22+
# same on both sides; the org secrets really are CF_API_TOKEN/CF_ACCOUNT_ID.
6323
secrets:
6424
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
6525
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
6626
with:
67-
# `stackiq.conduction.nl` resolves as of 2026-08-23 — attached as a
68-
# second custom domain on the SAME `softwarecatalog-docs` worker that
69-
# serves `softwarecatalog.conduction.nl`. Both hosts answer 200.
70-
# (The comment that used to sit here said the new host answered 000;
71-
# that was true when it was written and is not any more.)
7227
cname: stackiq.conduction.nl
7328

29+
# softwarecatalog.conduction.nl is the retired hostname. It stays in docs-hosts so
30+
# existing links keep resolving, and canonical-host below turns it into a
31+
# 301 rather than a second live copy of every page.
32+
# EVERY host this worker answers on, in FULL: wrangler reconciles the
33+
# worker's triggers against this list, so a host left out is REMOVED and
34+
# goes dark.
7435
docs-hosts: softwarecatalog.conduction.nl,stackiq.conduction.nl
75-
76-
# The worker that ALREADY holds both custom domains. Without this the
77-
# callee derives the name from `cname` — `stackiq-docs` — which does not
78-
# exist. Deploying that would create a SECOND worker while both custom
79-
# domains keep routing to `softwarecatalog-docs`: every deploy green,
80-
# reaching nobody. Renaming the worker is a Cloudflare-side move, not
81-
# something this file can perform.
36+
# The ONE hostname this site is reached on. Every other host in
37+
# docs-hosts answers 301 to the same path here. Before this, both hostnames
38+
# served identical content and the retired name stayed as discoverable
39+
# as the current one.
40+
canonical-host: stackiq.conduction.nl
41+
# PINNED. Deriving the name is how a deploy goes green and reaches
42+
# nobody: wrangler creates the derived worker and publishes there while
43+
# the custom domains keep routing to the real one.
8244
worker-name: softwarecatalog-docs

.github/workflows/l10n.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
# Observed fleet-wide n=225: median 0.1 min, max 1.2 min. Bound loosely at 15.
1717
timeout-minutes: 15
1818
steps:
19-
- uses: actions/checkout@v4
19+
- uses: actions/checkout@v7
2020
- uses: actions/setup-node@v4
2121
with:
2222
node-version: "22"

.github/workflows/merge-hygiene.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
name: Conflict markers and PHP syntax
3434
runs-on: ubuntu-latest
3535
steps:
36-
- uses: actions/checkout@v4
36+
- uses: actions/checkout@v7
3737

3838
# Conflict markers, anywhere in the tree we author. A marker means a merge
3939
# was committed half-finished; every downstream signal from that commit is

.github/workflows/release.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ concurrency:
2727
group: release-${{ github.ref_name }}
2828
cancel-in-progress: false
2929

30+
# `previous-app-id` is the App Store id this app shipped under before the
31+
# rename. The store keys everything on the id, so `stackiq` is a brand new
32+
# entry starting from nothing -- without this the version line restarts
33+
# BELOW what the app already published as `softwarecatalog`, and the store accepts
34+
# that with a 200 and then never offers it to anyone. Drop the input once
35+
# the `softwarecatalog` entry is retired.
3036
jobs:
3137
unstable:
3238
if: github.ref == 'refs/heads/development'
@@ -47,6 +53,7 @@ jobs:
4753
with:
4854
release-type: unstable
4955
app-name: stackiq
56+
previous-app-id: softwarecatalog
5057
secrets: inherit
5158

5259
beta:
@@ -59,6 +66,7 @@ jobs:
5966
with:
6067
release-type: beta
6168
app-name: stackiq
69+
previous-app-id: softwarecatalog
6270
secrets: inherit
6371

6472
stable:
@@ -71,4 +79,5 @@ jobs:
7179
with:
7280
release-type: stable
7381
app-name: stackiq
82+
previous-app-id: softwarecatalog
7483
secrets: inherit

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,11 @@ tests/e2e/test-results/
7878
.stale/
7979
/.e2e-state/
8080
.phpunit.cache
81+
82+
# Build and test transcripts written at the repo ROOT by local tooling. These
83+
# were tracked, so every local build produced a commit-shaped diff, and every
84+
# such commit triggered the full Code Quality suite — 34 jobs deciding a
85+
# changed log file. Root-anchored on purpose: .claude/skills/*/examples/*.log
86+
# are gate FIXTURES, not artefacts, and must stay tracked.
87+
/*.log
88+
/changed.tsv

appinfo/info.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Vrij en open source onder de EUPL-licentie.
5050
5151
**Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op via sales@conduction.nl.
5252
]]></description>
53-
<version>0.1.142-beta.20260820200443</version>
53+
<version>0.1.147-unstable.20260830083652</version>
5454
<licence>EUPL-1.2</licence>
5555
<author mail="info@conduction.nl" homepage="https://www.conduction.nl/">Conduction</author>
5656
<namespace>Stackiq</namespace>

appinfo/routes.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
return [
1919
'routes' => [
2020
// Dashboard routes
21+
// First-time setup wizard (ADR-042) - the standard CnSetupWizard contract.
22+
['name' => 'setup#status', 'url' => '/api/setup/status', 'verb' => 'GET'],
23+
['name' => 'setup#runAction', 'url' => '/api/setup/action/{actionId}', 'verb' => 'POST', 'requirements' => ['actionId' => '[a-z0-9\\-]+']],
2124
['name' => 'dashboard#page', 'url' => '/', 'verb' => 'GET'],
2225
['name' => 'dashboard#index', 'url' => '/api/dashboard', 'verb' => 'GET'],
2326

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191
"phpstan/phpstan": "^2.0",
9292
"phpunit/phpunit": "^10.5",
9393
"roave/security-advisories": "dev-latest",
94-
"squizlabs/php_codesniffer": "^3.9",
94+
"squizlabs/php_codesniffer": "^4.0",
9595
"vimeo/psalm": "^5.26"
9696
},
9797
"config": {

0 commit comments

Comments
 (0)