Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.

Release: merge development into beta#18

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

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

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

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

Merging this PR will trigger the beta release workflow.

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

WilcoLouwerse and others added 3 commits March 24, 2026 15:02
Release: promote beta to stable — initial CI pipeline and app scaffold
…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>
@github-actions

Copy link
Copy Markdown
Contributor Author

Quality Report — ConductionNL/nextcloud-app-template @ 321c395

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.

WilcoLouwerse and others added 26 commits April 16, 2026 18:31
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.
Defense against supply-chain attacks (e.g. shai-hulud, nx-style
compromised publishes) by blocking install of any package version
published less than 24h ago.

- .npmrc: `min-release-age=1` (npm 11.5+ native; older npm ignores it)
- .github/dependabot.yml: `cooldown.default-days: 1`, with @conduction/*
  excluded so first-party releases reach our apps immediately

For release-day consumption of fresh @conduction/* deps, use
`npm install --min-release-age=0 @conduction/pkg@x.y.z`.
Every new Conduction app spun up from this template now inherits the
AI-baseline contract on first deploy:

- @conduction/docusaurus-preset bumped from ^2.6.1 to ^3.4.0. The
  3.4.0 release ships Organization + WebSite JSON-LD on every page,
  SoftwareApplication from <DetailHero>, FAQPage from <FAQ>, default
  og:image + twitter:* meta, sitemap options, and a postBuild plugin
  that emits a sensible default robots.txt when the site doesn't
  ship its own.
- docs/scripts/validate-ai-baseline.mjs: generic validator that
  checks the 8 universal AI-crawler contract items: robots.txt
  exists with AI-bot allow lines + a Sitemap, sitemap.xml has >= 1
  URL, homepage emits Organization + WebSite JSON-LD, og:image /
  og:type / twitter:* meta are present, and og:image resolves to
  a real file in the build. Exits 1 on regression so CI blocks.
- docs/package.json: postbuild npm script wires the validator into
  every `npm run build`, plus a validate:ai-baseline alias for
  ad-hoc runs.
- docs/static/llms.txt: placeholder llmstxt.org-format index that
  app teams customise per app. Markers ({{APP_NAME}}, {{slug}},
  {{ONE_SENTENCE_DESCRIPTION}}) make the TODO obvious.

Combined with the centralised ConductionNL/.github documentation
workflow change that runs build on every PR, this means a regression
to the AI baseline now blocks at PR review time, fleet-wide, for
every app built from this scaffold.

App teams: replace docs/static/llms.txt placeholders, optionally
drop a per-app docs/static/img/og-app.png and reference it via
themeConfig.image, and optionally ship a docs/static/robots.txt
to override the preset default.
Replace bare-brand Layout title with descriptive form that
includes the keyword payload. Docusaurus auto-appends the site
title as suffix, so SERPs no longer show duplicate brand titles.
fix(docs): descriptive homepage title (#80)
rubenvdlinde and others added 30 commits May 21, 2026 20:39
phpcs.xml, phpmd.xml, psalm.xml, phpstan.neon, and phpstan-bootstrap.php
in this repo are the source of truth for the Conduction PHP fleet. Apps
mirror these files; per-app deviations belong in baselines, not in the
canonical files themselves.

This unblocks a fleetwide template-sync flow that propagates root-config
changes from here to all 17 PHP apps in apps-extra/.
Lock the canonical root configs so they reflect the union of patterns
already in use across the 17 PHP apps. Each addition is driven by a
fleetwide concern, not a per-app preference -- per the consolidation
directive, exceptions are fleetwide or they don't exist.

phpcs.xml:
- Add SpecTagSniff (warning) -- hydra ADR-003 fleetwide @SPEC convention.
  Currently enforced in decidesk/pipelinq/procest; promote so every app
  surfaces the gap.
- Add NoLegacyServerAccessorsSniff (error) -- blocks
  \OC::$server->getX()/query() removed in Nextcloud 34. Already in
  openregister and openconnector.
- Add ignore_warnings_on_exit=1 so SpecTag warnings surface in CI
  without failing the build.
- Add vendor-bin/ exclude (bamarni/composer-bin-plugin, used by
  larpingapp + softwarecatalog).
- Add lib/Resources/template/* exclude (apps that ship a verbatim
  template snapshot, e.g. openbuilt).

psalm.xml -- add referencedClass entries that recur across the fleet:
- OCA\OpenRegister Db (ObjectEntity, RegisterMapper, SchemaMapper),
  Event (Object{Creating,Created,Updating,Updated,Deleted}Event), and
  Service (RegisterService, FileService).
- OCP infrastructure types: IBootContext, IRegistrationContext,
  DoesNotExistException, DB\Exception, IGroup, IUser, IRepairStep.

phpstan.neon -- promote ignoreErrors families that recur in 4+ apps:
- Broader OC\ + OC_App ignoreErrors (server internals not in OCP stubs).
- OCA\DAV\ family (DAV server-internal).
- OCA\OpenRegister\ family extras: has invalid type, implements unknown
  interface.
- Doctrine\DBAL\ family (shipped by Nextcloud server, not stubbed).
- OCP stub gaps: IRequest::getContent, IRequest::setParameter,
  IMessage::attachFile, Entity::jsonSerialize.
- Add vendor-bin/ and lib/Resources/template/ to excludePaths.

Two new custom phpcs sniff files copied verbatim from their current
homes (SpecTagSniff from decidesk, NoLegacyServerAccessorsSniff from
openregister).

Drives Phase 2 of the fleetwide root-config consolidation: with the
canonical locked, the remaining 14 apps can sync to it (the 3 already-
aligned: shillinq, planix, deskdesk).
Three follow-ups discovered during the decidesk pilot of the fleetwide
canonical (#48):

1. Ship empty phpstan-baseline.neon and include it from phpstan.neon.
   This is the canonical tracked-debt entry point. The fleet directive
   forbids per-app validation rules, but baselines remain acceptable as
   temporary tracked-debt markers (with an open GitHub issue + removal
   timeline). Apps without debt ship the empty baseline file as-is.

2. Fix the JSONResponse statusCode ignoreErrors regex to match both
   `Parameter $statusCode` and the more common `Parameter #N $statusCode`
   forms that phpstan actually emits. The previous regex only matched
   the first form, which is why decidesk needed a per-app suppression
   (and several other apps too).

3. Add OCA\OpenRegister\Service\CalendarEventService to the psalm
   referencedClass whitelist. Currently only decidesk uses it, but it's
   an OpenRegister-shipped class loaded at runtime; whitelisting it
   fleetwide is no-op for apps that don't depend on it.
The `#` is the PCRE delimiter for phpstan ignoreErrors regexes, so the
unescaped `(#\d+ )?` in #49 closed the pattern early and produced
"Unknown modifier" errors when an app actually parsed the canonical.

Escape it as `(\#\d+ )?` so the pattern parses and matches both
`Parameter $statusCode` and `Parameter #N $statusCode` forms.

Caught while applying the canonical to decidesk in the Phase 2 pilot.
…ut (#51)

Four refinements identified during the 16-app fleet rollout (Phase 2),
absorbed back into the canonical so the next round of fleet syncs is
cleaner:

1. psalm.xml ignoreFiles adds lib/Resources/template — mirrors the
   existing phpcs/phpstan excludes for apps shipping a verbatim
   template snapshot (openbuilt). No-op for apps without the directory.

2. psalm.xml gains errorBaseline="psalm-baseline.xml" + ships an empty
   psalm-baseline.xml as the canonical tracked-debt entry point.
   openregister/mydash/opencatalogi/docudesk already used this pattern
   ad-hoc; making it canonical means every app's psalm tracked debt
   has a uniform home. Mirrors the phpstan-baseline.neon pattern from
   nextcloud-app-template #49.

3. psalm.xml referencedClass list gains
   OCA\OpenRegister\Service\RiskLevelService and
   OCA\OpenRegister\Db\EntityRelationMapper. docudesk surfaced these
   as needed-but-missing; promoting fleetwide so per-app additions
   aren't required (per the no-per-app-rules directive).

4. phpstan.neon ignoreErrors gains the OC\Security\CSRF\ family.
   Pipelinq surfaced CsrfTokenManager during the sync (no OCP
   equivalent yet); this is server-internal and applies fleetwide.
When the canonical root configs change on this template's `development`
branch, this workflow opens PRs across the 17 Conduction PHP fleet apps
syncing the change. Implements the propagation half of hydra ADR-033.

Files in scope (synced):
- phpcs.xml, phpmd.xml, psalm.xml, phpstan.neon, phpstan-bootstrap.php
- phpcs-custom-sniffs/CustomSniffs/Sniffs/**

Files explicitly NOT synced (per-app):
- phpstan-baseline.neon, psalm-baseline.xml (tracked debt per app)
- composer.json (vendor name is app-specific)

Cosmetic deviations preserved per-app post-sync (manual step in the PR):
- phpcs.xml <description>
- phpmd.xml <ruleset name> + inner <description>

Setup: requires the `FLEET_SYNC_TOKEN` repo-secret — a PAT with `repo`
scope on every target. Use a dedicated bot token rather than a
personal PAT so it survives team changes.
Brings the composer require.php constraint in line with the runtime
the container + CI already use (PHP 8.3). Drops formal support for
PHP 8.1 and 8.2. Several vendor packages (e.g. azjezz/psl@4.3.0) use
PHP 8.3 typed class constants, making the codebase functionally
8.3-only already.

Part of the fleet-wide PHP 8.3 sweep.
Three deliverables:

1. **Extends `sync-canonical-fleet.yml`** to add 6 more byte-for-byte
   canonical files alongside the existing PHP-tooling set (phpcs/phpmd/
   psalm/phpstan + custom sniffs):

   Frontend / JS quality tooling
     - stylelint.config.js
     - eslint.config.js
     - .prettierrc

   Git / npm / Node toolchain
     - .gitattributes
     - .npmrc
     - .nvmrc

   Workflow path-triggers + header comment updated to match. Fleet
   sync will fire on changes to any of these going forward.

2. **`docs/canonical-files.md`** — user-facing policy doc explaining
   the 5-tier categorization (strict canonical, template-based,
   app-specific, gitignored, per-app extras), how a canonical change
   flows from edit → fleet PRs, and what's deliberately out of scope
   (ExApp sidecars + external repos). References ADR-033 as the
   authoritative architecture decision.

3. **`docs/fleet-extras-audit.md`** — snapshot of every root-directory
   file in fleet apps that's NOT in this template, scored by frequency
   (≥6 / 3-5 / 1-2 apps) with a per-file decision: promote to template,
   keep app-private, move out of repo root, delete, or out of scope.
   Calls out the per-app cleanup candidates app-by-app (softwarecatalog
   is the heavy hitter with ~30 root scratch files).

Drift baseline from the audit:
  - phpcs.xml: 20/21 apps drifted from template
  - phpmd.xml: 19/20 drifted
  - psalm.xml: 21/21 ALL drifted (zero matches)
  - eslint.config.js: 14/14 ALL drifted
  - phpstan.neon: 13/16 drifted
  - phpunit.xml: 12/16 drifted

The agent's recent `chore: sync canonical root configs` PR series
(deskdesk#33, decidesk#243, shillinq#300) is the in-flight rollout
that closes this drift. The doc captures the policy that work follows.
…h placement (#56)

Four corrections to the canonical-files policy:

1. **No more per-app description deviations**: phpcs.xml and phpmd.xml
   now ship with app-generic descriptions
   ('Conduction Nextcloud app coding standard…'). The earlier convention
   of allowing per-app description strings created a manual re-stamping
   step after every fleet sync and made drift hard to spot (an app's
   accidental rule edit looked identical to a legitimate
   description-only deviation in the diff). Generic strings make any
   diff visible at sync time.

2. **Test scripts go to tests/**, not scripts/dev/. App-unique test
   files (.sh, shell-driven Newman, integration scenarios) ARE the
   app's test suite and belong with unit / Playwright / Newman in
   tests/. scripts/dev/ is for diagnostic one-shot dev utilities
   (debug_*.php, etc.), NOT tests.

3. **test-results/ and similar artefact dirs** called out as a separate
   class of repo-root pollution. The template's canonical .gitignore
   already covers them; the follow-up is verifying fleet apps inherit
   the .gitignore + don't have committed instances. Audit one-liner
   included.

4. **softwarecatalog reframed as special case**: the WIP / issue / testing
   notes are PRESERVED as future-spec source material — move to
   docs/specs-todo/, do not delete. Same intent for other apps with
   long-form work-notes (opencatalogi REFACTORING_PLAN, openregister
   QUALITY_OVERVIEW etc.).
#57)

After #56 retired the cosmetic-deviation allowance, the canonical files
sync byte-for-byte including descriptions. The auto-generated PR body
on each fleet sync still told reviewers to re-stamp per-app description
strings — that's now wrong. Replace with explicit 'no manual post-merge
step needed' guidance.

Also linked the new policy doc (docs/canonical-files.md) so downstream
reviewers can find the rules without bouncing through ADR-033.
…tform 8.3 (#59)

Security architecture: a single GHA-secret PAT with repo:write on 16
fleet apps is a credential blast-radius we don't want to defend. The
canonical sync moves to hydra/scripts/fleet-sync/ where it runs locally
with each developer's own gh auth — no shared token, no GHA secret to
rotate, no third-party action with broad fleet access.

Changes:
- Delete .github/workflows/sync-canonical-to-fleet.yml + .github/sync-canonical-fleet.yml
  (moved to hydra/scripts/fleet-sync/ in a separate hydra PR).
- composer.json config.platform.php: 8.1 -> 8.3. require.php was bumped
  fleet-wide last night (PR #54 here, plus 18 sibling PRs across the
  fleet); the platform pin keeps composer's resolver consistent with
  the runtime constraint.
- docs/canonical-files.md updated to describe the new local-sync flow,
  the security rationale, and the hydra location.

Issue #58 (FLEET_SYNC_TOKEN setup) becomes obsolete and will be closed.
…drift) (#60)

Companion to docs/fleet-extras-audit.md. That doc covered root-file
presence; this one digs into content drift across the things that
aren't byte-for-byte canonical but still want template alignment:

- composer.json scripts: 25-script common base + 3 outliers
  (app-versions, opentalk, zaakafhandelapp at sub-canonical sizes)
- package.json scripts: 18/18 on build/lint/stylelint, but only 9/18
  have test or test:e2e — half the fleet has no JS test gate
- CI workflows: 9 canonical workflows in 13-17 of 22 apps; 4 apps
  (larpingapp/openconnector/openregister/zaakafhandelapp) missing
  several canonical ones; app-versions has 13 workflows including a
  pile of legacy ones to retire
- appinfo/info.xml: MASSIVE drift — PHP min-version ranges 8.0 to 8.3
  across the fleet (composer is unified at ^8.3 now, info.xml is
  incoherent with it in 17/22 apps), NC version ranges all different,
  license spelling 4 different ways
- README structure: ~14 common sections present in 12+ of 22 apps,
  natural scaffold target

Each section has explicit recommendations: Tier A (script-driven
fleet cleanup, high leverage) → info.xml canonicalization, missing
canonical workflows. Tier B (scaffold drift sweeps) → close composer/
package.json script gaps. Tier C (documentation) → canonical script
inventory.

Adds a forward-link from canonical-files.md so the entry point doc
points at the deeper research.
…+ 8 unit tests (#21)

Ports the ADR-023 action-authorization pattern from decidesk's reference
implementation (decidesk@88844d0) to the template so every new app picks
it up for free.

What's included:

* lib/Service/ActionAuthService.php — verbatim service:
  - requireAction (throws OCSForbiddenException)
  - can (non-throwing bool variant)
  - getAllowedGroups / getMatrix / setMatrix / getActions
  - Admin always passes (break-glass)
  - Default-deny on missing or malformed matrix
  - Matrix stored in IAppConfig under 'actions' key

* lib/Repair/InitializeActions.php — seeds lib/actions.seed.json on
  install; preserves admin-customized matrix on upgrade

* lib/actions.seed.json — empty "actions" object (template ships
  with no actions; implementers add one entry per requireAction()
  call site as the app grows)

* tests/Unit/Service/ActionAuthServiceTest.php — 8 unit tests
  covering the contract:
  1. Admin always passes
  2. Default-deny on missing matrix entry
  3. Admin-only matrix entry blocks non-admin
  4. Group-matching non-admin passes
  5. Admin in entry doesn't auto-pass non-admin
  6. can() returns bool
  7. Malformed matrix JSON falls back to deny
  8. getAllowedGroups defaults to ['admin']

* appinfo/info.xml — registered Repair\InitializeActions on install
  + post-migration

* openspec/architecture/adr-023-action-authorization.md — ADR copied
  from hydra for the app's local reference

Implementers declare an action name in their controller (e.g.
'item.publish'), add it to actions.seed.json with ['admin'] default,
then call $this->actionAuth->requireAction($user, 'item.publish')
wrapped in try/catch for OCSForbiddenException. Admin broadens the
group list via a settings UI (not included in this template port — a
follow-up change can port decidesk's SettingsController matrix API).

@SPEC openspec/architecture/adr-023-action-authorization.md
…) (#63)

* fix(deps): resolve eslint-config/typescript-eslint ERESOLVE (closes #62)

@nextcloud/eslint-config@8.4.2 peer-requires @vue/eslint-config-typescript@^13
and eslint-import-resolver-typescript@^3.8, neither of which were declared.
npm eagerly pulled the optional eslint-plugin-import-x peer (which demands
@typescript-eslint/utils@^8.56) against the pinned @typescript-eslint@7.18,
producing the ERESOLVE that blocked npm ci fleet-wide.

- Add @vue/eslint-config-typescript@^13.0.0 + eslint-import-resolver-typescript
  + eslint-plugin-import as explicit devDeps (the missing required peers).
- Pin eslint-import-resolver-typescript to ~3.8.7 and override import-x's
  @typescript-eslint/utils to ^7.18.0 so the optional import-x peer stays on the
  7.x tree instead of dragging in 8.x.
- Bump node-polyfill-webpack-plugin 3 -> 4 (a second, pre-existing ERESOLVE
  surfaced once the first cleared: @nextcloud/webpack-vue-config@6.3.2 peers 4.0.0).

npm ci and npm run lint both pass cleanly; no legacy-peer-deps escape hatch.

* fix(deps): regenerate package-lock.json with npm 10 to match CI

* fix(ci): allowlist pako + sha.js compound SPDX licenses

pako (MIT AND Zlib) and sha.js (MIT AND BSD-3-Clause) are denied by the
License (npm) gate because its check only splits SPDX OR expressions, not AND.
Both halves of each are permissive and EUPL-1.2/AGPL-3.0 compatible, so add
manual overrides. These two transitive deps predate this PR (they failed the
same gate on the broken-CI baseline); adding the overrides unblocks the gate
now that npm install resolves.
… populate example openspec (#61)

Consolidates the unique, still-unmerged work from the stale PRs #19, #20 and
#22 (their branches predated the manifest-renderer refactor and could not be
merged without reverting it), reapplied freshly on current development:

Headers / SPDX / @SPEC (was #19):
- Fix the propagated `dev@conductio.nl` -> `info@conduction.nl` typo across all
  10 template PHP files (root-cause fix; the typo had spread fleet-wide via
  scaffolds).
- Bump @copyright 2024 -> 2026 and add SPDX-FileCopyrightText /
  SPDX-License-Identifier inside the main docblock (after @license), matching
  the canonical format already used by lib/Mcp/ExampleToolProvider.php.
- Add the file-level @SPEC example to Application.php (ADR-003) so scaffolds
  show builders the convention.

Observability + REUSE (was #20):
- Add HealthController + MetricsController. appinfo/routes.php already declared
  metrics#index and health#index, but the classes were missing, so /api/health
  and /api/metrics returned 500. HealthController is public (@publicpage) and
  verifies OpenRegister; MetricsController is admin-only Prometheus text with
  {app}_info and {app}_health_status gauges (ADR-006).
- Add REUSE.toml for non-PHP source files.

Example OpenSpec (was #22):
- Populate openspec/changes/example-change/ + 7 openspec/specs/ capabilities
  that the @SPEC docblock tags point at, so the tags no longer resolve to dead
  links. Adapted task-5 / item-management / design.md to the current layout:
  the removed ItemController/ItemService demo is replaced by ActionAuthService
  (ADR-023, merged in #21) as the per-object-auth illustration.
Annotate the 29 uncovered methods so every observable capability in the
scaffold carries an @SPEC reference. Because this is the fleet template,
the balance struck here is the reference for new apps.

Backend (16): annotate to the existing example specs / ADR-023.
Frontend (10): two new example capability specs — frontend-data-stores
(Pinia object/settings stores + boot initializer) and scaffold-components
(cell-renderer, modal, widget runtime behaviour).
Excluded (3, App.vue): provide() channel wiring, the i18n translate
wrapper, and the window.OC permissions passthrough — the canonical
plumbing-only exclude examples for template-derived apps.

Gate-16 now reports 0 uncovered methods.
Adopt CnAppNav three-section nav: Documentation and Features & roadmap
move to section:"footer" (bottom-pinned). Remove the user-settings menu
item since the new nav auto-prepends a Personal settings entry. Bump
@conduction/nextcloud-vue to 1.0.0-beta.97, which ships the new CnAppNav
(the old bundled one drops section:"footer" items). Patch info.xml.
* feat(preferences): scaffold generic per-user preferences endpoint

New apps inherit GET/PUT /api/preferences/{key} (IConfig user values,
pref_* namespace, key-sanitised). Backs the shared nextcloud-vue
CnSupportDialog auto-mount's per-user "seen" flag and any other small
per-user UI flag. NoAdminRequired + NoCSRFRequired.

* feat(preferences): add PreferencesController (force-add; gitignore '**/*references*' accidentally matches 'Preferences')
Add documentationUrl config to the Features & roadmap roadmap page so all
three footer nav items (Documentation, Features & roadmap, Settings)
resolve. Bump @conduction/nextcloud-vue to 1.0.0-beta.101.
Reverse-specs the generic per-user preferences endpoint, annotating
getPreference/setPreference with @SPEC references. Closes the 2-method
spec-coverage gap to zero. Comment-only; no behaviour change.
…IUserConfig (#73)

App-value calls: none found — ActionAuthService and SettingsService already use
IAppConfig::getValueString/setValueString (modern API, no changes needed).

User-value calls: 3 calls (getUserValue/setUserValue/deleteUserValue) in
PreferencesController intentionally LEFT as IConfig — info.xml min-version=28,
IUserConfig requires min-version>=31. Migrate when min-version is bumped to 31+.

Pre-existing PHPCS errors fixed while touching affected files:
- HealthController/MetricsController: missing named params in parent::__construct
- ActionAuthService: missing named params on internal calls, redundant always-false
  type guards removed from setMatrix (PHPStan level-5 fix), blank-line-after-fn
- InitializeActions: concat-spacing, inline-ternary (replaced with if/else),
  blank-line-after-fn

PHPStan: OK (no errors). PHPCS: errors resolved (warnings are @SPEC tag placeholders
across scaffold classes — tracked separately).
…ment, nc-vue bump, repair-steps, seed RBAC

- #74: Change repair step to loadConfiguration(force: false) — prevents
  operator-customised schemas being silently overwritten on every NC upgrade;
  keep force:true in SettingsController::load() for explicit resets only
- #75: HealthController and MetricsController already exist (pre-solved)
- #76: Align manifest schema/route on 'example'/'examples' to match seed
  register JSON slug and DeepLinkRegistrationListener (was: 'item'/'items'),
  fixing 404 on every fresh-install API call
- #77: Bump @nextcloud/vue from ^8.16.0 to ^8.39.0 and add overrides entry
  to prevent NC34 nav-corner sliver regression in every scaffolded app
- #79: Add InitializeSettings to info.xml <repair-steps> install + post-
  migration blocks so it fires after peer app autoloaders are fully loaded
- #82: DeepLinkRegistrationListener already uses 'example'/'examples',
  consistent with manifest after #76 alignment (no code change needed)
- #84: Add explicit rbac:true, multitenancy:true to register x-openregister
  and publicRead:false, publicWrite:false per schema so access control
  defaults are documented and stable across OR version bumps

Closes #74, #75, #76, #77, #79, #82, #84
…d AbstractToolHandler, initial-state, docusaurus bump

- #78: Delete src/store/modules/object.js (bespoke Pinia store with no CSRF,
  no pagination, manual URL assembly). Replace with createObjectStore from
  @conduction/nextcloud-vue in store.js — fleet-standard store handling
  CSRF headers, pagination, and consistent error surfacing
