Release: merge development into beta - #18
Conversation
Release: promote beta to stable — initial CI pipeline and app scaffold
Merge beta into main
…uctors Issues caught by every code review on apps built from this template: - <licence>agpl</licence> → <licence>eupl</licence> - OC.requestToken → getRequestToken() from @nextcloud/auth - Added SPDX-License-Identifier: EUPL-1.2 to all PHP/JS/Vue files - Constructor properties: private → private readonly
The app store only recognises agpl/apache/mit — not eupl. Source files use SPDX EUPL-1.2 headers (the actual licence). info.xml uses agpl for store compatibility only.
…uth dep Fixes from planix: correct .phphunit → .phpunit typo, remove duplicate glob patterns, and add @nextcloud/auth for getCurrentUser() support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Quality Report — ConductionNL/nextcloud-app-template @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ❌ | ✅ 215/215 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ |
Quality workflow — 2026-04-16 15:18 UTC
Download the full PDF report from the workflow artifacts.
Replace the individual conduction symlink inside openspec/schemas/ with a single symlink at openspec/schemas pointing to hydra schemas directory. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…asset only) (#24) The central Quality workflow (ConductionNL/.github#34) now publishes SBOMs exclusively as release assets — see SECURITY.md "Software Bill of Materials". This PR cleans up the per-app remnants: - delete .github/workflows/sbom.yml (the central job replaces it) - delete the checked-in sbom.cdx.json (release asset is the source of truth) - gitignore SBOM files so future generations don't accidentally land in repo Stable URL for clients: https://github.com/ConductionNL/nextcloud-app-template/releases/latest/download/sbom.cdx.json Co-authored-by: SBOM Cleanup <ops@conduction.nl>
Path-based codeowner mapping per the OR-abstraction-audit follow-up (2026-05-03). PRs that touch each domain auto-request review from the matching owners; first-to-approve unblocks per the org ruleset.
Wires the ConductionNL bundling pattern from ADR-004 (Build / bundling) into the template so apps cloned from this repo get a working dashboard widget out of the box and never trip the appName/devtool/duplicate-framework pitfalls that landed across opencatalogi/pipelinq/procest/docudesk. What is added: - webpack.config.js: optimization.splitChunks with stable-filename shared chunks for Vue + @nextcloud/vue + pinia + icons + @conduction/nextcloud-vue. Each entry-point keeps only entry-specific code; shared chunks load once. - lib/Dashboard/ExampleWidget.php: minimal IWidget. load() attaches shared chunks BEFORE the per-widget bundle (vendor → nc-vue → widget). Comments explain why and reference ADR-004. - src/exampleWidget.js: webpack entry that registers the Vue renderer via OCA.Dashboard.register. Hard-coded id matches Widget::getId() from PHP. - src/views/widgets/ExampleWidget.vue: minimal NcDashboardWidget that fetches /api/items via @nextcloud/axios with try/catch + graceful empty state. - AppInfo/Application.php: registerDashboardWidget(ExampleWidget::class). - README: 'Adding a dashboard widget' how-to listing the 5 registration points and pointing at ADR-004 for the full rationale. Apps that don't need a dashboard widget delete: - lib/Dashboard/ + src/exampleWidget.js + src/views/widgets/ - the registerDashboardWidget(...) line in Application.php - the exampleWidget entry in webpack.config.js The splitChunks block is harmless with only main + adminSettings entries (produces small shared chunks that two entries reuse) and starts paying off the moment a widget is added.
Spec the canonical Tier-4 scaffolding for the JSON manifest renderer pattern in nextcloud-app-template. Codifies hydra ADR-024's "new apps MUST adopt the manifest from inception" requirement at the source — the template — rather than retrofitting per app. Includes proposal, design, tasks, and 10 REQ-TMV1-* requirements covering manifest contents, bootstrap pattern, registry contract, webpack alias, dependency floor, and the manifest-first README quickstart.
Add the canonical template manifest with 4 example pages (one each of type dashboard / index / detail / settings) and 4 menu entries. Declares openregister as the default dependency. Settings page demonstrates the version-info rich-section widget. Add src/customComponents.js as the empty-by-default registry contract with a single example placeholder (CustomExample) so the registry's role is visible to first-time cloners. The manifest does NOT reference CustomExample by default — it only ships as documentation. Add tests/validate-manifest.js (copied from decidesk's reference) for Ajv-based schema validation. Wired up via npm run check:manifest. Trim l10n/en.json to a clean baseline aligned with the new manifest strings; add an empty l10n/en_US.json placeholder.
Adopt decidesk's mount-survivable bootstrap pattern (commits 50e4df7c
+ 866ff132) as the template default:
main.js:
- Import bundledManifest from './manifest.json' and customComponents
from './customComponents.js'.
- Build vue-router routes from manifest.pages[*].{id,route} via a
routesFromManifest() helper that uses a shallow-cloned
CnPageRenderer ({ ...CnPageRenderer }) — required because Vue 2's
Vue.extend() mutates the component options object with a _Ctor
cache, which throws against the lib's frozen barrel exports.
- Pass shallow-cloned defaultPageTypes and customComponents to
App.vue as props.
- Mount on #content immediately, NOT inside loadTranslations'
callback (NC dev installs commonly 404 the /l10n/<locale>.json
route, which would silently kill boot). Translation load is
fire-and-forget; strings fall back to English on miss.
App.vue:
- Mount <CnAppRoot> with manifest + customComponents + pageTypes
props, app-id, translateForApp closure, and permissions array.
- Provide an objectSidebarState reactive channel via provide() and
mount <CnObjectSidebar> in the #sidebar slot — the standard
pattern for CnDetailPage → host-rendered sidebar.
settings.js + AdminRoot.vue:
- Keep the Nextcloud admin app-settings webpack entry-point (a
distinct surface from the manifest's type:'settings' SPA page).
Replace the deleted views/settings/AdminRoot.vue with a minimal
placeholder NcSettingsSection that documents the divergence.
Delete the legacy shell:
- src/router/index.js (routes built from manifest at boot).
- src/navigation/MainMenu.vue (CnAppNav replaces it).
- src/views/Dashboard.vue (manifest type:'dashboard' replaces it).
- src/views/settings/ (manifest type:'settings' replaces it).
…README
package.json:
- @conduction/nextcloud-vue ^0.1.0-beta.3 → ^1.0.0-beta.12 (the
published lib version with the Vue.extend frozen-component fix).
- @nextcloud/router ^2.0.1 → ^3.1.0 — required by @nextcloud/vue
8.37+ (NcDashboardWidget / NcAvatar import getBaseUrl, missing
from router 2.x).
- Add ajv ^8.17.1 + ajv-formats ^3.0.1 devDependencies for the
manifest validator.
- Add scripts.check:manifest → node tests/validate-manifest.js
(satisfies the fleet adoption spec's build-time validation gate).
webpack.config.js:
- Add @nextcloud/axios$ alias to force the lib's transitive axios
import to resolve to the app's installed copy (decidesk pattern,
commit ed34703c). Without the $ exact-match suffix webpack walks
up to the lib's own node_modules and loads a second axios
instance, breaking shared interceptors / CSRF tokens.
eslint.config.js:
- Override no-console / n/no-process-exit / n/shebang for the
tests/validate-manifest.js Node CLI script.
README.md:
- Lead with manifest-first messaging in the intro paragraph and
in the OpenRegister callout.
- Add an "Adding a page (manifest-first)" section that documents
the page-type table and tells cloners to edit src/manifest.json
rather than writing per-page Vue files. Custom Vue components
are only required for type:"custom" pages.
- Add a "Renaming the app" section listing the files where the
app id appears (the manifest itself does NOT carry the id).
- Update the directory-structure diagram to reflect the new
layout (manifest.json, customComponents.js, no router/, no
navigation/).
feat: scaffold the JSON manifest renderer pattern as the template default (template-manifest-v1)
…copy with placeholders The SCSS webpack rule was added to webpack.config.js in a prior PR (commit 209224e "chore: bump nextcloud-vue + router, add axios alias"), but the actual sass + sass-loader packages were never added to devDependencies. Result: running 'npm run build' on a fresh clone of the development branch produces a 'Module parse failed: Unexpected token' error whenever nextcloud-vue components ship <style lang=scss> blocks. Fix: - package.json devDependencies: add sass ^1.99.0, sass-loader ^16.0.8 (matching the versions already in opencatalogi + decidesk) - package-lock.json regenerated via 'npm install --package-lock-only' Separately, appinfo/info.xml currently ships marketing copy for the template itself (Nextcloud App Template, A template for creating new Nextcloud apps, plus a 13-line feature-list description). This copy survives '/app-create' scaffolding because the placeholder substitution phase only rewrites identifier-shaped tokens (app-template -> {APP_ID}, AppTemplate -> {APP_NAMESPACE}). Result: every newly scaffolded app ships with the template's own description until a human remembers to rewrite it. Fix: - appinfo/info.xml: replace summary + description content with {APP_NAME}, {APP_SUMMARY}, {APP_DESCRIPTION} placeholders so the /app-create skill can auto-substitute them from openspec/app-config.json during scaffolding. Companion PR in ConductionNL/market-intelligence updates the .claude/skills/app-create placeholder-replacement-guide.md so the new placeholders get processed automatically.
…-placeholders fix: add sass + sass-loader to devDeps + use placeholders for info.xml marketing copy
… deps PR #28 (merge 4601d2c) shipped a package-lock.json that was missing 11 transitive dependencies of pinia (devtools-kit, devtools-shared, mitt, perfect-debounce, speakingurl, superjson, copy-anything, is-what, rfdc, plus a nested @nextcloud/dialogs > pinia entry). Result: `npm ci` failed on every CI quality job, taking down 5 checks at once (Security npm, License npm, ESLint, Stylelint, SBOM). Fix: regenerate the lockfile with `npm install` (no package.json changes). Verified locally: - `npm ci` now succeeds - `npm run lint` passes - `npm run stylelint` passes - `npm audit --audit-level=high` reports 0 high/critical (25 low/moderate)
chore: regenerate package-lock.json to fix quality CI failures
…hecks (#32) Adds a Spec Validation workflow + three checks that catch the class of bug seen in scholiq Wave 2 *before* it reaches development: - tests/validate-json-strict.js — strict JSON parse of src/manifest.json + lib/Settings/*_register.json; FAILS on duplicate keys (git merges JSON line-by-line; two adds at the same key but different file positions produce no textual conflict, just a doc with a dup key, and json_decode keeps the last → silent schema/page loss) and on `appendOnly` nested inside an x-openregister block (OpenRegister's Schema::hydrate only reads a top-level appendOnly, so a nested one is silently dropped). - tests/validate-register.js — structural checks on the register seed: every schema has slug/type/required/properties; slug uniqueness; lifecycle `requires:` references a PHP class that exists under lib/ (catches scholiq's missing CoursePublishGuard); a 'schema looks clobbered / is a stub' heuristic (≤3 props + no x-openregister-*). Optional deep check against OR's configurations/validate endpoint when OR_BASE_URL + OR_BASIC_AUTH are set. - package.json: check:register, check:json-strict, check:specs scripts. - .github/workflows/spec-validation.yml: runs `npm run check:specs` on every push/PR. Add 'Spec Validation / validate' to the branch-protection ruleset's required checks to make it block merges. - README: documents the new checks and why they matter. Every app scaffolded from this template inherits the gate.
* feat: adopt the Features & Roadmap menu Ships the in-product Features & Roadmap page wired into the manifest so apps scaffolded from this template inherit the Settings-section "Features & roadmap" entry (powered by OpenRegister's github-issue-proxy, UI from @conduction/nextcloud-vue). Mirrors the per-app adoptions (procest/decidesk/ pipelinq/scholiq/openbuilt). - src/views/FeaturesRoadmap.vue — thin wrapper around CnFeaturesAndRoadmapView; the `repo` fallback is `ConductionNL/nextcloud-app-template` — cloners change it (or provide it via IInitialState `<appId>::features_roadmap_*`) - customComponents.js — register it as the `FeaturesRoadmap` custom component - manifest.json — add the `FeaturesRoadmap` custom page (route /features-roadmap) + a `FeaturesRoadmapMenu` entry in the settings section - bump @conduction/nextcloud-vue ^1.0.0-beta.12 → ^1.0.0-beta.35; pin @nextcloud/axios ~2.5.2 (+ overrides) — 2.6.0 ships a broken `exports` field that breaks @nextcloud/vue under webpack (cf. ConductionNL/openregister#1489) Refs: ConductionNL/hydra#251 * chore: refresh package-lock.json for the @conduction/nextcloud-vue ^1.0.0-beta.35 bump
…ate (#31) Wires the AI Chat Companion MCP-tool pattern (hydra ADR-034/035) into the template so new Conduction apps get it by default. - lib/Mcp/ExampleToolProvider.php — heavily-commented copy-me provider with two trivial example tools: app-template.ping and app-template.describeApp - lib/AppInfo/Application.php — registers the provider under the alias OCA\OpenRegister\Mcp\IMcpToolProvider::{appId} - tests/Stubs/Mcp/IMcpToolProvider.php — stub interface until openregister PR #1466 ships the real one; wired via composer autoload-dev + bootstraps - tests/Unit/Mcp/ExampleToolProviderTest.php — contract test (7 cases) - README: new "AI Chat Companion / MCP tools" section - psalm.xml: allow the OCA\OpenRegister\Mcp\IMcpToolProvider cross-app ref Widget mount: the template already mounts CnAppRoot from @conduction/nextcloud-vue (^1.0.0-beta.12), which renders the companion FAB once nextcloud-vue beta.31 (CnAiCompanion) is published — no bump needed.
…ings) (#34) - Change SettingsMenu entry to action: "user-settings" / icon: "Cog"; drop route + section. CnAppNav invokes cnOpenUserSettings inject (CnAppRoot) which opens NcAppSettingsDialog. The Settings type:'settings' page stays for direct-URL /settings access. - App.vue: import NcAppSettingsSection; add #user-settings slot to CnAppRoot with a placeholder NcAppSettingsSection — replace with real settings content when scaffolding a new app. - README: one-line note in the manifest/menu section documenting the pattern. - Upgrade @conduction/nextcloud-vue from beta.30 → beta.35 (schema 1.5.0) to support action: "user-settings" — package.json already declared ^1.0.0-beta.35; node_modules was behind. - validate-manifest PASS (0 Ajv errors, schema 1.5.0); lint 0 errors; build succeeds.
Keeps the app template on the current lib — CnIndexPage store-backed self-fetch (nc-vue #223) + columns[].formatter / .widget / .aggregate + pages[].config.filter (#219/#221/#222) — so scaffolded apps start at the right version.
chore(deps): bump @conduction/nextcloud-vue to ^1.0.0-beta.40
…fold (#36) Every new Conduction app currently inherits only a documentation.yml deploy-workflow stub with no Docusaurus site behind it. This adds the default so a scaffolded app is born docs-ready: - docs/ on @conduction/docusaurus-preset 2.6.1 — createConfig/BRAND_THEME, brand <DetailHero>/<WidgetShelf> landing page, intro.md, custom.css, sidebars.js, package-lock.json, static/CNAME (app-template.conduction.nl), static/img/logo.svg. - journeydoc scaffold (hydra ADR-030) — tutorials/{_category_,user/,admin/} with two stock stories (user "open the app for the first time" → Dashboard, admin "manage settings" → Admin Settings), tests/e2e/docs-screenshots.spec.ts capture suite, and playwright.config.ts with chromium + docs-capture projects. - .github/workflows/documentation.yml — deploy from `development` to app-template.conduction.nl (was: `documentation` branch / app-template.app). - .gitignore — docs/ build artefacts, docs/i18n/nl/, playwright outputs. - README.md — Screenshots section now points at the journeydoc workflow, directory-structure block lists docs/ + tests/e2e/ + playwright.config.ts, and a Documentation section describes the docs-site build + deploy. Build verified locally (`npm ci --legacy-peer-deps && npm run build`).
…Vue-mount failure) (#37) webpack.config.js uses splitChunks with `enforce: true` cacheGroups that emit two shared chunks (`<appId>-shared-vendor`, `<appId>-shared-nc-vue`). The main and adminSettings entry bundles wrap their Vue mount in `__webpack_require__.O(0, [shared chunks], …)`, which only fires once every listed chunk has registered itself on `self.webpackChunk<appId>`. With only the entry script in `addScript()`, the shared chunks never load, the mount callback never fires, and the app silently renders nothing. Mirrors the canonical fix in zaakafhandelapp#206. ExampleWidget.php already loaded the shared chunks correctly; this brings the page and admin-settings templates into line. Because this is the scaffold template, every newly-generated app inherits the fix.
…ate from code CI/CD) (#38)
chore(deps): @conduction/nextcloud-vue 3.0.0-vue3.6
Quality Report — ConductionNL/nextcloud-app-template @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 738/738 | |||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ |
Quality workflow — 2026-08-04 13:00 UTC
Download the full PDF report from the workflow artifacts.
…pping (#123) A skipped job and a passing job are indistinguishable in the Quality Report. Every gate turned on here reported 'skipped' in every run. Each newly-enabled leg was measured against this tree BEFORE being enabled; the results are in the PR description. Legs that were measured failing are enabled anyway - the defects are pre-existing, and the only thing that changed is that CI can now see them. Journeydoc Capture and enable-axe are deliberately NOT enabled.
Quality Report — ConductionNL/nextcloud-app-template @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| check-manifest | ❌ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 738/738 | |||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ |
Quality workflow — 2026-08-04 15:55 UTC
Download the full PDF report from the workflow artifacts.
…o its own ruleset (#125) The canonical ruleset carried a nested <rule ref="...UnusedFormalParameter"><exclude-pattern>*Migration*</exclude-pattern></rule> which is INERT. PHPMD 2.15 reads exclude-patterns in RuleSetFactory::getIgnorePattern(), which walks $xml->children() - only elements DIRECTLY under <ruleset>. A nested one parses without error and does nothing, so lib/Migration was always scanned by the rule the pattern was meant to spare. This 103-line phpmd.xml was copied byte-identically into 16 downstream apps, so every one of them inherited the broken shape (ConductionNL/.github#155, this repo #124). Simply promoting the pattern to the top level of phpmd.xml is not the fix: PDepend applies a top-level exclude-pattern at file-collection time, so it drops the file from EVERY rule in the ruleset. Measured on openregister, that shape is silently swallowing 11 real non-UnusedFormalParameter findings in lib/Migration. Instead, UnusedFormalParameter now lives alone in phpmd-unusedparams.xml with a TOP-LEVEL */Migration/* exclude-pattern, and the composer script runs both rulesets as separate legs keeping the worst exit code, so neither leg can short-circuit the other. The exclusion is scoped to that one rule; every other rule still analyses lib/Migration. Measured with PHPMD 2.15.0 / PHP 8.4.22. This repo has no lib/Migration, so the finding list is unchanged (0 before, 0 after, exit 0 on both legs). Positive-controlled on a throwaway copy carrying a probe lib/Migration class: its UnusedFormalParameter is dropped, its ElseExpression is still reported, and an UnusedFormalParameter outside lib/Migration is still reported. No phpmd.baseline.xml exists in this repo and none was added or removed. No @SuppressWarnings was added; none was removed (this repo has no lib/Migration suppressions). Refs #124
Quality Report — ConductionNL/nextcloud-app-template @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| check-manifest | ❌ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 738/738 | |||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-05 13:07 UTC
Download the full PDF report from the workflow artifacts.
…dead suppression (#126) * fix(phpmd): collapse the widget's static Util calls into one, drop a dead suppression TRUE PHPMD findings — measured with every @SuppressWarnings stripped on a throwaway copy, PHPMD 2.15.0 / PHP 8.4.22 — go **4 → 2**. Shipped findings stay at 0. There is no phpmd.baseline.xml in this repo and there must not be: PHPMD AUTO-DISCOVERS that filename, so a baseline would stay active even after the `baseline` CLI flag was removed. ExampleWidget::load() --------------------- Three literal `Util::addScript()` calls were three separate StaticAccess findings for one idea: "load these chunks, in this order". The ordered list is data, so it moves to a `SCRIPT_CHUNKS` class constant and the method loops over it. One static call site instead of three — 3 findings → 1, and the load order that the docblock has always insisted on is now expressed once, next to the names, instead of being re-stated in three call sites that could drift apart. The remaining finding is the floor. `OCP\Util` is a static-only facade; Nextcloud exposes no injectable equivalent of `addScript()` for a Dashboard widget, so the suppression stays — with its reason spelled out rather than asserted in five words. Application ----------- * `register()` — the `@SuppressWarnings(PHPMD.UnusedFormalParameter)` was DEAD. The method uses `$context` on every line of its body. A suppression that suppresses nothing is worse than none: it reads as an admission of debt that is not there, and it hides the day the parameter genuinely does fall out of use. Deleted. * `boot()` — JUSTIFIED and kept. `$context` is mandated by `OCP\AppFramework\Bootstrap\IBootstrap::boot()`; the template has nothing to do at boot time and cannot drop the parameter without breaking the interface. The tag now carries that reason in writing, plus a note telling apps copied from this template to delete it once they boot something. Suppression ledger: 3 → 2 (1 deleted as dead, 2 kept as justified; both kept tags previously had a thin or absent reason and now carry a full one). `ExampleWidget::load()` gains `@spec openspec/specs/scaffold-components/spec.md` — its body changed, so gate-16 (spec-coverage, diff-scoped on the declaration and body) now has it in scope. That spec exists precisely so the demo components' @SPEC references resolve, and `lib/Mcp/ExampleToolProvider.php` already cites it the same way. Verified inside nextcloud:latest (PHP 8.4.22) with `composer install` run IN this worktree: phpcs 0 errors, phpstan "No errors", psalm exit 0, phpmd exit 0, PHPUnit 20 tests / 54 assertions OK (unchanged). * fix(spec): make the template's example @SPEC anchor resolve `@spec openspec/changes/example-change/tasks.md#task-N` was a literal placeholder. gate-46 (spec-anchor-existence) opens the target and looks for the heading or task id, so `#task-N` resolved to nothing and the gate failed on every PR that touched this file — which this PR is, so the pre-existing gap surfaced here. Repointed at `#task-4`, which exists and is the right one: it is the DeepLinkRegistrationListener subscription that `register()` performs. The surrounding teaching comment now also says the anchor must resolve, and tells apps copied from this template to repoint rather than leave a placeholder — otherwise every derived app inherits the same failing gate.
Quality Report — ConductionNL/nextcloud-app-template @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| check-manifest | ❌ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 738/738 | |||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-05 14:31 UTC
Download the full PDF report from the workflow artifacts.
…on/* to */lib/Migration/* (#128) `*/Migration/*` is a path-SEGMENT pattern, not a directory anchor. PDepend compiles an exclude-pattern into an UNANCHORED regex — Input\ExcludePathFilter preg_quote()s the pattern and then turns `\*` into `.*` — so it matches ANY path containing a `/Migration/` segment: `lib/Service/Migration/`, `lib/Command/Migration/`, any future `lib/*/Migration/`. Those are ordinary classes with no interface-mandated signature, and a genuine unused parameter in one of them would never be reported while the run still looked clean. PROBE MATRIX, PHPMD 2.15.0 / PHP 8.4.22, three probe classes on a throwaway copy: probe */Migration/* */lib/Migration/* lib/Migration/… UFP not reported not reported (intended) lib/Migration/… Else (leg 1) reported reported (leg 1 unaffected) lib/Service/… UFP reported reported lib/Service/Migration/… UFP NOT REPORTED reported (the leak) The IMigrationStep exclusion still does exactly its job — a step that cannot drop changeSchema/preSchemaChange/postSchemaChange's three mandated parameters is still spared — and leg 1 still analyses lib/Migration for every other rule. Real numbers: UNCHANGED. A grep for `/Migration/` directories outside lib/Migration/ found none in this repo, so nothing was actually being swallowed. The trap goes regardless. One process note worth recording: the first draft of this comment contained a literal `<-` inside the <description>, which made the ruleset UNPARSEABLE. PHPMD then exited 1 and reported NOTHING — and "no findings printed" is exactly what a clean run looks like to a grep. It was caught only by reading the exit code directly. The XML is validated as part of the check now.
Quality Report — ConductionNL/nextcloud-app-template @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| check-manifest | ❌ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 738/738 | |||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-05 15:52 UTC
Download the full PDF report from the workflow artifacts.
…ue3.6 was unpublished) (#127) Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/nextcloud-app-template @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| check-manifest | ❌ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 738/738 | |||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-05 17:40 UTC
Download the full PDF report from the workflow artifacts.
…failing CI (#130) v1.0.1 is `f4d9756` (2026-08-03) and predates three gate fixes, so every Hydra Gates run this repo has ever made executed a script in which 16 gates reported PASS when their helper never ran (ConductionNL/.github#147), gate-33 had no axe report to read and never said so (#148), and gates 6 and 7 reported PASS on an empty scope (#149). The tick was identical either way, which is why nothing in this repo's history shows it. That pin is now also RED, and the mechanism is worth writing down. quality.yml is referenced `@main` while this package is PINNED, so the two can desync. #164 flipped `hydra-gates-require-full-coverage` to default true in the shared workflow, and that flag requires a gate to DECLARE itself not-applicable. v1.0.1 contains ZERO `_skip` calls; v1.3.0 has 36. v1.0.1 has no vocabulary to declare, so every absent prerequisite became "DID NOT RUN" and failed the job — for gates the repo has no subject matter for. Measured on this branch, diff-scoped against origin/development exactly as CI scopes it, in a private mount namespace with a private tmpfs (the runner's ~50 /tmp/hydra-gate-*.log paths are shared state and two concurrent runs corrupt each other's counts, .github#158 item 6): v1.0.1 exit 98 FAIL — "GATES THAT DID NOT RUN: 24 33" v1.3.0 exit 0 PASS — those gates named NOT APPLICABLE, with reasons Independently confirmed end-to-end: doriath#160 changed this one line and nothing else, and its Hydra Gates job went failure -> success. v1.3.0 is `f7eaf2a` = .github@main at the time it was cut. Refs ConductionNL/.github#159
Quality Report — ConductionNL/nextcloud-app-template @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| check-manifest | ❌ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 738/738 | |||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-05 18:45 UTC
Download the full PDF report from the workflow artifacts.
… EUPL-1.2 (#131) tests/e2e/docs-screenshots.spec.ts declared SPDX-License-Identifier: AGPL-3.0-or-later while every other licence signal in the repo (LICENSE, composer.json, package.json, appinfo/info.xml and every lib/**.php @license tag) says EUPL-1.2. Header-only change; PHPUnit (20 tests) identical before and after. gate-28: PASS -> PASS. The JS side could not be exercised: npm ci fails E404 because package.json pins @conduction/nextcloud-vue@3.0.0-vue3.6, which is unpublished. That is a pre-existing, separate issue; the pin is deliberately left untouched here. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/nextcloud-app-template @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| check-manifest | ❌ | ||||
| composer | ✅ | ✅ 100/100 | |||
| npm | ✅ | ✅ 738/738 | |||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-05 19:17 UTC
Download the full PDF report from the workflow artifacts.
) The 32 floor was raised on the premise that nothing tested below it. That is false here: this repo's own CI runs stable31, and min-version is enforced at install time, so occ app:enable refuses on 31 and the e2e seed fails with "is not installed or enabled". The original reason for a 32 floor no longer holds either. It came from openregister implementing OCP\ContextChat\IContentProvider, an interface absent before NC 32. openregister#2372 removed every eager reference to that class, so it is only loaded inside interface_exists() guards and the header is never read on an older server. openregister#2380 restored its own 28 floor on that evidence.
Quality Report — ConductionNL/nextcloud-app-template @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ❌ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| check-manifest | ❌ | ||||
| composer | ❌ | ✅ 100/100 | |||
| npm | ✅ | ✅ 738/738 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-07 11:14 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/nextcloud-app-template @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| check-manifest | ❌ | ||||
| composer | ❌ | ✅ 100/100 | |||
| npm | ✅ | ✅ 738/738 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-07 11:28 UTC
Download the full PDF report from the workflow artifacts.
#136) `tests/validate-manifest.js` hardcoded the v1 app-manifest schema while `src/manifest.json` declares v2: "$schema": ".../app-manifest-v2.schema.json" The v1 schema sets `additionalProperties: false` on `$defs.widgetDef` and predates the `content` / `icon` / `integrationId` widget keys that `CnDetailPage` actually renders, so it reported errors against a manifest that renders correctly and passes its own declared schema with zero errors. The validator now picks the schema variant from the manifest itself, as openconnector and scholiq already did. Ported verbatim from portaliq#59, where the same defect was diagnosed against a live browser: the pages render their widgets fully, and the migration the errors seemed to demand would have blanked every icon in the app (`icon` holds an MDI component name, neither `iconClass` nor `iconUrl`). Also drops the two hardcoded `/tmp/worktrees/...` schema candidates, which cannot resolve on a CI runner. This repo is the scaffold every new app copies, so the stale validator reproduces the same false failure in each one.
Quality Report — ConductionNL/nextcloud-app-template @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| check-manifest | ✅ | ||||
| composer | ❌ | ✅ 100/100 | |||
| npm | ✅ | ✅ 738/738 | |||
| PHPUnit | ⏭️ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-08 07:50 UTC
Download the full PDF report from the workflow artifacts.
…ap (#137) `trace: 'on-first-retry'` only writes a trace when a retry actually happens, so it is a function of `retries`. Off CI this config sets `retries: 0`, and two repos scaffolded from this template (nldesign, launchpad) carry `retries: 0` on CI as well — in those the trace artifact the shared workflow faithfully uploads on failure has always been empty of traces. `retain-on-failure` captures every test, keeps only the failures, and does not depend on the retry count. No repo in the fleet set `globalTimeout`. The shared quality.yml Playwright job is `timeout-minutes: 45`, and a job cancelled by that cap produces no verdict and no artifacts: the trace upload is `if: failure()` and the report upload is `if: always()`, and neither runs on a cancelled job. Runs cancelled at ~45m16s have been observed in this fleet. Measured job overhead before the Playwright step starts is 2.0-2.4 min, so 38m leaves ~7 min of margin while guaranteeing a tally and its artifacts. Fixing the template is the point: every new app is scaffolded from it, so without this the defect regenerates. Fleet-wide sweep: ConductionNL/.github#188.
Quality Report — ConductionNL/nextcloud-app-template @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| check-manifest | ✅ | ||||
| composer | ❌ | ✅ 100/100 | |||
| npm | ✅ | ✅ 738/738 | |||
| PHPUnit | ❌ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-08 14:19 UTC
Download the full PDF report from the workflow artifacts.
@SPEC tags (#139) Two regeneration sources kept gate-46 (spec-anchor-existence) findings coming back no matter how often they were ground down. This repo owns both. 1. THE SNIFF TOLD DEVELOPERS TO WRITE THE PATTERN GATE-46 REJECTS `SpecTagSniff` runs as a blocking `PHP Quality (phpcs)` job and instructed, in its docblock and in its own warning text: @SPEC openspec/changes/{change-name}/tasks.md#task-N A change directory is temporary by definition — completing a change moves it to `openspec/changes/archive/<date>-<name>/`, and renaming or dropping one removes it outright. Every tag written to that instruction dangles from that moment. Measured on portaliq: 100 unresolved targets, 260 of 385 live tags pointing into a change dir. The sniff ships identically in 20 ConductionNL repos. Both messages and the docblock now name the canonical form: @SPEC openspec/specs/{capability}/spec.md#requirement-{slug} The method-level message previously carried no guidance at all. Severity is unchanged (WARNING) — verified with phpcs: an untagged class and public method still report 0 errors / 2 warnings, and a tagged file still reports nothing. 2. THE TEMPLATE'S OWN BOILERPLATE POINTED AT ITS OWN EXAMPLE CHANGE The template was green on these because it still ships `openspec/changes/example-change/` — but every app scaffolded from it inherits the tags and then deletes the example change, so they dangle on day one. That is exactly what portaliq's "target file not found" findings for `example-change`, `scaffold-v2` and `template-manifest-v1` are, and petstore carries them too. Repointed at canonical specs this repo actually ships, each anchor verified to name a real heading with gate-46's own resolver: lib/AppInfo/Application.php -> deep-linking#REQ-LINK-001 (register() subscribes the DeepLinkRegistrationListener) lib/Controller/MetricsController -> observability#REQ-OBS-001 lib/Controller/HealthController -> observability#REQ-OBS-002 lib/Controller/PreferencesController -> settings-management#REQ-CFG-005/006 src/App.vue -> template-manifest-v1#REQ-TMV1-4 + scaffold-v2#requirement-app-vue-... src/formFields/EmailField.vue -> scaffold-v2#requirement-five-kind-... src/cellRenderers/StatusBadge.vue -> (same) src/modals/ExampleModal.vue -> (same) `Application.php`'s docblock prose also actively taught the change-dir convention; it now teaches the canonical one. 3. A CAPABILITY THAT HAD NO SPEC `PreferencesController` was the template's only red gate-46 pair, pointing at `openspec/changes/retrofit-2026-05-26-preferences-api/` — a change directory that exists nowhere in this repo. There was no canonical requirement to repoint it at, so REQ-CFG-005 (read) and REQ-CFG-006 (write) were added to `settings-management`, written from the controller's actual behaviour: 401 without a session, 400 on a key that fails sanitisation, and an empty value DELETING the stored preference so a cleared and a never-set key read back identically. MEASURED, using gate-46's real resolver from ConductionNL/.github@main (post-#246), full-tree over lib + src: before: 2 findings after: 0 findings and 0 remaining `@spec openspec/changes` tags in lib + src. Verification is not just "the string changed": a control run confirmed the resolver still reports a bad anchor, a bad requirement id, and a missing file on these same paths, so the 0 is a real 0. Refs ConductionNL/.github#228
Quality Report — ConductionNL/nextcloud-app-template @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| check-manifest | ✅ | ||||
| composer | ❌ | ✅ 100/100 | |||
| npm | ✅ | ✅ 738/738 | |||
| PHPUnit | ❌ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-08 14:57 UTC
Download the full PDF report from the workflow artifacts.
Follow-up to the gate-46 sniff work. That change added REQ-CFG-005 and REQ-CFG-006,
and added them WITHOUT the `@e2e` annotation ADR-020 requires — 6 new
scenarios of undeclared coverage. This closes that.
Each exclusion carries a real reason rather than a formula:
- REQ-CFG-005/006 — the preferences endpoint is called by shared
@conduction/nextcloud-vue widgets, not by any page in this app, so a browser
test would exercise the widget rather than this contract.
MEASURED with gate-19'"'"'s own helper against origin/development:
before: 14 scenario(s) without a running e2e test
after: 8
The remaining 8 are pre-existing REQ-CFG-001..004 scenarios that predate this
work. They are in scope only because gate-19 is diff-scoped per FILE, and this
PR touches the file. They are deliberately NOT annotated here: asserting
coverage about scenarios I did not write, to clear a gate my own edit put them
in front of, is suppression rather than coverage. They belong to the fleet-wide
gate-19 backlog.
gate-46 is unchanged by this commit (verified: 0 / 23 / 8 for
nextcloud-app-template / petstore / portaliq, same as development).
Refs ConductionNL/.github#228
…ements docs(spec): annotate the requirements added for #228 with @e2e exclude
Quality Report — ConductionNL/nextcloud-app-template @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| check-manifest | ✅ | ||||
| composer | ❌ | ✅ 100/100 | |||
| npm | ✅ | ✅ 738/738 | |||
| PHPUnit | ❌ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-08 15:52 UTC
Download the full PDF report from the workflow artifacts.
Automated PR to sync development changes to beta for beta release.
Merging this PR will trigger the beta release workflow.