Release: merge development into beta - #1271
Conversation
This page already handles the failure well: `loadCatalogue()` stores the
message and the body renders an error banner instead of the report list.
The KPI tile above it did not take part. It binds `:count="reports.length"`,
which is 0 when the load failed, so the page showed
[ 0 report types ]
Failed to load the report catalogue
— a confident zero directly above the sentence explaining that nothing was
read. Of the two, the number is the one a reader takes at face value.
One line. `CnStatsBlock`'s `error` prop (nextcloud-vue 2.19.0) replaces the
count with a dash and "Unavailable", so the tile and the banner now say the
same thing.
Smaller than the sibling fixes in decidiq and pipelinq, where nothing surfaced
the failure at all — here it was only the tile disagreeing with the page.
Verified against what is installed, not what the library repo holds:
`package.json` ranges `^2.19.0`, `node_modules` holds 2.19.0, and that copy
contains `hasError()`. eslint and prettier pass.
…1269) First live-checked batch of #1261. Three of the seven segment-P&L aggregations translate cleanly; four cannot be translated at all. TRANSLATED — GLLine.byCostCenter / byCostObject / byProject Each declared `source: "GLLine"` on GLLine itself. That key is inert: AggregationRunner reads only field/filter/from/groupBy/join/metric/ metrics/select/where. It is emphatically NOT the engine's `from`, which switches the runner into its cross-schema path and needs a parent row — translating `source` to `from` would have changed the execution mode, not the spelling. Since source == the declaring schema, dropping it is the whole change, plus `sum: ["amount"]` -> `metric: "sum", field: "amount"`. VERIFIED AGAINST THE ROWS, not for a non-empty response. Seeded two GLLine rows carrying codes and compared with the underlying data: byCostCenter CC-001=5000 CC-002=10000 null=24200 byCostObject KD-001=5000 KD-002=10000 null=24200 byProject PRJ-001=5000 PRJ-002=10000 null=24200 24200 is exactly 12100 + 10000 + 2100, the three pre-existing rows that carry no codes. Sums and grouping both correct, and the null bucket proves the grouping discriminates rather than collapsing everything. NOT TRANSLATED — these need ENGINE FEATURES, not a rewrite: byCostCenterHierarchy groups by `AnalyticalDimension.parentCode`, a field on the JOINED schema byAnalyticalDimension groups by the wildcard `dimensions.*` segmentPnl conditional metrics — `sum WHERE side='debit'`. segmentPnlByCostObject Checked: the engine has no `condition` concept anywhere in lib/Service/Aggregation. spentToDate `recursiveOver` plus an OR expression inside a filter VALUE That is the useful finding from this batch: part of #1261 is not badly written declarations but capabilities OpenRegister does not have. They need engine work or an accepted PHP service, and no amount of renaming will help. Ratchet 219 -> 216. The fragment test now pins which two are translated and which two deliberately are not, so the gap stays visible. Verified: 4980 tests / 46766 assertions, 0 failures; 6 JS validators; prettier; the new groupBy gate still passes.
| await leaf.click() | ||
|
|
||
| await expect(page).toHaveURL( | ||
| new RegExp(`${REQUISITIONS_ROUTE.replace(/\//g, '\\/')}$`), |
| await entry.locator('a.app-navigation-entry-link').first().click() | ||
|
|
||
| await expect(page).toHaveURL( | ||
| new RegExp(`${OVERVIEW_ROUTE.replace(/\//g, '\\/')}$`), |
| await openLink.click() | ||
|
|
||
| await expect(page).toHaveURL( | ||
| new RegExp(`${SAMPLE_VIEW_CARD.route.replace(/\//g, '\\/')}$`), |
| await link.click() | ||
|
|
||
| await expect(page).toHaveURL( | ||
| new RegExp(`${GENERATED_ROUTE.replace(/\//g, '\\/')}$`), |
The largest remaining gap in the fleet. The manifest is data the renderer walks, not source the l10n extractor scans, so nav labels, page titles, descriptions, setup-wizard copy and the getting-started tour all looked up keys that were never in the catalogue. A missing key falls back to the English source, so a Dutch user read English across nearly the whole app. 2,213 strings, spanning general ledger, AR/AP, VAT, payroll, treasury, consolidation, IFRS 15/16, pensions (IAS 19), inventory, EU funds and the Dutch public-sector reporting chain. This is regulated-domain Dutch. The terms are the ones the profession and the statutes use, not literal translations: Aging bucket -> ouderdomscategorie Dunning ladder -> aanmaningstrap 3-way match -> driewegmatch Deferred participants -> slapers DBO -> pensioenverplichting DTA / DTL -> actieve / passieve belastinglatentie Fair presentation -> getrouwheid Lawfulness -> rechtmatigheid Provisions -> voorzieningen Contingent liabilities-> niet in de balans opgenomen verplichtingen Beslagvrije voet, cesuur, dotatie, aanwending: kept as-is Statutory names are left exactly as the law writes them: Vpb, BTW, WBSO, KOR, SiSa, ENSIA, BBV, Iv3, RGS, BADO, BIK, RUDDO, Wet Fido, art. 29 OB, Archiefwet, AWR, AVG, Digipoort, Peppol, SEPA pain.001, ESRS, IFRS, RJ. TWO EDITORIAL SWEEPS, both deliberate. Requirement ids are stripped from user-facing text. Dozens of strings carried "REQ-BR-002", "REQ-CCD-001", "per REQ-AP-006 .. REQ-AP-008" and similar straight into a page description. Those belong in the specs; a bookkeeper reading a page header does not need the ticket number, and the Dutch reads as an instruction rather than a changelog entry. App names are corrected on the way through: docudesk -> Filinq, "NC Files" -> "Bestanden". Referring users to an app under a name it no longer has is worse in translation than in English, because they cannot pattern-match back to the original. Then `l10n:build`, because nl.json alone ships nothing: the frontend reads OC.L10N from l10n/nl.js and a raw .json is not served from an app directory. Verified: 0 manifest strings left without Dutch, keys DROPPED = 0 against the pre-change catalogues, nl.js registers under "shillinq" with 5,196 keys and resolves "Balance Sheet" -> "Balans", "Payees" -> "Crediteuren", "Provisions" -> "Voorzieningen". test:l10n, test:l10n-dutch-tokens, check:schema-l10n and check:manifest all PASS.
…credit split (#1276) First aggregation translated onto openregister#2904's conditional metrics. `consolidatedTrialBalance` declared a filter as a STRING ("eliminationFlag = false"), a groupBy as a STRING, and its two figures under an `operations` dict whose per-entry `condition` was a SQL string. None of that is engine grammar, so it computed nothing at all. It now uses `filter` as an object, `groupBy` as an array, and two conditional `metrics` entries whose `as` aliases keep the original response keys — `totalDebit` and `totalCredit` — so no consumer changes. VERIFIED AGAINST THE ROWS on a live ledger: 1300 {"totalDebit": 12100, "totalCredit": 0} 8000 {"totalDebit": 0, "totalCredit": 10000} 1510 {"totalDebit": 0, "totalCredit": 2100} 4400 {"totalDebit": 15000, "totalCredit": 6000} Account 4400 carries rows on BOTH sides — debit 5000 + 10000, credit 3000 + 3000 — so one group exercises both conditions. Every figure matches its rows. Had the conditions been dropped, both columns would read 21000, which is the shape of wrong answer this whole programme keeps producing. Ratchet 216 -> 215.⚠️ Correction to my own estimate: I expected conditional metrics to unblock about ten accounting aggregations. It unblocks ONE. TrialBalance. trialBalanceTotals and BalanceSheet.balanceSheetTotals aggregate `GLLine.amount` while being declared on other schemas — that is cross-schema, a different capability — and both also want an `operation: "check"` with an expression. Detail and the corrected blocker table are on #1261. Verified: 4980 tests / 46766 assertions, 0 failures; 8 JS validators; prettier; both new gates still pass. Refs #1261
Quality Report — ConductionNL/shillinq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-manifest-shell | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-manifest-budget | ✅ | ||||
| check-markers | ✅ | ||||
| check-registers | ✅ | ||||
| check-seeds | ✅ | ||||
| check-fragment-required | ✅ | ||||
| check-nav-reachability | ✅ | ||||
| check-job-registration | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 551/551 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-27 10:52 UTC
Download the full PDF report from the workflow artifacts.
…1279) * fix(aggregations): gate the BARE half of the field-reference check checkAggregationFieldRefs() opens with `if (dot === -1) continue`, so it only ever validated `Schema.field` refs. The bare form — `groupBy: ["fiscalYearId"]`, `filter: { costCenterCode: ... }` — was never looked at, and it is the larger half: 120 of the 451 bare references now checked resolve to no declared property at all. None of them errored. A bare groupBy on a property the target schema does not declare groups every row into ONE null bucket; a bare filter key that resolves to nothing narrows to zero rows. Both answer HTTP 200 with a figure that looks like an answer, which is why the class never surfaced as a bug. The bare form was skipped because "the source schema would need resolving, which source/sourceSchema spell inconsistently across fragments". The engine settles it: AggregationRunner reads `from` and nothing else — `source` and `sourceSchema` are inert keys it never consults. The target is `from` when present and the declaring schema otherwise, exactly as the runner computes it, so the ambiguity that justified skipping this is gone. Baselined at 120 and ratcheted, not waived. Also in this change: - The three segment-P&L declarations (Project, AnalyticalDimension x2) drop the `@self.code` correlation. It cannot work: no caller supplies a parent row — AggregationController, ReportRenderService and ThresholdEvaluationService all invoke AggregationRunner::run() without one — so `@self.code` resolved to null, and a null is applied as a real filter VALUE. Each would have returned the unassigned total, confidently, for every parent record. The correlating field becomes a groupBy DIMENSION instead, which needs no parent row and is narrowed per-record through the extraFilter query params the REST endpoint already passes through. - `kostenDragerCode` -> `costCarrierCode`. GLLine declares no kostenDragerCode, so that filter matched nothing. - byCostCenterHierarchy gains `metric: sum` / `field: amount` and an explicit `on: {costCenterCode: code}` map. The `on` shorthand names only the joined side, leaving the parent key inferred — which produced one '' bucket instead of one per cost-center (OpenRegister #2916). - The @self placeholder check no longer exempts `where` on a cross-schema spec. That exemption assumed the parent row was supplied; it is not. `GLLine.fiscalYearId` stays as-is and stays baselined. GLLine has no fiscal-year property, and periodId is a FINER grain — substituting it would silently change what these roll-ups group by, which is a schema decision rather than a rename. Refs #1261 * style: prettier formatting for the bare-ref gate * chore(gate): lower AGG_NO_METRIC_BASELINE to 211 after #1276 #1276 (consolidated trial balance) gave one more aggregation a real metric/metrics pair, so the ratchet correctly refused to stay at 212. The number is the gate's own measurement, not a derived estimate.
Quality Report — ConductionNL/shillinq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-manifest-shell | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-manifest-budget | ✅ | ||||
| check-markers | ✅ | ||||
| check-registers | ✅ | ||||
| check-seeds | ✅ | ||||
| check-fragment-required | ✅ | ||||
| check-nav-reachability | ✅ | ||||
| check-job-registration | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 551/551 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-27 12:57 UTC
Download the full PDF report from the workflow artifacts.
…xists (#1284) Documentation only. The held step stays held, and is deliberately NOT registered by this change. The hold cited "issue #503", which is the CODEBERG issue number. On GitHub #503 resolves to an unrelated MERGED pull request about manifest gates, so anyone following the reference lands on finished work and the hold reads as stale. It migrated to GitHub as #1094 ("Phase 3/4 + live-verify the held FoldIntoOrder migration"), which is still open and does describe the hold. Both citations are corrected. Why a stale reference matters more here than usual: gate-98 (repair-step-registration) flags this step as "written but never registered in appinfo/info.xml, so Nextcloud will not run them", and it cannot tell a deliberate hold from an oversight. Its suggested remedy is to register the step. On this step that remedy deletes irreversible source rows and removes the rollback window the hold exists to preserve - the fold must lead the delete by at least one release. This comment is the only thing between that advice and real data, so its reference has to resolve to something that explains itself. Found while clearing gate-98 across the fleet: integriq (#1603), filinq (#836) and opencatalogi (#1145) had genuinely forgotten steps and are registered; shillinq's is the one that must not be.
* fix(deps): development cannot npm install A bare `npm install` on development fails with ERESOLVE. The quality suites pass because they install with --legacy-peer-deps, which accepts a tree only npm-with-an-existing-lock can produce, never one a fresh clone can. npm reports one conflict at a time, so this had to be peeled layer by layer. Every layer is the same shape: a bump landed past what a toolchain package's peer range allows. gridstack ^13.2.0 -> ^12.6.0 @babel/core ^8.0.1 -> ^7.22.9 postcss-html ^2.0.0 -> ^1.0.0 sass-loader ^16.0.8 -> ^17.0.0 stylelint-config-html ^2.0.0 -> ^1.1.0 stylelint-config-recommended-vue ^2.0.0 -> ^1.6.1 webpack-cli ^7.2.2 -> ^6.0.1 @babel/preset-env 8 requires @babel/core ^8.0.0 while @nextcloud/webpack-vue-config@7.0.4 requires ^7.22.9 -- no version satisfies both, so the babel pins revert together. Moving one alone is what produced the deadlock, and is why the babel-8 dependabot PRs cannot land individually. gridstack is pinned to ^12.6.0 so development installs now. ConductionNL/nextcloud-vue#801 widens that peer to accept 13 (measured: identical build and 6752 passing tests on both majors); dependabot can raise it again once 2.20.0 publishes. Verified with no --legacy-peer-deps: npm install rc=0 (was ERESOLVE), build rc=0, lint rc=0, stylelint rc=0. * 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>
Quality Report — ConductionNL/shillinq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ⏭️ | ||||
| phpcs | ⏭️ | ||||
| phpmd | ⏭️ | ||||
| psalm | ⏭️ | ||||
| phpstan | ⏭️ | ||||
| phpmetrics | ⏭️ | ||||
| eslint | ⏭️ | ||||
| stylelint | ⏭️ | ||||
| build | ⏭️ | ||||
| composer | ⏭️ | ⏭️ | |||
| npm | ⏭️ | ⏭️ | |||
| app:check-code | ⏭️ | ||||
| info.xml | ⏭️ | ||||
| REUSE | ⏭️ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-27 13:43 UTC
Download the full PDF report from the workflow artifacts.
* fix(clean-env): point the vendored skill at a script that exists This repo carries a copy of hydra's clean-env skill, and the copy told you to run `bash .claude/scripts/clean-env.sh` -- a file that does not exist here, in hydra, or in any other checkout. Its app list was five names long against a fleet of twenty-one, and three of those five (softwarecatalog, docudesk-era names, mydash) are app directories or retired apps rather than app ids, so following it by hand enables nothing. Synced from hydra's canonical version. It now drives `.github/dev-up.sh`, and its verification section names what to actually check: an app enables successfully with no vendor/autoload.php and then fatals on every request, and enables successfully with a pre-rename JS bundle and then renders a blank page. Both were true of live apps in this workspace on 2026-08-27, and "confirm apps are listed and enabled" sees neither. * fix(repair): record WHY RetireSubsidieSchema is withheld, in the form gate-98 reads gate-98 shipped yesterday and fails this repo on RetireSubsidieSchema. The mechanism it reports is real -- the step's <step> entry is commented out, so Nextcloud will never run it -- but the conclusion is wrong: it is commented out on purpose. The reasoning was already written above it in prose (issue #503): the step irreversibly deletes the Subsidie rows FoldIntoOrder folded, so it must lag that fold by at least one release to leave a folded instance a rollback window. Doing what the gate asked -- registering it -- would have enabled a destructive step the team had deliberately withheld. The prose stays. This adds the same decision in the machine-readable form the gate now understands (.github#588), so the hold is recorded once and read by both a person and the gate: <!-- hydra-gate-98 held: OCA\Shillinq\Repair\RetireSubsidieSchema — ... --> The marker names this one class and carries the reason and the condition for lifting it; it does not suppress anything else. RetireSubsidieSchema remains unregistered and does not run.
Quality Report — ConductionNL/shillinq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-manifest-shell | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-manifest-budget | ✅ | ||||
| check-markers | ✅ | ||||
| check-registers | ✅ | ||||
| check-seeds | ✅ | ||||
| check-fragment-required | ✅ | ||||
| check-nav-reachability | ✅ | ||||
| check-job-registration | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 551/551 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-27 15:57 UTC
Download the full PDF report from the workflow artifacts.
gate-70 (walkthrough-flows-stop) failed on development itself: shillinq ships a `type:"flows"` page and no tour step pointed at it, so the automation surface was discoverable only to someone who already knew it was there. The gate measured the fleet on 2026-08-27: 19 apps declare a walkthrough, 12 ship a flows page, and exactly ONE tour mentioned flows at all. That is the same shape as a feature that ships and is never found. The new stop is view-only by construction: `optional: true`, `allowManualNext: true`, and it advances on `route-match` rather than `object-created`. The gate's sibling rule (forcing-flows-stop) exists precisely so "here is where flows live" cannot become "build one first" — a tour you cannot finish is worse than one that stays quiet, because the user never reaches the end to learn what else exists. Placed before `done` so the tour still closes on its closing step. ## Three traps this had to avoid - The copy carries no em-dash or double-dash. An earlier draft did, and gate-96 (manifest-copy-style) failed it against voice.md section 8. - The manifest is written with tabs and compact one-line objects. Re-serialising it through json.dumps reformatted all 19,204 lines while changing nothing, so this is a targeted textual insert: 12 added lines, nothing else moved. - The l10n catalogues are NOT stored in sorted order. Sorting them on write rewrote all 5,196 entries and buried the three real additions, so the writer now re-sorts only a catalogue that was already sorted. ## Translated, and the artefact rebuilt The three new strings are in l10n/en.json and l10n/nl.json, and l10n/*.js is rebuilt. Every other walkthrough string is already in the Dutch catalogue, so an untranslated stop would render in English inside an otherwise Dutch tour. No gate checks that — check:l10n-js passes either way — which is why it is done here rather than left to CI. ALL 68 applicable hydra gates green, and all 68 ran. check:manifest, check:manifest-budget, check:nav-reachability, check:l10n-js, test:l10n, check:schema-l10n and check:markers all exit 0.
`InventoryTransfer.quantity`, `OrderPick.quantity` and `InventoryReorderRule.reorderQuantity` declared `minimum: 0` plus `exclusiveMinimum: true` — the draft-04 spelling. Every draft this repository validates against expects a NUMBER there, so those three schemas are not just mis-declared, they are unvalidatable: a validator rejects the schema before it ever looks at an object. This file already uses the correct numeric form elsewhere (`distance`, `ratePerKm`), so these three were the outliers, not the convention. `minimum: 0` + `exclusiveMinimum: true` becomes `exclusiveMinimum: 0`, which is the same constraint — strictly positive — in a spelling the validator understands.
Three objects per (register, schema) pair, each generated from the schema itself and validated against it — 1497 objects, 0 failures. Not installed automatically: a mock register is imported on demand, from the setup walkthrough or `occ openregister:descriptors:list --app=shillinq --import=<slug>`.
…ransaction (#1287) * feat(glline): declare fiscalYearId, and backfill it from the parent transaction GLLine declared no fiscal-year property at all. Three segment-P&L roll-ups grouped by `GLLine.fiscalYearId` regardless, so every row landed in ONE null bucket — a plausible total rather than an error, which is why it survived. The gate had it waived with the note that it needed a schema decision. This is that decision. `periodId` was deliberately NOT reused. A period is a FINER grain than a year, so grouping by it would have silently changed what those roll-ups mean — twelve buckets where the consumer expects one — instead of fixing them. That is the same shape of defect, not a fix for it. ## What lands - `GLLine.fiscalYearId` (string, nullable), denormalised from the parent `GLTransaction.fiscalYearId`. The name and type match the six schemas that already declare it (GLTransaction, BalanceSheet, TrialBalance, ConsolidatedReport, ClosingEntry, kernGegevensConfig). - `GlLineFiscalYearBackfillMigrator` — the pure resolution core, modelled on GlLineAdministrationBackfillMigrator. Indexes each transaction under every identity a line may reference it by (id, @self.id, uuid, transactionNumber), because `transactionId` has been written as each of those at different points. - `BackfillGlLineFiscalYear` — the repair step, registered post-migration only (a fresh install has no historical lines to stamp). - The `GLLine.fiscalYearId` waiver is removed from AGGREGATION_REF_BASELINE. The dotted-reference gate now passes with NO baselined entries at all. - `AGG_BARE_REF_BASELINE` 120 -> 116. Declaring the property also resolved four BARE `fiscalYearId` references in other aggregations, which the ratchet caught and refused to let pass unrecorded. ## Reporting rather than gating, deliberately The administration backfill closes a config gate and refuses to reopen it unless a re-read proves completeness. That is right for `administrationId`: it is a tenant SCOPE, and a half-scoped ledger makes a filter return a silent zero. `fiscalYearId` is a GROUPING key. An unresolved line is not a leak and zeroes nothing — it appears as a null bucket, which is visible in the result. Aborting every resolvable line because one ancient row lost its transaction would trade a visible gap for no backfill at all. So the step stamps what resolves and REPORTS what did not, by re-reading the store afterwards and counting the whole set. The count is emitted even when it is zero, so "nothing left behind" is something the operator read rather than assumed. A line that already carries a year is never rewritten, even when its parent now disagrees — that disagreement is reported instead, because re-pointing a posted line is a bigger decision than a backfill gets to make. ## Verification - 10 new unit tests, 37 assertions - Mutation control: removing the no-overwrite guard makes the suite fail, and restoring it makes it pass again — the tests can detect the thing they claim - Full suite 4990 tests, 0 failures - validate-registers exits 0 with every gate at its baseline - Parsed-tree diff confirms the register edit added exactly the five `fiscalYearId` leaves and touched nothing else Refs #1261 * fix(l10n): add the fiscalYearId description to the catalogues and rebuild the JS check:schema-l10n went one over its baseline: the new GLLine.fiscalYearId description had no catalogue key, so it would have rendered in English inside an otherwise translated form. Both artefacts are updated, not just the source. l10n/*.json is what the check reads; l10n/*.js is what the browser actually loads, and a translation present only in the JSON reaches nobody. * docs(spec): tag the fiscal-year backfill methods with @SPEC gate-16 (spec-coverage) failed with 8 changed methods missing @SPEC. Found by running the real gate runner locally against origin/development as the delta base — the CI run was still queued behind a saturated runner pool, and without an explicit base the gate reports NOT APPLICABLE rather than passing, so it would have judged nothing. Tags point at openspec/specs/bookkeeping-cost-centers-dimensions/spec.md #req-cc-005 — the segment-P&L requirement that motivated the property — rather than at an openspec/changes/ directory. Archiving a change breaks every @SPEC tag pointing into it, and most existing tags in this repo have that shape. Also removes a tag that landed on the CLASS_UNRESOLVABLE constant docblock instead of a method, and adds the two the first pass placed on neighbouring docblocks (run, assertCountsMatch) — verified by re-running the gate, not by counting matches. gate-16 PASS, gate-98 PASS. * fix(l10n): stop re-sorting the catalogues, which rewrote all 5,196 entries The previous commit added ONE string and produced a 4,473-line diff on l10n/nl.json. The writer called dict(sorted(...)) on every catalogue, and these catalogues are not stored in sorted order — so the whole file was reordered and the single real addition was buried in it. That is not cosmetic. A reviewer cannot see a one-line change inside a four-thousand-line reordering, and the next person to touch l10n gets a conflict against every entry rather than against the line that moved. The catalogues are now written back in their ORIGINAL key order, re-sorting only a file that was already sorted. Net effect of this branch on l10n drops from ~4,500 changed lines to 8. Verified against origin/development, not against the branch tip: the previous commit looked small there too until the base was made explicit.
…ADR-099 §3) (#1283) * refactor(lib): collapse three single-class namespaces into Service/ (ADR-099 §3) Consolidation/ConsolidationGuard -> Service/ConsolidationGuard Integration/TenderNedStatusSync -> Service/TenderNedStatusSync Recognition/RevenueRecognitionService -> Service/RevenueRecognitionService ## A string reference nearly shipped broken, and only a test caught it `ConsolidationGuard` is named as an FQCN STRING six times in `lib/Settings/shillinq_register.json`: "requires": "OCA\\Shillinq\\Consolidation\\ConsolidationGuard::requireFiscalPeriodClosed" I grepped for exactly this before moving anything and got ZERO hits — the pattern's backslash escaping did not match the JSON's doubled backslashes. The move went ahead on a measurement that was wrong. `RegisterLifecycleGuardsResolveTest` caught it: *"class OCA\Shillinq\Consolidation\ConsolidationGuard does not exist"*. Without that test, five lifecycle guards would have silently stopped resolving — the failure mode being a guard that no longer runs, which is invisible until something passes that should have been refused. All six strings are updated and the register JSON still parses. 🔑 A grep that returns zero is only evidence if you have seen it return non-zero for the same shape. ## Five directories the rule should NOT have taken Applying "one class collapses into Service/" literally to shillinq's eleven single-FILE directories catches things that are not services: - **`Data/`** holds `nl_vat_rates_2026.json` — a DATA FILE, no class at all. The count that flagged it counted files, not classes. - **`Request/InvoiceGenerationRequest`** is a DTO. - **`Enums/DBAConstants`** is a constants holder. - **`Support/FleetAppId`** is the shared helper five fleet apps keep at that exact path. - **`Sort/FefoSort`** is a strategy, arguably either way; left alone rather than moved on a coin-flip. Together with learniq#655 — where moving three INTERFACES broke a test that guards ADR-022's "no new PHP" decision — Decision 3 needs carve-outs for interfaces, data directories, DTOs, enums and `Support` before it can be mechanised or gated. ## Verification - 4,980 tests / 46,776 assertions, no failures. The suite was run BEFORE committing precisely because the last app's collapse broke an invariant. - phpcs 0 errors, phpstan `[OK] No errors`, `check:register` passes, after `composer dump-autoload`. * docs: correct the verification claim in the previous commit The previous message said "`check:register` passes". There is no such script — it is `check:registers`, plural, and my invocation exited 1 because npm could not find the name, not because anything failed. Run correctly it PASSES: [validate-registers] PASS — every checkable aggregation field reference resolves [validate-registers] PASS — every metric-bearing aggregation declares groupBy as an array [validate-registers] PASS — every bookkeeping + procurement schema declares x-openregister-audit-trail.enabled=true Recording it rather than quietly amending: a verification claim in a commit message is evidence, and one taken from a command that never ran is worth exactly nothing. The underlying change is unaffected. * test(listener): cover the fail-soft contract the coverage ratchet named The PHPUnit pgsql cell failed on the coverage guard, not on a test — 4980 tests passed and then "coverage of the code this change KEEPS or ADDS dropped by 0.36%" (330/383 = 86.16% against an 86.52% base). This namespace collapse pulls five files into diff scope, and their untested branches came with them. Reproduced the guard's number locally with pcov (330/383 exactly, same scope) and covered two real contracts on OrderFulfilmentTransitionListener: - **an event of another type is ignored** — the dispatcher hands `Event`, so the instanceof guard is all that stands between this listener and a `getTo()` that does not exist on that class; - **a throwing sync is swallowed, and the milestone still stands** — the REQ-006 fail-soft contract. This listener runs inside another object's transition, so a throw would roll that transition back; the milestone event fires before the sync, so it must survive the sync failing. 336/383 = 87.73%, above the base. I also wrote a test for the `is_array($oplevering) === false` branch and then deleted it: `ObjectEntity::getObject(): array` is return-typed and coerces null to `[]`, so that branch is unreachable from here. Leaving a test that cannot fail would have been worse than the gap — the branch is dead defensive code, noted rather than papered over. Remaining uncovered in scope (not introduced here, not required by the ratchet): RevenueRecognitionService 18, TenderNedStatusSync 15, ConsolidationGuard 8, RecognitionController 4. Every check passes: lint, phpcs, phpmd, psalm, phpstan, test:all. (`composer check:strict` needs COMPOSER_PROCESS_TIMEOUT=0 locally — the composite exceeds composer's 300s default and reports that as a failure.)
…eep (#1288) * feat(glline): declare fiscalYearId, and backfill it from the parent transaction GLLine declared no fiscal-year property at all. Three segment-P&L roll-ups grouped by `GLLine.fiscalYearId` regardless, so every row landed in ONE null bucket — a plausible total rather than an error, which is why it survived. The gate had it waived with the note that it needed a schema decision. This is that decision. `periodId` was deliberately NOT reused. A period is a FINER grain than a year, so grouping by it would have silently changed what those roll-ups mean — twelve buckets where the consumer expects one — instead of fixing them. That is the same shape of defect, not a fix for it. ## What lands - `GLLine.fiscalYearId` (string, nullable), denormalised from the parent `GLTransaction.fiscalYearId`. The name and type match the six schemas that already declare it (GLTransaction, BalanceSheet, TrialBalance, ConsolidatedReport, ClosingEntry, kernGegevensConfig). - `GlLineFiscalYearBackfillMigrator` — the pure resolution core, modelled on GlLineAdministrationBackfillMigrator. Indexes each transaction under every identity a line may reference it by (id, @self.id, uuid, transactionNumber), because `transactionId` has been written as each of those at different points. - `BackfillGlLineFiscalYear` — the repair step, registered post-migration only (a fresh install has no historical lines to stamp). - The `GLLine.fiscalYearId` waiver is removed from AGGREGATION_REF_BASELINE. The dotted-reference gate now passes with NO baselined entries at all. - `AGG_BARE_REF_BASELINE` 120 -> 116. Declaring the property also resolved four BARE `fiscalYearId` references in other aggregations, which the ratchet caught and refused to let pass unrecorded. ## Reporting rather than gating, deliberately The administration backfill closes a config gate and refuses to reopen it unless a re-read proves completeness. That is right for `administrationId`: it is a tenant SCOPE, and a half-scoped ledger makes a filter return a silent zero. `fiscalYearId` is a GROUPING key. An unresolved line is not a leak and zeroes nothing — it appears as a null bucket, which is visible in the result. Aborting every resolvable line because one ancient row lost its transaction would trade a visible gap for no backfill at all. So the step stamps what resolves and REPORTS what did not, by re-reading the store afterwards and counting the whole set. The count is emitted even when it is zero, so "nothing left behind" is something the operator read rather than assumed. A line that already carries a year is never rewritten, even when its parent now disagrees — that disagreement is reported instead, because re-pointing a posted line is a bigger decision than a backfill gets to make. ## Verification - 10 new unit tests, 37 assertions - Mutation control: removing the no-overwrite guard makes the suite fail, and restoring it makes it pass again — the tests can detect the thing they claim - Full suite 4990 tests, 0 failures - validate-registers exits 0 with every gate at its baseline - Parsed-tree diff confirms the register edit added exactly the five `fiscalYearId` leaves and touched nothing else Refs #1261 * fix(l10n): add the fiscalYearId description to the catalogues and rebuild the JS check:schema-l10n went one over its baseline: the new GLLine.fiscalYearId description had no catalogue key, so it would have rendered in English inside an otherwise translated form. Both artefacts are updated, not just the source. l10n/*.json is what the check reads; l10n/*.js is what the browser actually loads, and a translation present only in the JSON reaches nobody. * fix(aggregations): translate the legacy DSL — batch 1 of the #1261 sweep Forty-one aggregations move from keys AggregationRunner never reads to keys it does. None of them errored before; they returned an empty result or nothing at all, under HTTP 200. ## 19 `@self` correlations become groupBy dimensions `filter: { poolId: "@self.id" }` on an intra-schema aggregation does not resolve. PlaceholderResolver acts only on `$`-prefixed values, so `@self.id` stayed a LITERAL STRING and matched no row — an empty result that looks like "no data" rather than "this never worked". And it could not have worked: no caller supplies a parent row. AggregationController, ReportRenderService and ThresholdEvaluationService are the only three call sites of `run()` in OpenRegister, and none passes one. Grouping by the same field needs no parent row and answers for every record at once; a caller narrows to one through the `extraFilter` query params the REST endpoint already passes through. ## 22 `operations` maps become `metrics` `operations` is not an engine key either. Each entry is `{field, operation, target}` — which is exactly `{field, metric, as}` — so the translation is mechanical. `target` duplicated the map key; both became `as`. Where `field` was written qualified (`RetainerDrawdown.drawdownAmount`) the prefix is stripped, since `from` already resolves bare names on that schema. Conditional entries carry through as `condition`, which the runner honours since openregister #2917. ## What was deliberately NOT translated - 33 `operations` maps containing an `expression` op. The engine has no equivalent, and the conditions are SQL-ish STRINGS ("VATLine.type = 'collected'") where computeMetrics() takes a filter OBJECT. Guessing either would produce a confident wrong number, which is the failure mode this whole sweep exists to remove. `BtwAangifte.totalsByReturn` is pinned in its test as still-untranslated so the gap stays visible. - `ExpenseClaimEntry.settlementTotals`. It uses a multi-source `sources` array whose "amountField"/"customerField" are CONFIG KEYS of a source descriptor, not fields. An earlier pass of this change translated them into metrics; that was nonsense and is reverted. ## Two defects the gates caught mid-change Translating `operations` into `metrics` moved those field names into a key the bare-reference gate can SEE, and the count rose from 102 to 111. That is not new debt — it is debt that was previously invisible: - `ProvisionDisclosureTabel.provisionDisclosureGeneration` kept `source` instead of `from`, so its five movement fields resolved against the wrong schema. - `IBProfitAttribution.innovatieboxAdministratie` summed `kwalificerende_winst_na_nexus`; the schema declares `qualifying_profit_after_nexus`. A Dutch/English mismatch that matched nothing. - `GhgInventory.scope3ByCategory` tripped the string-groupBy gate the moment it gained metrics — a STRING groupBy is silently dropped and yields one ungrouped total. All three are fixed here, so the bare-ref count returns to 102 with no baseline raised. ## Ratchets AGG_PLACEHOLDER_BASELINE 81 -> 62 (exactly the 19 converted) AGG_NO_METRIC_BASELINE 206 -> 185 AGG_BARE_REF_BASELINE 102 -> 102 (unchanged, after the three fixes) ## Verification - Parsed-tree diff against HEAD: exactly the intended aggregations changed, ZERO non-aggregation leaf changes across all 14 files - Only files whose JSON round-trips byte-identically were rewritten, so no file was silently reformatted - Five fragment tests updated from the old vocabulary to the engine's, each asserting the inert key is GONE rather than only that the new one is present - Full suite 4990 tests, 0 failures - validate-registers exits 0 with every gate at its baseline Stacked on #1287 (GLLine.fiscalYearId), whose baseline changes this builds on. Refs #1261 * docs(spec): tag the fiscal-year backfill methods with @SPEC gate-16 (spec-coverage) failed with 8 changed methods missing @SPEC. Found by running the real gate runner locally against origin/development as the delta base — the CI run was still queued behind a saturated runner pool, and without an explicit base the gate reports NOT APPLICABLE rather than passing, so it would have judged nothing. Tags point at openspec/specs/bookkeeping-cost-centers-dimensions/spec.md #req-cc-005 — the segment-P&L requirement that motivated the property — rather than at an openspec/changes/ directory. Archiving a change breaks every @SPEC tag pointing into it, and most existing tags in this repo have that shape. Also removes a tag that landed on the CLASS_UNRESOLVABLE constant docblock instead of a method, and adds the two the first pass placed on neighbouring docblocks (run, assertCountsMatch) — verified by re-running the gate, not by counting matches. gate-16 PASS, gate-98 PASS. * fix(l10n): stop re-sorting the catalogues, which rewrote all 5,196 entries The previous commit added ONE string and produced a 4,473-line diff on l10n/nl.json. The writer called dict(sorted(...)) on every catalogue, and these catalogues are not stored in sorted order — so the whole file was reordered and the single real addition was buried in it. That is not cosmetic. A reviewer cannot see a one-line change inside a four-thousand-line reordering, and the next person to touch l10n gets a conflict against every entry rather than against the line that moved. The catalogues are now written back in their ORIGINAL key order, re-sorting only a file that was already sorted. Net effect of this branch on l10n drops from ~4,500 changed lines to 8. Verified against origin/development, not against the branch tip: the previous commit looked small there too until the base was made explicit.
Quality Report — ConductionNL/shillinq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ⏭️ | ||||
| phpcs | ⏭️ | ||||
| phpmd | ⏭️ | ||||
| psalm | ⏭️ | ||||
| phpstan | ⏭️ | ||||
| phpmetrics | ⏭️ | ||||
| eslint | ⏭️ | ||||
| stylelint | ⏭️ | ||||
| build | ⏭️ | ||||
| composer | ⏭️ | ⏭️ | |||
| npm | ⏭️ | ⏭️ | |||
| app:check-code | ⏭️ | ||||
| info.xml | ⏭️ | ||||
| REUSE | ⏭️ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-27 17:30 UTC
Download the full PDF report from the workflow artifacts.
…y onboarded (#1296) #1292 added the `see-flows` stop and marked it `sinceVersion: "1.3.18"` — the same version as every other step in the tour. The manifest schema says what that field is for: "sinceVersion": Semver the step was introduced in; drives the "what's new" composition. So a step claiming to have existed since 1.3.18 is not new to anyone who already completed the 1.3.18 tour. The stop shipped, and the users it was written for would never have been shown it. That is the same defect gate-70 exists to catch, one level further in. The gate asks whether the flows surface is discoverable to someone who does not already know it is there; a what's-new entry dated before those users onboarded fails that test just as completely as having no step at all — and it fails it while the gate reports PASS, because the step IS present in the manifest. The step now declares 1.4.0, and the manifest version moves to 1.4.0 with it. The other four steps keep 1.3.18: they genuinely did exist then, and back-dating or forward-dating them would either re-show a finished tour or hide real history. Credit to #1291, which proposed the same stop independently and got this part right. Its copy could not land as written (em-dashes, which gate-96 refuses against voice.md section 8), but the version bump was the half I had wrong. ALL 67 applicable hydra gates green, and all 67 ran. check:manifest, check:manifest-budget, check:manifest-shell, check:l10n-js and test:l10n all exit 0.
…e 4) An app installed from the App Store opens on an empty list, and the only question its first reader has is whether they can see it work. Answering it needs data they cannot author, against a schema they do not know yet. A welcome screen answers a question nobody asked; it can still say hello from second place. `DemoDataService` imports this app's generated mock register through the same OpenRegister importer the app already uses for its real configuration. Two decisions worth stating: - `force: true`. OpenRegister version-gates a non-forced import and SKIPS silently when the version has not moved. An operator who asks for demo data and is told it worked, on an instance where nothing was written, has been lied to by a version compare. The request is explicit, so the import is. - Its own config identity (`<app>.demo`), so the demo import and the real configuration import cannot mask one another's version gate. `skip-demo-data` exists so "no thanks" is a decision the wizard can record — otherwise the only way past the step is to install demo data, which is wrong on a production instance. The status flag records that the step was DEALT WITH, not that demo objects exist. The cross-app getter returns `object`, not the OpenRegister class: naming a class from an optional app in a native return type makes PHP resolve it on every return, so an instance without OpenRegister fails with a TypeError about a class nobody mentioned instead of the RuntimeException that names the missing app. 7 unit tests, 13 assertions. phpcs clean, manifest validates against app-manifest-v2, gate-100 passes.
Quality Report — ConductionNL/shillinq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ⏭️ | ||||
| phpcs | ⏭️ | ||||
| phpmd | ⏭️ | ||||
| psalm | ⏭️ | ||||
| phpstan | ⏭️ | ||||
| phpmetrics | ⏭️ | ||||
| eslint | ⏭️ | ||||
| stylelint | ⏭️ | ||||
| build | ⏭️ | ||||
| composer | ⏭️ | ⏭️ | |||
| npm | ⏭️ | ⏭️ | |||
| app:check-code | ⏭️ | ||||
| info.xml | ⏭️ | ||||
| REUSE | ⏭️ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-27 18:24 UTC
Download the full PDF report from the workflow artifacts.
…hows The stop itself landed on development (#1291's manifest change), but its three user-facing strings did not reach any catalogue, so the tour renders them in English for every locale. Nothing would have reported it: this app's l10n checker scans src/ for t() calls and never reads src/manifest.json, so a manifest-only string is invisible to it in both directions. It cannot say the key is missing, and it would call it unused if you added it. opencatalogi hit the same blindness from the other side and fixed its checker in #1153. Dutch is written rather than machine-produced. "Flows" stays "Flows", matching the existing catalogue. The browser .js is rebuilt in the same change, because l10n/*.json is the source and l10n/*.js is what the browser actually loads.
… batch 2 (#1301) Five `operations` maps become `metrics`, now that OpenRegister #2941 gives the engine a derived metric. They were held back in batch 1 for two reasons that have both been removed: - `vatBalance` is an `expression` op, which the engine had no equivalent for. - The conditions were SQL-ish STRINGS ("VATLine.type = 'collected'") where computeMetrics() takes a filter OBJECT. The strings convert mechanically: `= 'x'` and `= true/false` become a literal, `IN (...)`/`IN [...]` becomes `{in: [...]}`, `IS NOT NULL` becomes `{ne: null}`. Field names lose the schema prefix, since `from` already resolves bare names. `AGG_NO_METRIC_BASELINE` 185 -> 180. Bare refs unchanged at 102 — nothing new became visible this time. ## The mistake this batch made first, and the gate that now prevents it The `operations` maps key their entries one way and name their `target` another. Expressions were written against the KEY. Translating the metrics list to be keyed by `target` therefore left every expression naming an alias that no longer existed. A first pass produced 41 such references across 13 aggregations. Every one would have raised on first use — correctly, because the evaluator refuses an unknown alias rather than resolving it to 0, but at RUN time, on whatever dashboard opened it. Two things came out of that: 1. The translation rewrites expression identifiers from the map key to the emitted alias, and REFUSES any aggregation whose expression still names something undeclared. That is why this batch is five and not thirteen: eight of them reference FIELDS (`eigen_rd_kosten`, `dboOpening`), which makes them row-level formulas rather than arithmetic over sibling metrics — a different capability, not this one. 2. `checkAggregationExpressionAliases()` now fails the build on a derived metric naming an alias that is not declared BEFORE it. Verified by injecting a typo into a real expression: the gate names the identifier and lists what was available, and clears when reverted. `ExpenseClaimEntry.settlementTotals` stays untranslated, again. It carries a multi-source `sources` array across three schemas; a flat metrics list cannot express that, and the translator now refuses anything declaring `sources` rather than producing the nonsense a previous pass did. ## Verification - Parsed-tree diff: exactly the 5 intended aggregations, ZERO non-aggregation leaf changes - Only round-trip-stable files were rewritten, so nothing was reformatted - Two pinned tests updated — both were pins I wrote in batch 1 saying "still untranslated", and both fired the moment that stopped being true - Full suite 4992 tests, 0 failures - validate-registers exits 0 with every gate at its baseline Refs #1261
… budget `check:manifest-budget` failed at 1,128,882 B against a 1,128,750 B budget — 132 B over. Every byte of this JSON ships in the main webpack chunk on every page load, so the tripwire is doing real work and is worth respecting. 🔴 THE HEADROOM WAS ALREADY GONE BEFORE THIS BRANCH. `development` had grown to 1,128,545 B on its own, leaving 205 B — 0.018% — against the 0.58–0.88% ratio this file argues for. The tripwire was one small edit away from firing for whoever touched the manifest next; what it caught here was not a payload problem so much as its own exhausted margin. TRIMMED FIRST, then raised, which is the procedure this file's own notes set out. ADR-111 rule 4 requires `setup.steps[0]` to be the demo-data offer, so the step is not negotiable — its prose is. The body went from 200 characters to 76, recovering 124 B, and still states the two things an operator needs: what lands, and not to run it on production. Raised to 1,135,500 B — 6,742 B of headroom, 0.59%, back inside the ratio and close to the 0.58% the check ran with two bumps ago. Deliberately not more: the point of restating the ratio is that the next change re-measures rather than inheriting slack. Also merges development, which is where the 626 B of drift came from and why this reproduced only against the merge result, not against the branch alone. Verified: budget PASS, gate-100 PASS, manifest validates against app-manifest-v2 with 0 errors, l10n 0 failures, demo data 499 schemas checked.
Quality Report — ConductionNL/shillinq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-manifest-shell | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-manifest-budget | ✅ | ||||
| check-markers | ✅ | ||||
| check-registers | ✅ | ||||
| check-seeds | ✅ | ||||
| check-fragment-required | ✅ | ||||
| check-nav-reachability | ✅ | ||||
| check-job-registration | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 551/551 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-27 20:19 UTC
Download the full PDF report from the workflow artifacts.
…r expects it
Adding `DemoDataService` to the constructor for ADR-111 rule 4 broke the
existing SetupController tests, and I did not grep for callers when I changed
the signature. buildiq's CI named it on all three NC versions:
TypeError: SetupController::__construct(): Argument #6 ($demoDataService)
must be of type OCA\Buildiq\Service\DemoDataService,
MockObject_SettingsService_f805ce68 given
A positional construction does not fail where the argument is missing — it
fails one slot LATER, reported as a type error on whatever now lands in the
wrong place. That is why it reads as a problem with $settings.
Swept all five apps that gained the step: three construct the controller in
tests. openbuild and opencatalogi positionally (opencatalogi twice — a
match-count assertion caught the second, which a single blind replace would
have missed), shillinq by named argument, where the new parameter is simply a
missing required one rather than a shifted slot.
Verified: openbuild 3 tests, shillinq 10, opencatalogi 16 — all green.
Quality Report — ConductionNL/shillinq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-manifest-shell | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-manifest-budget | ✅ | ||||
| check-markers | ✅ | ||||
| check-registers | ✅ | ||||
| check-seeds | ✅ | ||||
| check-fragment-required | ✅ | ||||
| check-nav-reachability | ✅ | ||||
| check-job-registration | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 551/551 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-28 21:48 UTC
Download the full PDF report from the workflow artifacts.
) The install arm timed out at 60s. Not a defect: the same assertion PASSED at 49.6s on the previous run. The largest app seeds 1497 objects where the smallest seeds 30, so the call sits on the default boundary and flips between runs. `test.slow()` rather than a blanket timeout bump — it triples the budget for this one arm and records why, leaving every other arm strict. 🔴 THE ASSERTION IS WORTH ITS COST. It is the only check that the install actually WROTE something; the defect it exists for is an import that reported `register "x" imported.` while seeding ZERO objects and passed every unit test. Making it cheap by not importing would delete the property under test. This branch deliberately does NOT touch ci-seed.sh: another session settled the demo-data decision on development first, via `occ config:app:set <app> demo_data_decided --value=skipped`, which does the same job as the `skip-demo-data` API call I had proposed. Theirs landed, so only the timeout fix remains here.
Quality Report — ConductionNL/shillinq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-manifest-shell | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-manifest-budget | ✅ | ||||
| check-markers | ✅ | ||||
| check-registers | ✅ | ||||
| check-seeds | ✅ | ||||
| check-fragment-required | ✅ | ||||
| check-nav-reachability | ✅ | ||||
| check-job-registration | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 551/551 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ❌ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-28 22:03 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/shillinq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-manifest-shell | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-manifest-budget | ✅ | ||||
| check-markers | ✅ | ||||
| check-registers | ✅ | ||||
| check-seeds | ✅ | ||||
| check-fragment-required | ✅ | ||||
| check-nav-reachability | ✅ | ||||
| check-job-registration | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 551/551 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-28 22:31 UTC
Download the full PDF report from the workflow artifacts.
The seed collector reads BOTH buckets, and says so:
// ImportHandler reads BOTH buckets:
// foreach ([$data['components']['objects'], $data['objects']] ...
for (const obj of asArray(doc.objects)) ...
for (const obj of asArray(doc.components && doc.components.objects)) ...
The schema lookup two dozen lines later read only one:
const schemas = (merged.components && merged.components.schemas) || {}
So the gate was narrower than the set it collects, and it failed in both
directions. A seed declared against a top-level schema was reported as
naming a schema "no fragment declares" — a false alarm — and, worse, its
`required` list was never checked at all. This is a RATCHET: a class it
cannot see silently lowers the number it guards, which is the failure mode
a ratchet exists to prevent.
Found by running this validator against dossiq, which declares
`stufEndpoint`, `stufMessage` and `zaaksysteemMapping` at the top level of
`register.d/80-stuf-zkn-outbound.json`. It reported that fragment's seed as
orphaned; the schema is right there.
NO EFFECT ON THIS REPO'S NUMBERS, verified rather than assumed: shillinq
declares 820 schemas under `components.schemas` and ZERO at the top level,
so the widened lookup returns the same set. Before and after, identically:
seed objects checked: 746
seeds naming a schema no fragment declares: 1
seeds that cannot satisfy their schema: 53 (baseline 53)
Positive control on dossiq: the false alarm goes 1 -> 0 with the same
patch, and its 192 seeds still report 0 unsatisfiable.
…oth-schema-shapes fix(validate-seeds): read both schema shapes, as the seed collector does
Quality Report — ConductionNL/shillinq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-manifest-shell | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-manifest-budget | ✅ | ||||
| check-markers | ✅ | ||||
| check-registers | ✅ | ||||
| check-seeds | ✅ | ||||
| check-fragment-required | ✅ | ||||
| check-nav-reachability | ✅ | ||||
| check-job-registration | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 551/551 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-29 00:37 UTC
Download the full PDF report from the workflow artifacts.
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>
Quality Report — ConductionNL/shillinq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-manifest-shell | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-manifest-budget | ✅ | ||||
| check-markers | ✅ | ||||
| check-registers | ✅ | ||||
| check-seeds | ✅ | ||||
| check-fragment-required | ✅ | ||||
| check-nav-reachability | ✅ | ||||
| check-job-registration | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 551/551 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-29 10:34 UTC
Download the full PDF report from the workflow artifacts.
…#1340) gate-103 (locale-duplication) fails this app: 28 locale pairs are byte-for-byte copies of another language. They are not "untranslated" — they are WRONG, and a user who picks one of them is shown a language they did not choose. Identified by reading the content, not just the hashes: * bs, cs, mk, sk, sl, sr are byte-identical to hr and contain CROATIAN ("Action" -> "Radnja", "Created" -> "Stvoreno"; Czech would be "Akce" / "Vytvořeno"). Kept hr. * be and uk are byte-identical to ru and contain RUSSIAN ("Action" -> "Действие"). Kept ru. * sv is byte-identical to da and contains DANISH ("Action" -> "Handling", "Created" -> "Oprettet"; Swedish would be "Åtgärd" / "Skapad"). Kept da. Deleted rather than blanked, so those nine fall back to English. English a Slovak reader can recognise as not-their-language beats Croatian they may mistake for it. Verified after the deletion: zero duplicate groups in both the .json and the .js catalogues, 27 locales remain, and hr/ru/da/nl still parse. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/shillinq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-manifest-shell | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-manifest-budget | ✅ | ||||
| check-markers | ✅ | ||||
| check-registers | ✅ | ||||
| check-seeds | ✅ | ||||
| check-fragment-required | ✅ | ||||
| check-nav-reachability | ✅ | ||||
| check-job-registration | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 551/551 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-29 12:43 UTC
Download the full PDF report from the workflow artifacts.
…ee (#1343) My previous change deduplicated by FILE HASH and took gate-103 from 28 pairs to 3. The remainder survived because the gate does not compare bytes, despite saying so: it reads the `.js` bundles, keeps the values that differ from English, and flags a pair when EVERY shared translated value matches. Two files can differ in key order or coverage — so hash differently — and still be the same translation. Running the gate's own script locally names them: ca and es: all 1523 translated values identical -> ca holds SPANISH ("Acción"; Catalan is "Acció") de and lb: all 1503 translated values identical -> lb holds GERMAN ("Aktion"; Luxembourgish is "Aktioun") it and rm: all 1511 translated values identical -> rm holds ITALIAN ("Azione") Deleted the three copies, keeping es, de and it. The gate advises translating rather than blanking, because the parity gate refuses empty values. Removing the bundle outright is the third option and the honest one here: nobody translated these, and English a Catalan reader recognises as not-their-language beats Spanish they may mistake for it. Verified with the gate's own script: "checked 24 locale(s) … no locale is a byte-for-byte copy of another." Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
#1344) gate-103 still failed after #1340 dropped nine bundles: ca, lb and rm are copies of es, de and it respectively — all 1523, 1503 and 1511 shared translated values identical. The source language of each pair was read off its own strings, not assumed from the filename: es says "Acción"/"Creado"/"Fecha" (Catalan would be "Acció"/"Creat"), de says "Aktion"/"Erstellt"/"Datum", it says "Azione"/"Creato"/"Data". The genuine file is kept and the copy dropped. Worth recording how this was missed. gate-103 does NOT compare file bytes, despite saying "byte-for-byte": it parses `l10n/*.js`, takes the values that differ from English, and flags a pair when every SHARED translated value matches. Two bundles with different key sets and different bytes are still a copy by that definition — so an md5 sweep over `l10n/*.json` reports clean over a failing tree, which is exactly what it did here. Verified by running the gate's own checker, not a proxy for it: 24 locales, "no locale is a byte-for-byte copy of another." Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Two independent faults, either of which alone stops the docs site updating. TRIGGER. This listened on a branch called `documentation`. Nobody has pushed to one since 2026-05-25, so every docs change merged to `development` passed review and published nothing. SECRETS. A reusable workflow receives no secrets by default. With none mapped, the callee's publish step finds CF_API_TOKEN empty and skips itself on its own guard, and the run finishes GREEN having changed nothing. Fixing only the trigger would have produced exactly that. The worker name is now pinned. Deriving it is the documented way to get a green run that reaches nobody: wrangler creates the derived worker and publishes there while the custom domains keep routing to the real one. Where the app was renamed, `canonical-host` turns the retired hostname from a second live copy of every page into a 301 to the same path on the current one. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Quality Report — ConductionNL/shillinq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ⏭️ | ||||
| phpcs | ⏭️ | ||||
| phpmd | ⏭️ | ||||
| psalm | ⏭️ | ||||
| phpstan | ⏭️ | ||||
| phpmetrics | ⏭️ | ||||
| eslint | ⏭️ | ||||
| stylelint | ⏭️ | ||||
| build | ⏭️ | ||||
| composer | ⏭️ | ⏭️ | |||
| npm | ⏭️ | ⏭️ | |||
| app:check-code | ⏭️ | ||||
| info.xml | ⏭️ | ||||
| REUSE | ⏭️ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-29 13:07 UTC
Download the full PDF report from the workflow artifacts.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Quality Report — ConductionNL/shillinq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-manifest-shell | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-manifest-budget | ✅ | ||||
| check-markers | ✅ | ||||
| check-registers | ✅ | ||||
| check-seeds | ✅ | ||||
| check-fragment-required | ✅ | ||||
| check-nav-reachability | ✅ | ||||
| check-job-registration | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 551/551 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-29 14:19 UTC
Download the full PDF report from the workflow artifacts.
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Bumps [pinia](https://github.com/vuejs/pinia) from 3.0.4 to 4.0.3. - [Release notes](https://github.com/vuejs/pinia/releases) - [Commits](vuejs/pinia@v3.0.4...v4.0.3) --- updated-dependencies: - dependency-name: pinia dependency-version: 4.0.3 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> Co-authored-by: Ruben van der Linde <rubenvdlinde@gmail.com>
* feat(nav): a Flows surface in this app, on the shared page types ADR-110 Decision 4. A flow is app-specific — it operates on this app's objects — so the authoring surface belongs here rather than behind a deep link to another app's list. The ENGINE stays single (ADR-065): these pages are a scoped view onto OpenRegister's one native flow store, not a per-app store. Two manifest pages and one settings entry, no component files: `type: "flows"` and `type: "flow-detail"` are shipped page types in @conduction/nextcloud-vue 2.19.0, scoped by `config.app`. Note the layout of the diff: entries are appended textually rather than by reserialising the manifest. A `json.dump` round-trip rewrote pipelinq's file as a 3,950-line diff for a 20-line addition — correct output, unreviewable change. * build(deps): @conduction/nextcloud-vue 2.19.0 for the flows page types Required by the manifest change: `type: "flows"` / `type: "flow-detail"` are rejected by the compiled validator in earlier versions, and CI installs with `npm ci` — so the LOCK is what decides, not the `^2.x` range. Several of these locks were pinned many minors back, which is why some lockfile diffs are large: npm restructures the nested tree (mostly @esbuild platform binaries under @nextcloud/vue) to satisfy 2.19.0's peers. No direct dependency other than @conduction/nextcloud-vue changes. * chore(tests): refresh the vendored manifest schema to 2.25.0 The manifest gate resolves its schema from `tests/schemas/` FIRST, ahead of node_modules — deliberately, so the gate is self-contained and does not depend on a fresh install. The cost is that the copy freezes: this one was 12 minors behind, so the gate could not see any page type, widget or field the library had added since, and reported PASS the whole time because nothing it knew about was wrong. Refreshed from the installed 2.19.0 (schema 2.25.0) so `type: "flows"` and `type: "flow-detail"` validate. Worth noting for whoever owns this gate: four apps vendor this file and all four had drifted — 2.12.0, 2.13.0, 2.13.0 and 2.22.0 against a library at 2.25.0. A copy with no refresh step is a gate with a silent expiry date. * test(nav): baseline FlowDetail, reachable by row click not by menu The reachability gate reads menu[] and a fixed list of manifest link fields. CnFlowsPage navigates with this.$router.push(`${detailRoute}/${id}`), so the edge from the Flows index to /flows/:id lives in the shared component and is invisible to a static gate — the same class as the runtime related-object widgets design.md §2 scopes out. Not an IA gap: verified in a browser on dossiq, where /flows lists flows and clicking one opens the canvas. The entry says so, and says what would let it be removed — CnFlowsPage accepting a route NAME in config.detailRoute, which would make the edge declarative and visible to this gate fleet-wide. * feat(flows): give the flow-detail canvas its sidebar The manifest _note claimed the controls rendered in the NC app sidebar, but the sidebarComponent field it described did not exist. Every #/flows/:id -- and #/flows/new, the same route with the literal id -- drew a bare canvas: savable and runnable, but with no way to name, describe, trigger or step-edit the flow, because those controls all live in CnFlowSidebar. Mirrors pipelinq#1490. ADR-110 Decision 4.
beta held 14 commit(s) development did not. Merged with -s ours: development's tree is kept BYTE FOR BYTE and only the ancestry is recorded. That is the payload -- without it the merge base never moves and the next development -> beta promotion conflicts on the version file exactly as before. 13 of 19 promotion PRs were CONFLICTING for this reason. Nothing is silently imported. What beta holds and development does not, and which this deliberately does NOT bring over: .forgejo/.beta-release-trigger Those are dead Forgejo/Codeberg CI (removed from development on 2026-08-24/25 by 'chore(ci): remove dead Forgejo/Codeberg CI configuration'), generated Docusaurus build output, and community-health files that never existed on development. Each can be added deliberately if wanted; resurrecting them as a side effect of a sync is how a merge silently undoes a decision.
…0260830084308 chore(sync): carry beta back into development
…00841 chore(sync): record beta's ancestry on development
pinia 4.0.3 (#1243) landed on development with three checks red — Frontend Build, Frontend Tests (unit) and the E2E run downstream of the build. Two independent resolution regressions, both mechanical: 1. `@vue/devtools-api` moved from pinia's `dependencies` (^7.7.7 in 3.0.4) to a NON-optional `peerDependency` (^8.1.5 in 4.0.3). This repo's .npmrc sets `legacy-peer-deps=true`, so npm installs no peers and the package was simply absent — the lockfile had zero top-level entries for it. vitest died with "Cannot find package '@vue/devtools-api' imported from node_modules/pinia/dist/pinia.js" (1 of 22 suites). Declared it explicitly; the lock only hoists the 8.2.1 copy @nextcloud/vue already pulled in, so no version moves. 2. pinia 4 dropped `main` AND `module`, shipping only an `exports` map. The Vue-2-era `pinia$` alias pointed at the package DIRECTORY, which bypasses `exports` and then looks for a main/index.js that no longer exists — every import failed with "Can't resolve 'pinia'" (5 webpack errors). This is the exact trap the neighbouring @nextcloud/vue, @nextcloud/dialogs and vue-router comments in this file already document; aliased to the absolute FILE the same way. Verified locally: vitest 22/22 files, 256/256 tests (was 1 failed / 21 passed); webpack production build 0 errors (was 5). The remaining sax/'stream' warning is pre-existing and present before the bump. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Quality Report — ConductionNL/shillinq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ⏭️ | ||||
| phpcs | ⏭️ | ||||
| phpmd | ⏭️ | ||||
| psalm | ⏭️ | ||||
| phpstan | ⏭️ | ||||
| phpmetrics | ⏭️ | ||||
| eslint | ⏭️ | ||||
| stylelint | ⏭️ | ||||
| build | ⏭️ | ||||
| composer | ⏭️ | ⏭️ | |||
| npm | ⏭️ | ⏭️ | |||
| app:check-code | ⏭️ | ||||
| info.xml | ⏭️ | ||||
| REUSE | ⏭️ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-30 09:07 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/shillinq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-manifest-shell | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| check-manifest-budget | ✅ | ||||
| check-markers | ✅ | ||||
| check-registers | ✅ | ||||
| check-seeds | ✅ | ||||
| check-fragment-required | ✅ | ||||
| check-nav-reachability | ✅ | ||||
| check-job-registration | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 541/541 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-30 10:21 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.