- #80: Remove '|| echo skip' from psalm/phpstan/phpmd scripts so composer
  check:strict correctly propagates non-zero exit codes when quality tools
  actually fail; tools are all in require-dev so they are always installed
- #83: Refactor ExampleToolProvider to extend AbstractToolHandler so the
  fleet-standard requireWriteRole()/requireAdminUser() helpers are available;
  add AbstractToolHandler stub for standalone CI; update test constructor
- #85: Replace data-version DOM attribute in admin.php with
  IInitialStateService::provideInitialState() (NC-standard CSP-compliant
  initial-state API); read via loadState('app-template','version') in Vue
- #86: Bump @conduction/docusaurus-preset from ^3.4.0 to ^3.12.0 in
  docs/package.json — canonical fleet version auto-published from design-system

Also fix registry.js status-badge appliesTo schema: 'item' → 'example'
to align with the seed register slug corrected in PR #91.

Closes #78, #80, #83, #85, #86
… IDelegatedSettings docblock, publiccode.yml

- #81 HIGH: Add inline docblock to AdminSettings explaining ISettings vs
  IDelegatedSettings pattern so developers know how to add delegated-admin
  support without inheriting the pattern blindly
- #87 LOW: Bump info.xml <php min-version> from 8.1 to 8.3 to match
  composer.json platform pin and openspec declared support matrix
- #88 LOW: Fix playwright.config.ts SPDX identifier from AGPL-3.0-or-later
  to EUPL-1.2 (canonical Conduction license; AGPL only appears in
  appinfo/info.xml as a Nextcloud store workaround)
- #89 LOW: Filter register UUID from SettingsController::index() response
  for non-admin users; admin-sensitive binding is now only returned when
  isAdmin=true. Update SettingsControllerTest with explicit admin/non-admin
  coverage for the new behaviour
- #90 LOW: Add publiccode.yml stub at repo root for public-sector open
  source discovery (PublicCode standard, Common Ground ecosystem)

Closes #81, #87, #88, #89, #90
…ep link, double repair (#94)

- C1: SettingsService::loadConfiguration now reads app_template_register.json from disk,
  extracts info.version, and calls importFromApp(appId, data, version, force) with all 4
  required arguments. Previous 2-arg call would throw on any OpenRegister instance.
- C3: HealthController and MetricsController hardcoded version '0.1.0'. Both now inject
  IAppManager and call getAppVersion(APP_ID) so version always tracks info.xml.
- H1: DeepLinkRegistrationListener urlTemplate changed from '/apps/app-template/#/examples/{uuid}'
  to '/apps/app-template/examples/{uuid}' — hash-fragment URLs are not valid deep-link patterns
  and break OpenRegister's unified search result navigation.
- H2: Application::register() was calling both registerRepairStep(InitializeSettings) AND
  info.xml declares the same step — causing double-execution. Removed the PHP-side call;
  info.xml repair-steps is the canonical registration path.
…+M3+M4+L1) (#95)

- C2/L1: Drop @NoCSRFRequired from PreferencesController setPreference and
  getPreference — per project policy, @NoCSRFRequired is OR-only. Both methods
  are user-session endpoints with CSRF protection required.
- M4: Add @NoCSRFRequired to MetricsController::index with explicit rationale
  comment — Prometheus scrapers have no CSRF token; admin-auth replaces it per
  ADR-006. This is the documented exception to the policy.
- M1: Delete src/store/store.js and src/store/modules/settings.js — these were
  orphan bespoke Pinia store files with no importers anywhere in the codebase.
- M3: Consolidate tests/Unit/ (capital U) into tests/unit/ (lowercase) — both
  directories existed simultaneously with disjoint content. phpunit.xml updated
  to reference only tests/unit/ going forward.
…st cleanup (H3+L2+L3+L4+L5) (#96)

- H3: appinfo/info.xml now declares <app>openregister</app> dependency, matching
  manifest.json which already listed "openregister" in dependencies. Without this
  NC won't enforce the OR dependency on install.
- L2/L3: AdminSettings::getForm injects IInitialStateService and calls
  provideInitialState to push the app version to the frontend CSP-safely.
  The service-locator call (\OC::$server->get()) in templates/settings/admin.php
  is removed — state is now injected by the DI'd class instead.
- L4: Added SPDX-FileCopyrightText and SPDX-License-Identifier to
  lib/Repair/InitializeActions.php and lib/Service/ActionAuthService.php (both
  were missing these required headers).
- L5: Removed placeholder @SPEC tag pointing to example-change/tasks.md#task-N
  from Application.php docblock (was a scaffold comment, not a real spec link).
- Manifest cleanup: remove hardcoded isUpToDate:true and showUpdateButton:false
  from version-info widget props (these are dynamic values the widget should
  determine itself from injected state, not static manifest props).
…ics comment (#97)

TC1 (false-positive doc fix): Remove misleading @AuthorizedAdminSetting claim
from MetricsController docblock. The endpoint IS admin-only by NC default
(SecurityMiddleware enforces isAdminUser() when @NoAdminRequired is absent);
the comment wrongly cited an attribute that was not applied.

TC2: Fix canonical brand icons (app.svg, app-dark.svg, app-store.svg).
- app.svg: fill="currentColor" -> fill="#fff" (was rendering as black silhouette)
- app-store.svg: replace <rect rx="40"> + NC blue #0082C9 with canonical
  point-up hexagon on Conduction cobalt #21468B, no overlapping strokes
- app-dark.svg: strip fill="white" -> no fill (inherits currentColor per convention)

TC3: Synchronise l10n/en.json and l10n/nl.json to a shared key-set union.
Previously en had 12 keys, nl had 29 keys, only 4 overlapping. Both files now
contain all 43 keys gathered from Vue sources + manifest + existing locale files,
with proper English values and Dutch translations throughout.

Fixes: /tmp/triage-nldesign-nctemplate.md TC1, TC2, TC3
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants