diff --git a/appinfo/info.xml b/appinfo/info.xml
index 7c516b404..c842d443a 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -26,7 +26,7 @@ Vrij en open source onder de EUPL-1.2-licentie.
**Ondersteuning:** Voor ondersteuning, neem contact op via support@conduction.nl.
]]>
- 0.2.9-unstable.20260831013830
+ 0.2.12-unstable.20260901050759EUPL-1.2ConductionShillinq
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 9bad6e2d4..6d6c921b5 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -144,11 +144,32 @@ export default [
'no-console': 'off',
'n/no-process-exit': 'off',
'n/hashbang': 'off',
- // `_` / `__` as a deliberate throwaway binding β `catch (_)`, a
- // discarded destructuring slot. Narrow on purpose: the pattern matches
- // UNDERSCORES ONLY, so a real name that happens to start with `_` is
- // still reported. v9 drives plain `.js` through the CORE rule (the
- // `@typescript-eslint` swap is per-file-type), so it is set here.
+ // Tests import devDependencies by definition; this rule is about what
+ // ships in the published package, which tests/ never does.
+ 'n/no-unpublished-import': 'off',
+ },
+ },
+
+ {
+ // `_` / `__` as a deliberate throwaway binding β `catch (_)`, a discarded
+ // destructuring slot. Narrow on purpose: the pattern matches UNDERSCORES
+ // ONLY, so a real name that happens to start with `_` is still reported.
+ //
+ // π΄ `.js` / `.mjs` ONLY, NOT `.ts`. The CORE rule is not TypeScript-aware:
+ // applied to a `.ts` file it reads the parameter NAMES inside a function
+ // TYPE as bindings and reports them unused. Measured on humaniq β
+ //
+ // t?: (app: string, key: string) => string
+ //
+ // produced four `no-unused-vars` errors for `app` and `key`, which are
+ // documentation, not variables. The same mis-scoping made every unused
+ // `catch (e)` in a `.ts` spec report TWICE, once per rule.
+ //
+ // v9 already turns the core rule off for `.ts` and drives
+ // `@typescript-eslint/no-unused-vars` instead; naming `.ts` here switched
+ // it back on. TypeScript files are handled by the block below.
+ files: ['tests/**/*.js', 'tests/**/*.mjs'],
+ rules: {
'no-unused-vars': [
'error',
{
@@ -165,12 +186,116 @@ export default [
ignoreRestSiblings: true,
},
],
- // Tests import devDependencies by definition; this rule is about what
- // ships in the published package, which tests/ never does.
- 'n/no-unpublished-import': 'off',
},
},
+ {
+ // The TypeScript half of the block above. Same intent, same patterns, on
+ // the rule that actually understands the language: it knows a name inside
+ // a function type is not a binding, so type annotations stay quiet while a
+ // genuinely dead local is still reported.
+ files: ['tests/**/*.ts', 'tests/**/*.tsx'],
+ rules: {
+ '@typescript-eslint/no-unused-vars': [
+ 'error',
+ {
+ varsIgnorePattern: '^_+$',
+ caughtErrors: 'all',
+ caughtErrorsIgnorePattern: '^_+$',
+ argsIgnorePattern: '^_',
+ ignoreRestSiblings: true,
+ },
+ ],
+ },
+ },
+
+ {
+ // π΄ Node-side CLI tooling under `scripts/`, which is COMMONJS. Flat
+ // config defaults every `.js` to ESM with browser-ish globals, so without
+ // this block eslint reports the CommonJS wrapper itself as undefined
+ // identifiers. Measured on this app: 52 of the 233 errors under
+ // `tests/` + `scripts/` were `no-undef`, ALL of them in `scripts/`, and
+ // all five names were the environment rather than a typo β `process` 23,
+ // `require` 20, `__dirname` 6, `__filename` 2, `module` 1.
+ //
+ // This is describing the environment, not relaxing a rule, and it is the
+ // same argument the test-globals block below makes: declaring them keeps
+ // `no-undef` able to do its real job, which is catching a genuinely
+ // misspelled identifier. Suppressing the rule instead would bury that.
+ //
+ // `no-console` is off because printing its report is what a CLI checker
+ // is FOR.
+ //
+ // π΄ NO `n/*` ENTRIES HERE, DELIBERATELY. `eslint-plugin-n` is NOT
+ // registered for these files under eslint 10 + @nextcloud/eslint-config
+ // 9, so `'n/no-process-exit': 'off'` would be dead config that reads as
+ // if it were doing something. Measured both ways on this app: 0 `n/`
+ // findings with the entries and 0 without.
+ //
+ // What DID report was the opposite β four `scripts/*.js` carried
+ // `/* eslint-disable n/no-process-exit */` and `/* eslint-disable
+ // n/shebang */` left over from the eslintrc era, and an inline disable
+ // naming an unregistered plugin is itself an error ("Definition for rule
+ // 'n/shebang' was not found"). Those 8 comments are removed; do not add
+ // `n/*` rules back to replace them.
+ //
+ // β οΈ `.js` and `.cjs` ONLY. A `scripts/*.mjs` is genuinely ESM and must
+ // keep the default `sourceType`, or `import` stops parsing there.
+ files: ['scripts/**/*.js', 'scripts/**/*.cjs'],
+ languageOptions: {
+ sourceType: 'commonjs',
+ globals: {
+ require: 'readonly',
+ module: 'writable',
+ exports: 'writable',
+ process: 'readonly',
+ __dirname: 'readonly',
+ __filename: 'readonly',
+ console: 'readonly',
+ Buffer: 'readonly',
+ global: 'readonly',
+ URL: 'readonly',
+ TextEncoder: 'readonly',
+ TextDecoder: 'readonly',
+ },
+ },
+ rules: {
+ 'no-console': 'off',
+ },
+ },
+
+ {
+ // The ESM half of the block above. A `scripts/*.mjs` is genuinely a module
+ // and must keep the default `sourceType`, so it gets Node's globals but
+ // none of the CommonJS wrapper. Measured: `process` reported undefined 2x
+ // in hermiq's generate-opengemeenten-icons.mjs and 4x in openregister's
+ // l10n/runtime-check.mjs, which the `.js`/`.cjs` block deliberately does
+ // not match.
+ files: ['scripts/**/*.mjs', 'tests/**/*.mjs'],
+ languageOptions: {
+ globals: {
+ process: 'readonly',
+ console: 'readonly',
+ Buffer: 'readonly',
+ global: 'readonly',
+ URL: 'readonly',
+ TextEncoder: 'readonly',
+ TextDecoder: 'readonly',
+ },
+ },
+ rules: {
+ 'no-console': 'off',
+ },
+ },
+
+ {
+ // eslint must not try to PARSE a shell script. `tests/e2e/seed.test.sh`
+ // matches the `**/*.test.*` glob some presets use, and eslint then reads
+ // it as JavaScript and reports "Parsing error: Unexpected character" β
+ // a finding about a file it should never have opened.
+ ignores: ['**/*.sh', '**/*.bash'],
+ },
+
{
// Test globals. Several apps keep their spec files INSIDE `src/`, which the
// lint script scans, and neither `@nextcloud/eslint-config` nor the runner
diff --git a/lib/Repair/FoldExpensesAndHoursIntoProject.php b/lib/Repair/FoldExpensesAndHoursIntoProject.php
index 68dbaedcd..81aac98c6 100644
--- a/lib/Repair/FoldExpensesAndHoursIntoProject.php
+++ b/lib/Repair/FoldExpensesAndHoursIntoProject.php
@@ -90,6 +90,8 @@ public function getName(): string {
* @param IOutput $output The repair-step output (progress + warnings).
*
* @return void
+ *
+ * @spec openspec/specs/bookkeeping-consultancy-project-accounting/spec.md
*/
public function run(IOutput $output): void {
try {
@@ -99,7 +101,7 @@ public function run(IOutput $output): void {
// Index every Project by every identifier a source object might
// reference (id / uuid / projectNumber / code). Values are kept
// as live mutable arrays so multiple lines fold into one save.
- $projects = $this->readAllRows(objectService: $this->objectService, registerSlug: $registerSlug, schema: 'Project');
+ $projects = $this->readAllRows(objectService: $this->objectService, registerSlug: $registerSlug, schema: 'engagement');
if ($projects === []) {
$output->info('Shillinq: no Project records β expense/hours fold skipped.');
@@ -171,11 +173,11 @@ public function run(IOutput $output): void {
try {
$this->objectService
->setRegister($registerSlug)
- ->setSchema('Project')
+ ->setSchema('engagement')
->saveObject(
object: $record,
register: $registerSlug,
- schema: 'Project',
+ schema: 'engagement',
_rbac: false,
_multitenancy: false,
);
diff --git a/lib/Repair/RematerialiseConvertedCalculations.php b/lib/Repair/RematerialiseConvertedCalculations.php
index f9bafd1d4..cfe8348c7 100644
--- a/lib/Repair/RematerialiseConvertedCalculations.php
+++ b/lib/Repair/RematerialiseConvertedCalculations.php
@@ -101,7 +101,7 @@ class RematerialiseConvertedCalculations implements IRepairStep {
'ZzpDeduction',
'SisaReport',
'InventoryReorderRule',
- 'Project',
+ 'engagement',
'ProjectAssignment',
'VatReturn',
'InnovatieboxElection',
diff --git a/lib/Settings/register.d/000-register-declaration.json b/lib/Settings/register.d/000-register-declaration.json
index 510fc0094..d5bc66c98 100644
--- a/lib/Settings/register.d/000-register-declaration.json
+++ b/lib/Settings/register.d/000-register-declaration.json
@@ -356,7 +356,7 @@
"PricingTier",
"Programma",
"Programmabegroting",
- "Project",
+ "engagement",
"ProjectAssignment",
"ProjectBudget",
"ProvincialeFondsPosting",
diff --git a/lib/Settings/register.d/abstract-project-cost-lines.json b/lib/Settings/register.d/abstract-project-cost-lines.json
index 53636dead..297b06940 100644
--- a/lib/Settings/register.d/abstract-project-cost-lines.json
+++ b/lib/Settings/register.d/abstract-project-cost-lines.json
@@ -2,7 +2,7 @@
"x-shillinq-fragment": "abstract-project-cost-lines",
"components": {
"schemas": {
- "Project": {
+ "engagement": {
"version": "0.2.0",
"properties": {
"costLines": {
diff --git a/lib/Settings/register.d/add-shillinq-audit-trail.json b/lib/Settings/register.d/add-shillinq-audit-trail.json
index d94caf7e3..b1ed91121 100644
--- a/lib/Settings/register.d/add-shillinq-audit-trail.json
+++ b/lib/Settings/register.d/add-shillinq-audit-trail.json
@@ -542,7 +542,7 @@
"description": "OR's audit-trail-immutable captures every create / update / lifecycle / delete event on PerDiemRate with actor, timestamp, action, before/after snapshot, and hash chain per ADR-022 + REQ-AT-001..AT-002. Retention is governed by OR per REQ-AT-005 (no shillinq cleanup job)."
}
},
- "Project": {
+ "engagement": {
"x-openregister-audit-trail": {
"enabled": true,
"description": "OR's audit-trail-immutable captures every create / update / lifecycle / delete event on Project with actor, timestamp, action, before/after snapshot, and hash chain per ADR-022 + REQ-AT-001..AT-002. Retention is governed by OR per REQ-AT-005 (no shillinq cleanup job)."
diff --git a/lib/Settings/register.d/bookings-deposit-to-invoice.json b/lib/Settings/register.d/bookings-deposit-to-invoice.json
index 25db74eeb..d23ebe165 100644
--- a/lib/Settings/register.d/bookings-deposit-to-invoice.json
+++ b/lib/Settings/register.d/bookings-deposit-to-invoice.json
@@ -402,7 +402,7 @@
},
"Invoice": {
"slug": "Invoice",
- "icon": "FileDocumentOutline",
+ "icon": "ReceiptTextOutline",
"version": "0.1.0",
"title": "Invoice",
"description": "A final customer invoice materialised in Shillinq when a booking order completes. Carries a service line and, when a deposit was authorised, a negative deposit-credit line. Bidirectionally linked to its Order via sourceDocumentUri and to the credited DepositPayment via depositPaymentId (REQ-DI-001). Integrates into Shillinq AR aging with no special handling (REQ-DI-009).",
diff --git a/lib/Settings/register.d/bookkeeping-cost-centers-dimensions.json b/lib/Settings/register.d/bookkeeping-cost-centers-dimensions.json
index 8c17ab701..da8502690 100644
--- a/lib/Settings/register.d/bookkeeping-cost-centers-dimensions.json
+++ b/lib/Settings/register.d/bookkeeping-cost-centers-dimensions.json
@@ -456,7 +456,7 @@
],
"filter": {},
"join": {
- "through": "Project",
+ "through": "engagement",
"on": "Project.code",
"select": [
"Project.name",
@@ -570,7 +570,7 @@
{
"@self": {
"register": "shillinq",
- "schema": "Project",
+ "schema": "engagement",
"slug": "proj-internal-platform"
},
"code": "PROJ-INT-PLAT",
@@ -584,7 +584,7 @@
{
"@self": {
"register": "shillinq",
- "schema": "Project",
+ "schema": "engagement",
"slug": "proj-grant-research"
},
"code": "PROJ-GRANT-RND",
diff --git a/lib/Settings/register.d/bookkeeping-detachering-payroll-administratie.json b/lib/Settings/register.d/bookkeeping-detachering-payroll-administratie.json
index 9deb6617e..3207586da 100644
--- a/lib/Settings/register.d/bookkeeping-detachering-payroll-administratie.json
+++ b/lib/Settings/register.d/bookkeeping-detachering-payroll-administratie.json
@@ -9,7 +9,7 @@
"schemas": {
"Employee": {
"slug": "Employee",
- "icon": "AccountTie",
+ "icon": "AccountTieOutline",
"version": "0.1.0",
"title": "Employee",
"description": "Employee / detached worker / freelancer master record (REQ-PAY-001). Carries legal name, BSN (11-proef validated, masked on display per ADR-005), contract classification and onboarding/exit dates. A contact/person is a Nextcloud entity; this register holds the payroll-administration projection only.",
@@ -211,7 +211,7 @@
},
"Payroll": {
"slug": "Payroll",
- "icon": "CashMultiple",
+ "icon": "CashSync",
"version": "0.1.0",
"title": "Payroll",
"description": "Payroll period sub-ledger record (REQ-PAY-002). Captures gross input and aggregated deduction totals; on issue it materialises a balanced GLTransaction (REQ-PAY-008) per the T1 JournalEntry pattern. UBL Peppol BIS 30 field shape is declared for T4 passthrough but NOT computed here (REQ-PAY-010).",
diff --git a/lib/Settings/register.d/zz-order-primitive.json b/lib/Settings/register.d/zz-order-primitive.json
index 4a382ee8d..12fe2a0d5 100644
--- a/lib/Settings/register.d/zz-order-primitive.json
+++ b/lib/Settings/register.d/zz-order-primitive.json
@@ -10,7 +10,7 @@
"schemas": {
"OrderPrimitive": {
"slug": "OrderPrimitive",
- "icon": "FileDocumentMultipleOutline",
+ "icon": "ClipboardListOutline",
"version": "0.1.0",
"title": "Order",
"x-schema-org": "schema:Order",
diff --git a/lib/Settings/register.d/zzz-mcp-tool-surface.json b/lib/Settings/register.d/zzz-mcp-tool-surface.json
index 7dc84b1ba..90d3b3837 100644
--- a/lib/Settings/register.d/zzz-mcp-tool-surface.json
+++ b/lib/Settings/register.d/zzz-mcp-tool-surface.json
@@ -168,7 +168,7 @@
}
}
},
- "Project": {
+ "engagement": {
"configuration": {
"x-openregister-mcp": {
"enabled": true,
diff --git a/lib/Settings/shillinq_mock_register.json b/lib/Settings/shillinq_mock_register.json
index 6ced9daa1..73782e59f 100644
--- a/lib/Settings/shillinq_mock_register.json
+++ b/lib/Settings/shillinq_mock_register.json
@@ -2,7 +2,7 @@
"openapi": "3.0.0",
"info": {
"title": "shillinq demo data",
- "version": "1.0.0",
+ "version": "1.1.0",
"description": "Demo data covering every schema this app supplies, offered as the first step of the app's setup walkthrough. Generated from the schemas themselves, so every object satisfies the schema that will validate it."
},
"x-openregister": {
@@ -23760,7 +23760,7 @@
{
"@self": {
"register": "shillinq",
- "schema": "Project",
+ "schema": "engagement",
"slug": "project-voorbeeld-name-1-1"
},
"administrationId": "Voorbeeld Administrationid 1",
@@ -23810,7 +23810,7 @@
{
"@self": {
"register": "shillinq",
- "schema": "Project",
+ "schema": "engagement",
"slug": "project-voorbeeld-name-2-2"
},
"administrationId": "Voorbeeld Administrationid 2",
@@ -23860,7 +23860,7 @@
{
"@self": {
"register": "shillinq",
- "schema": "Project",
+ "schema": "engagement",
"slug": "project-voorbeeld-name-3-3"
},
"administrationId": "Voorbeeld Administrationid 3",
@@ -33567,33 +33567,6 @@
"mkbProfitExemptionPercentage": 1.0,
"source": "Voorbeeld Source 3"
},
- {
- "@self": {
- "register": "shillinq",
- "schema": "example",
- "slug": "example-voorbeeld-title-1-1"
- },
- "title": "Voorbeeld Title 1",
- "description": "Voorbeeld Description 1"
- },
- {
- "@self": {
- "register": "shillinq",
- "schema": "example",
- "slug": "example-voorbeeld-title-2-2"
- },
- "title": "Voorbeeld Title 2",
- "description": "Voorbeeld Description 2"
- },
- {
- "@self": {
- "register": "shillinq",
- "schema": "example",
- "slug": "example-voorbeeld-title-3-3"
- },
- "title": "Voorbeeld Title 3",
- "description": "Voorbeeld Description 3"
- },
{
"@self": {
"register": "shillinq",
diff --git a/lib/Settings/shillinq_register.json b/lib/Settings/shillinq_register.json
index f8f585a50..7b3c61573 100644
--- a/lib/Settings/shillinq_register.json
+++ b/lib/Settings/shillinq_register.json
@@ -2,8 +2,8 @@
"openapi": "3.0.0",
"info": {
"title": "Shillinq Register",
- "description": "Register containing all schemas for the Shillinq application. v0.6.4 β re-authored 31 Dutch schema-level titles (entity display names) to English so manifest-driven UI labels are English-authored; Dutch display is now carried via l10n/nl.json per ADR i18n rule. Previous v0.6.3 β re-authored 254 Dutch schema-property titles (and none renamed keys) to English so manifest-driven UI labels are English-authored; Dutch display is now carried via l10n/nl.json per ADR i18n rule. Previous v0.6.2 β Register containing all schemas for the Shillinq application.",
- "version": "0.6.4"
+ "description": "Shillinq register v0.7.0: retires the scaffold `example` schema and renames the `Project` slug to `engagement`. Planninq owns `project` as a container for tasks and kanban columns; this one is a consultancy engagement with RJ 270 percentage-of-completion revenue recognition, which is a different entity wearing the same word. Slugs are global on a shared OpenRegister. The schema KEY stays `Project`, so the register.d fragments that extend it by key are unaffected. PREVIOUSLY: Register containing all schemas for the Shillinq application. v0.6.4 β re-authored 31 Dutch schema-level titles (entity display names) to English so manifest-driven UI labels are English-authored; Dutch display is now carried via l10n/nl.json per ADR i18n rule. Previous v0.6.3 β re-authored 254 Dutch schema-property titles (and none renamed keys) to English so manifest-driven UI labels are English-authored; Dutch display is now carried via l10n/nl.json per ADR i18n rule. Previous v0.6.2 β Register containing all schemas for the Shillinq application.",
+ "version": "0.7.0"
},
"x-openregister": {
"type": "application",
@@ -14,31 +14,7 @@
"paths": {},
"components": {
"schemas": {
- "example": {
- "slug": "example",
- "icon": "FileDocumentOutline",
- "version": "0.1.0",
- "title": "Example",
- "description": "Example schema β replace with your app's actual schemas.",
- "type": "object",
- "required": [
- "title"
- ],
- "properties": {
- "title": {
- "type": "string",
- "description": "The title of the example object",
- "example": "My example",
- "title": "Title"
- },
- "description": {
- "type": "string",
- "description": "An optional description",
- "example": "This is an example",
- "title": "Description"
- }
- }
- },
+
"BankConnection": {
"slug": "BankConnection",
"icon": "BankOutline",
@@ -5274,8 +5250,8 @@
}
}
},
- "Project": {
- "slug": "Project",
+ "engagement": {
+ "slug": "engagement",
"icon": "FolderOpenOutline",
"version": "0.1.0",
"title": "Project",
@@ -7548,7 +7524,7 @@
],
"source": {
"register": "shillinq",
- "schema": "Project",
+ "schema": "engagement",
"foreignKey": "projectId"
},
"precondition": {
@@ -7741,7 +7717,7 @@
"action": {
"forEach": {
"register": "@register",
- "schema": "Project",
+ "schema": "engagement",
"filter": {
"state": "active"
}
@@ -8091,7 +8067,7 @@
"eventType": "nl.conduction.shillinq.fiscal-year.dimension-rollover",
"targets": [
"AnalyticalDimension",
- "Project"
+ "engagement"
],
"filter": {
"administrationId": "@self.administrationId",
@@ -15021,7 +14997,7 @@
},
"Subsidie": {
"slug": "Subsidie",
- "icon": "CashMultiple",
+ "icon": "HandHeartOutline",
"version": "0.1.0",
"title": "Subsidy",
"description": "Grant administration record per Awb afdeling 4.2 + VNG ASV-model 2022. Covers the full lifecycle for both outgoing grants (gemeente grants to beneficiary) and incoming grants (gemeente receives from granting body). Lifecycle declared via x-openregister-lifecycle per ADR-031; no SubsidieLifecycleService.",
diff --git a/openspec/changes/hours-to-humaniq/.openspec.yaml b/openspec/changes/hours-to-humaniq/.openspec.yaml
new file mode 100644
index 000000000..b4b3ece78
--- /dev/null
+++ b/openspec/changes/hours-to-humaniq/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-09-01
diff --git a/openspec/changes/hours-to-humaniq/proposal.md b/openspec/changes/hours-to-humaniq/proposal.md
new file mode 100644
index 000000000..bf0d8764d
--- /dev/null
+++ b/openspec/changes/hours-to-humaniq/proposal.md
@@ -0,0 +1,129 @@
+# Change: hours-to-humaniq
+
+## Why
+
+ADR-107 decision 6 (`hydra/openspec/architecture/adr-107-money-and-effort-ownership.md`)
+assigns booked hours to one app, and it is not this one:
+
+> **Effort is recorded against the domain object and costed by hrmq.** Hours
+> logged on a case are hrmq time entries carrying the case reference. Hours Γ
+> the composed hourly cost becomes a cost allocation dispatched to Shillinq,
+> taakveld taken from the case's `caseType`. The domain app supplies **context
+> and classification**; hrmq supplies **the wage base**; Shillinq supplies
+> **the ledger-derived additions and the booking**.
+
+Shillinq ships the opposite arrangement. `lib/Settings/register.d/uren-domain-subject-link.json`
+adds `subjectApp` + `subjectId` to `UrenRegistratie` so a domain app can book
+hours against a case here. Its own `_meta` cites the same ADR:
+
+> Per hydra ADR-081, the domain app CLASSIFIES and Shillinq AGGREGATES.
+
+Both readings cite ADR-081. That number was claimed by two documents until the
+2026-08-26 renumbering, so the citation resolves to whichever the reader
+assumed. Read against ADR-107 as it now stands, decision 6 is explicit about
+where an hour lives, and this overlay contradicts it.
+
+Note the ADR's status is **Proposed**, not Accepted. This change proceeds on it
+anyway, for the reason in the next section: the arrangement it replaces does
+not work.
+
+## Investigation: nobody writes these fields, and nobody reads them
+
+Before designing a migration, we checked what would have to migrate. The answer
+is nothing.
+
+**`UrenRegistratie.subjectApp` has no writer.** A fleet-wide grep across every
+PHP, JS, Vue and JSON file in the workspace finds the field in exactly three
+places: the schema overlay that declares it, a shillinq unit test asserting the
+declaration, and one consumer.
+
+**That consumer is a dossiq KPI, and it can only ever read zero.** Dossiq's
+`CaseDetail` manifest carries a `case-kpis-hours` tile summing
+`shillinq.UrenRegistratie.hours` filtered on `subjectApp: "dossiq"` and
+`subjectId: @objectId`. Nothing writes a row matching that filter, so the tile
+reports 0 hours on every case in every install. It has done so since it
+shipped.
+
+**Humaniq's side is unwired too.** `TimeEntry.domainObjectRef` and
+`domainObjectType` are declared in `hr-cost-rate.json` and nothing in the fleet
+writes or reads them either. The `x-notes` still give `procest:case` as the
+example, an app id that was renamed to `dossiq`.
+
+So the fleet holds two competing designs for case hours, and neither has ever
+carried a record. This is the same failure ADR-107 itself documents about
+procest's IV3 report: an aggregator reading a field almost nothing filled.
+
+**Caveat.** We checked the source tree, not a production database. An operator
+could have created rows by hand through the OpenRegister UI. Any install
+running this change should count `UrenRegistratie` rows with a non-null
+`subjectApp` before upgrading. The verification task below does that.
+
+## What changes
+
+`UrenRegistratie` stops being a place to book hours against a domain object.
+Humaniq's `TimeEntry` becomes the only one, and it grows the case reference it
+was already declared for.
+
+Shillinq keeps everything ADR-107 decision 1 gives it. It remains the only
+general ledger and the only statutory reporter. What it stops doing is holding
+the hour.
+
+### The design question this change must answer
+
+`UrenRegistratie` carries nine fields `TimeEntry` has no equivalent for:
+
+| Field | What it is for |
+|---|---|
+| `recognisedRate` | RateCard hourly rate snapshotted at booking time |
+| `glTransactionId` | The GL transaction this hour posted to |
+| `wbsoTagId`, `activityCodeId`, `tagSource`, `wbsoTaggedAt` | WBSO subsidy tagging |
+| `projectAssignmentId`, `costProjectId` | Analytical dimensions |
+| `utilizationPercent` | Derived utilization per REQ-CPA-109 |
+
+These are ledger and subsidy concerns, and ADR-107 decision 1 keeps them here.
+So the answer is not "copy them to humaniq". Two shapes are open, and
+`design.md` decides between them:
+
+1. **Shillinq derives a cost line per humaniq TimeEntry.** The hour lives in
+ humaniq. Shillinq holds a booking that references it and carries the rate,
+ the GL link and the WBSO tags. Matches decision 6 exactly. Costs a
+ cross-app read on every booking.
+2. **Humaniq grows an opaque allocation payload.** `TimeEntry.allocationKey`
+ already exists as an opaque ledger dimension that humaniq refuses to
+ interpret. Extending that idea keeps the hour in one row. Risks smuggling
+ shillinq's model into humaniq under an opaque name, which is what
+ `allocationKey`'s own `x-notes` warn against.
+
+### Scope
+
+In scope, in this app:
+
+- Retire `subjectApp` / `subjectId` from `UrenRegistratie`.
+- Repoint the four specs that read hours: `invoice-from-time-and-expense`,
+ `time-expense-invoice-intake`, `wbso-uren-tagging-and-export`,
+ `zzp-urencriterium-tracker`.
+- Repoint the six code consumers: `TimeIntakeService` (10 references),
+ `WBSOExportValidationGuard` (5), `FinancialDashboardService` (3),
+ `InvoiceGenerationService` (2), `UrencriteriumGuard` (2),
+ `SubjectCostAggregator` (1).
+- Keep the ledger, the WBSO export and the urencriterium guard working. These
+ are statutory. A Dutch self-employed person loses a tax deduction if the
+ 1225-hour count is wrong.
+
+Out of scope, tracked elsewhere:
+
+- The humaniq write path and the shared hours widget ship with the dossiq case
+ detail work.
+- ADR-107's composed `hourlyCost` model is a separate programme.
+
+## Risks
+
+**The urencriterium and WBSO paths are statutory.** Both feed a tax position.
+Neither may read a partial hour set during the migration. Tasks below keep the
+old read path alive until the new one is proven against the same numbers.
+
+**Humaniq becomes a hard dependency for hours.** Today shillinq books hours
+alone. After this it cannot, and no fleet app declares an `` dependency in
+`info.xml`. An install without humaniq must degrade to a visible empty state,
+never a silent zero. The dossiq KPI's current behaviour is exactly the failure
+to avoid repeating.
diff --git a/openspec/changes/hours-to-humaniq/specs/hours-to-humaniq/spec.md b/openspec/changes/hours-to-humaniq/specs/hours-to-humaniq/spec.md
new file mode 100644
index 000000000..7d4dbc94a
--- /dev/null
+++ b/openspec/changes/hours-to-humaniq/specs/hours-to-humaniq/spec.md
@@ -0,0 +1,70 @@
+# Spec: hours-to-humaniq (delta)
+
+## ADDED Requirements
+
+### Requirement: REQ-H2H-001 Β· Hours on a domain object live in humaniq
+
+An hour worked on a domain object (a dossiq case, any case or matter object)
+MUST be recorded as a humaniq `TimeEntry` carrying `domainObjectRef` and
+`domainObjectType`. Shillinq MUST NOT hold the hour. Per ADR-107 decision 6,
+the domain app supplies context and classification, humaniq supplies the wage
+base, and shillinq supplies the ledger-derived additions and the booking.
+
+#### Scenario: `UrenRegistratie` no longer declares a domain subject
+
+- **GIVEN** the shillinq register fragments
+- **WHEN** `lib/Settings/register.d/uren-domain-subject-link.json` is looked up
+- **THEN** the file does not exist
+- **AND** no fragment declares `subjectApp` or `subjectId` on `UrenRegistratie`
+- @e2e exclude verified by file-existence and a static grep over the register
+ fragments. A removed schema field has no browser surface of its own.
+
+#### Scenario: A case shows hours booked in humaniq
+
+- **GIVEN** a dossiq case with two humaniq `TimeEntry` records whose
+ `domainObjectType` is `dossiq:case` and whose `domainObjectRef` is the
+ case uuid
+- **WHEN** a handler opens the case detail page
+- **THEN** the hours tile shows the sum of those two entries
+
+### Requirement: REQ-H2H-002 Β· An hours surface never reports zero for a missing app
+
+Every surface that shows booked hours MUST distinguish "humaniq is absent" from
+"no hours were booked". When humaniq is not installed or not reachable, the
+surface MUST render a named empty state that says so. It MUST NOT render 0.
+
+This requirement exists because the behaviour it forbids already shipped.
+Dossiq's `case-kpis-hours` tile summed a shillinq field no code ever wrote, so
+it reported 0 hours on every case in every install and looked correct doing it.
+
+#### Scenario: Humaniq is not installed
+
+- **GIVEN** an install with humaniq disabled
+- **WHEN** a handler opens a case detail page
+- **THEN** the hours tile names humaniq as unavailable
+- **AND** the tile does not show a numeric total
+
+#### Scenario: Humaniq is installed and the case has no hours
+
+- **GIVEN** an install with humaniq enabled and a case with no `TimeEntry`
+- **WHEN** a handler opens the case detail page
+- **THEN** the hours tile shows 0
+
+### Requirement: REQ-H2H-003 Β· Statutory hour counts are proven before cutover
+
+The WBSO export guard and the urencriterium guard both feed a tax position. For
+each, the humaniq-backed count MUST be proven equal to the `UrenRegistratie`
+count over the same period and administration before the old read path is
+removed.
+
+A wrong urencriterium count costs a self-employed person a real deduction, so
+"the tests pass" is not the bar. The two counts must be compared directly.
+
+#### Scenario: Both guards agree across the cutover
+
+- **GIVEN** an administration with hours booked over a full calendar year
+- **WHEN** the urencriterium total is computed from `UrenRegistratie` and from
+ humaniq `TimeEntry` over the same year
+- **THEN** the two totals are equal
+- @e2e exclude a numeric equivalence check between two service read paths, with
+ no UI of its own. Covered by an integration test.
diff --git a/openspec/changes/hours-to-humaniq/tasks.md b/openspec/changes/hours-to-humaniq/tasks.md
new file mode 100644
index 000000000..8c73361e2
--- /dev/null
+++ b/openspec/changes/hours-to-humaniq/tasks.md
@@ -0,0 +1,56 @@
+# Tasks: hours-to-humaniq
+
+## 1. Decide the split
+
+- [ ] 1.1 Write `design.md` choosing between the derived-cost-line shape and
+ the opaque-allocation shape described in `proposal.md`. Record why.
+- [ ] 1.2 Confirm ADR-107 is promoted from Proposed to Accepted, or record
+ that this change proceeds on a Proposed ADR and why that is safe.
+
+## 2. Prove there is nothing to migrate
+
+- [ ] 2.1 Add an `occ` command that counts `UrenRegistratie` rows with a
+ non-null `subjectApp`. Report per administration.
+- [ ] 2.2 Run it on the dev instance and on every reachable install. Record
+ the counts in `design.md`. A non-zero count reopens task 1.
+- [ ] 2.3 Only when every count is zero: proceed. Otherwise write a data
+ migration first.
+
+## 3. Move the hour
+
+- [ ] 3.1 Humaniq writes case-scoped hours: `TimeEntry.domainObjectRef` +
+ `domainObjectType` are stamped on create from the logging app.
+- [ ] 3.2 Correct humaniq's `x-notes` example from `procest:case` to
+ `dossiq:case`. The app id moved and nothing writes the field yet, so
+ this costs nothing now and is unrecoverable once hours exist.
+- [ ] 3.3 Shillinq reads hours from humaniq for the ledger, in the shape
+ task 1.1 chose.
+
+## 4. Repoint each consumer, statutory ones last
+
+- [ ] 4.1 `SubjectCostAggregator` (1 reference). Smallest, and it proves the
+ read path.
+- [ ] 4.2 `FinancialDashboardService` (3 references). Reporting only, no tax
+ position.
+- [ ] 4.3 `InvoiceGenerationService` (2 references) and
+ `TimeIntakeService` (10). Invoicing.
+- [ ] 4.4 `WBSOExportValidationGuard` (5 references). Subsidy. Run old and
+ new side by side over the same period and assert identical output
+ before cutting over.
+- [ ] 4.5 `UrencriteriumGuard` (2 references). A wrong 1225-hour count costs
+ a real person a real deduction. Same side-by-side proof as 4.4.
+
+## 5. Retire the dead fields
+
+- [ ] 5.1 Delete `lib/Settings/register.d/uren-domain-subject-link.json`.
+- [ ] 5.2 Delete `tests/Unit/Settings/UrenDomainSubjectLinkTest.php`.
+- [ ] 5.3 Update the four affected specs: `invoice-from-time-and-expense`,
+ `time-expense-invoice-intake`, `wbso-uren-tagging-and-export`,
+ `zzp-urencriterium-tracker`.
+
+## 6. Degrade honestly without humaniq
+
+- [ ] 6.1 Every hours surface shows a named empty state when humaniq is
+ absent. Never a zero. The dossiq KPI reporting 0 hours on every case
+ is the bug this change exists to stop repeating.
+- [ ] 6.2 Add a test that asserts the empty state, not the zero.
diff --git a/package-lock.json b/package-lock.json
index ce33a9b47..11b6c52b9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,7 @@
"version": "0.2.1",
"license": "EUPL-1.2",
"dependencies": {
- "@conduction/nextcloud-vue": "^2.24.4",
+ "@conduction/nextcloud-vue": "^2.31.1",
"@nextcloud/auth": "^2.6.0",
"@nextcloud/axios": "~2.5.2",
"@nextcloud/capabilities": "^1.2.1",
@@ -644,9 +644,9 @@
}
},
"node_modules/@conduction/nextcloud-vue": {
- "version": "2.27.2",
- "resolved": "https://registry.npmjs.org/@conduction/nextcloud-vue/-/nextcloud-vue-2.27.2.tgz",
- "integrity": "sha512-FhDF3FvM+ee0Jx7z70Lki7o7Mm4DeX/GsOqHo33hOcdylzXR63UXLRbxKMedodEiIqgOFUAI6GTox735iyL8Dw==",
+ "version": "2.31.1",
+ "resolved": "https://registry.npmjs.org/@conduction/nextcloud-vue/-/nextcloud-vue-2.31.1.tgz",
+ "integrity": "sha512-tF1/7yNaBgxj5iHhpNuxVdUrPugfy2ePQZILl7y+fFJTq68tR7WPs8EqPin8aaCBc6ZxgWp1ciyY+NW9rXBMYA==",
"license": "EUPL-1.2",
"dependencies": {
"@ckpack/vue-color": "^1.6.0",
diff --git a/package.json b/package.json
index 05c96ddc5..e8bd9724d 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,7 @@
"postdev": "[ -d ../openregister/custom_apps/shillinq/js ] && cp -r js/. ../openregister/custom_apps/shillinq/js/ || true",
"prewatch": "node scripts/generate-manifest-shell.js",
"watch": "NODE_ENV=development webpack --config webpack.config.js --progress --watch",
- "lint": "eslint src",
+ "lint": "eslint src tests scripts",
"lint-fix": "npm run lint -- --fix",
"test:l10n": "node tests/l10n/check-l10n.js",
"test:l10n-parity": "L10N_REQUIRED_LOCALES=nl node tests/l10n/check-l10n-parity.js",
@@ -50,7 +50,7 @@
"extends @nextcloud/browserslist-config"
],
"dependencies": {
- "@conduction/nextcloud-vue": "^2.24.4",
+ "@conduction/nextcloud-vue": "^2.31.1",
"@nextcloud/auth": "^2.6.0",
"@nextcloud/axios": "~2.5.2",
"@nextcloud/capabilities": "^1.2.1",
@@ -78,7 +78,7 @@
"overrides": {
"libxmljs2": "^0.37.0",
"apexcharts": "4.7.0",
- "@nextcloud/axios": "~2.5.2"
+ "@nextcloud/axios": "$@nextcloud/axios"
},
"devDependencies": {
"@babel/core": "^7.22.9",
diff --git a/scripts/build-l10n-js.js b/scripts/build-l10n-js.js
index 176ef519c..335b08d2c 100644
--- a/scripts/build-l10n-js.js
+++ b/scripts/build-l10n-js.js
@@ -101,6 +101,9 @@ function renderJs(id, translations, pluralForm) {
].join('\n')
}
+/**
+ *
+ */
function main() {
const check = process.argv.includes('--check')
const id = appId()
diff --git a/scripts/check-schema-l10n.js b/scripts/check-schema-l10n.js
index 8a860b330..3c4b2626f 100644
--- a/scripts/check-schema-l10n.js
+++ b/scripts/check-schema-l10n.js
@@ -113,6 +113,9 @@ function collect(node, where, sink) {
for (const value of Object.values(node)) collect(value, where, sink)
}
+/**
+ *
+ */
function main() {
const update = process.argv.includes('--update')
const list = process.argv.includes('--list')
diff --git a/scripts/generate-manifest-shell.js b/scripts/generate-manifest-shell.js
index 87f091b71..94d42408b 100644
--- a/scripts/generate-manifest-shell.js
+++ b/scripts/generate-manifest-shell.js
@@ -62,7 +62,7 @@ function slimPages(pages, fragment) {
return pages.map((page) => {
const slim = { _fragment: fragment }
for (const key of SHELL_PAGE_KEYS) {
- if (page && Object.prototype.hasOwnProperty.call(page, key)) {
+ if (page && Object.hasOwn(page, key)) {
slim[key] = page[key]
}
}
@@ -135,7 +135,7 @@ function generateShellDocument(dir = MANIFEST_D_DIR) {
function main() {
const shell = generateShellDocument()
fs.writeFileSync(SHELL_OUTPUT_PATH, JSON.stringify(shell, null, '\t') + '\n')
- // eslint-disable-next-line no-console
+
console.log(
`[generate-manifest-shell] wrote ${SHELL_OUTPUT_PATH} `
+ `(${shell.fragments.length} fragments, `
diff --git a/src/components/Dashboard/BBVComplianceDashboard.vue b/src/components/Dashboard/BBVComplianceDashboard.vue
index 61167a79d..1da5b9ee0 100644
--- a/src/components/Dashboard/BBVComplianceDashboard.vue
+++ b/src/components/Dashboard/BBVComplianceDashboard.vue
@@ -44,7 +44,12 @@
:loading="loading"
:cellHeight="80"
:gridMargin="16"
- :emptyLabel="t('shillinq', 'No widgets configured.')">
+ :emptyLabel="t('shillinq', 'No widgets configured.')"
+ :refreshing="loading"
+ @refresh="loadProgrammes">
+
-
@@ -338,21 +336,11 @@ export default {
min-height: 100%;
}
-.bbv-dashboard__refresh {
- border: 1px solid var(--color-border);
- background: var(--color-main-background);
- color: var(--color-main-text);
- padding: 0.25rem 0.75rem;
- border-radius: var(--border-radius);
- cursor: pointer;
- margin-left: 0.5rem;
-}
-
.bbv-dashboard__fy {
display: inline-flex;
align-items: center;
padding: 0.25rem 0.5rem;
- margin-right: 0.5rem;
+ margin-inline-end: 0.5rem;
border-radius: var(--border-radius);
background: var(--color-primary-element-light);
color: var(--color-primary-element-light-text);
@@ -361,14 +349,10 @@ export default {
}
.bbv-dashboard__administration {
- margin-right: 0.5rem;
+ margin-inline-end: 0.5rem;
max-width: 16rem;
}
-.bbv-dashboard__refresh:hover {
- background: var(--color-background-hover);
-}
-
.bbv-dashboard__error {
margin: 1rem;
padding: 0.75rem 1rem;
diff --git a/src/components/reporting/reportViews.js b/src/components/reporting/reportViews.js
index f67b1283b..201f3d404 100644
--- a/src/components/reporting/reportViews.js
+++ b/src/components/reporting/reportViews.js
@@ -289,6 +289,15 @@ export const reportViews = [
icon: 'ChartTimelineVariantOutline',
category: 'tax',
},
+ {
+ // The seventh report menu entry. The other six were already cards here
+ // when this file was written from the menu-IA audit; this one was
+ // missed, which is why its menu entry outlived the move.
+ id: 'BookkeepingDestructionReport',
+ label: 'Destruction report',
+ icon: 'DeleteClockOutline',
+ category: 'compliance',
+ },
{
id: 'VarianceReport',
label: 'Variance Report',
diff --git a/src/main.js b/src/main.js
index 200621431..e6954e3de 100644
--- a/src/main.js
+++ b/src/main.js
@@ -219,8 +219,35 @@ function routesFromManifest(manifest) {
return routes
}
+/**
+ * The router base for THIS page load.
+ *
+ * β οΈ `generateUrl('/apps/shillinq')` alone is not enough. Nextcloud serves the
+ * app under BOTH `/apps/shillinq/...` and `/index.php/apps/shillinq/...`, but
+ * `generateUrl()` returns only the form the instance is configured for. A
+ * visitor arriving on the other form β a bookmark, an emailed deep link, an
+ * integration that hardcodes `/index.php` β has a pathname the router cannot
+ * strip its base from. No route matches, the catch-all takes over, and they
+ * land on the dashboard with no error at all: the deep link is silently
+ * swallowed.
+ *
+ * Measured on a live instance for learniq, across all 282 of its routes:
+ * `/apps/learniq/courses` resolved to Courses, `/index.php/apps/learniq/courses`
+ * resolved to the dashboard. Every route behaved the same way, so this is not
+ * one broken page but every deep link in that URL form.
+ *
+ * Deriving the base from the pathname makes both forms resolve, because the
+ * base then always matches the URL the visitor actually arrived on.
+ *
+ * @return {string} The base path vue-router should strip from the URL.
+ */
+function routerBase() {
+ const match = window.location.pathname.match(/^(.*\/apps\/shillinq)(?:\/|$)/)
+ return match ? match[1] : generateUrl('/apps/shillinq')
+}
+
const router = createRouter({
- history: createWebHistory(generateUrl('/apps/shillinq')),
+ history: createWebHistory(routerBase()),
routes: routesFromManifest(mergedManifest),
})
diff --git a/src/manifest.d/30-bookkeeping-ib-aangifte-zzp.json b/src/manifest.d/30-bookkeeping-ib-aangifte-zzp.json
index 90ce7ccb3..b74b9c4b8 100755
--- a/src/manifest.d/30-bookkeeping-ib-aangifte-zzp.json
+++ b/src/manifest.d/30-bookkeeping-ib-aangifte-zzp.json
@@ -132,7 +132,7 @@
{
"id": "documents",
"label": "Documents",
- "icon": "FileDocumentOutline",
+ "icon": "FileDocumentMultipleOutline",
"order": 80,
"widgets": [
{ "type": "data", "componentName": "openregister-file-references", "props": { "register": "shillinq", "schema": "IBAangifte", "objectId": ":id" } }
diff --git a/src/manifest.d/40-eu-fondsen.json b/src/manifest.d/40-eu-fondsen.json
index fb2fd232e..ef5e514b5 100755
--- a/src/manifest.d/40-eu-fondsen.json
+++ b/src/manifest.d/40-eu-fondsen.json
@@ -302,7 +302,7 @@
{
"id": "file",
"label": "Document",
- "icon": "FileCertificateOutline",
+ "icon": "FileDocumentOutline",
"order": 10,
"widgets": [
{
diff --git a/src/manifest.d/administration-import-migration.json b/src/manifest.d/administration-import-migration.json
index 72517c142..73ddac704 100755
--- a/src/manifest.d/administration-import-migration.json
+++ b/src/manifest.d/administration-import-migration.json
@@ -121,7 +121,7 @@
{
"id": "documents",
"label": "Documents",
- "icon": "FileDocumentOutline",
+ "icon": "FileDocumentMultipleOutline",
"order": 80,
"widgets": [
{
diff --git a/src/manifest.d/bookkeeping-ifrs-16-lease.json b/src/manifest.d/bookkeeping-ifrs-16-lease.json
index 31939d8c9..5f9504a6a 100755
--- a/src/manifest.d/bookkeeping-ifrs-16-lease.json
+++ b/src/manifest.d/bookkeeping-ifrs-16-lease.json
@@ -76,7 +76,7 @@
{
"id": "documents",
"label": "Documents",
- "icon": "FileDocumentOutline",
+ "icon": "FileDocumentMultipleOutline",
"order": 80,
"widgets": [
{ "type": "data", "componentName": "openregister-file-references", "props": { "register": "shillinq", "schema": "LeaseContract", "objectId": ":id" } }
diff --git a/src/manifest.d/bookkeeping-market-government-separation.json b/src/manifest.d/bookkeeping-market-government-separation.json
index a326a83e9..cebcb4799 100755
--- a/src/manifest.d/bookkeeping-market-government-separation.json
+++ b/src/manifest.d/bookkeeping-market-government-separation.json
@@ -170,7 +170,7 @@
{
"id": "documents",
"label": "Documents",
- "icon": "FileDocumentOutline",
+ "icon": "FileDocumentMultipleOutline",
"order": 80,
"widgets": [
{ "type": "data", "componentName": "openregister-file-references", "props": { "register": "shillinq", "schema": "CommercialActivity", "objectId": ":id" } }
@@ -244,7 +244,7 @@
{
"id": "documents",
"label": "Documents",
- "icon": "FileDocumentOutline",
+ "icon": "FileDocumentMultipleOutline",
"order": 80,
"widgets": [
{ "type": "data", "componentName": "openregister-file-references", "props": { "register": "shillinq", "schema": "IntegralCostPrice", "objectId": ":id" } }
@@ -375,7 +375,7 @@
{
"id": "documents",
"label": "Documents",
- "icon": "FileDocumentOutline",
+ "icon": "FileDocumentMultipleOutline",
"order": 80,
"widgets": [
{ "type": "data", "componentName": "openregister-file-references", "props": { "register": "shillinq", "schema": "AlgemeenBelangBesluit", "objectId": ":id" } }
@@ -446,7 +446,7 @@
{
"id": "documents",
"label": "Documents",
- "icon": "FileDocumentOutline",
+ "icon": "FileDocumentMultipleOutline",
"order": 80,
"widgets": [
{ "type": "data", "componentName": "openregister-file-references", "props": { "register": "shillinq", "schema": "ACMReport", "objectId": ":id" } }
diff --git a/src/manifest.d/bookkeeping-wet-fido-treasury.json b/src/manifest.d/bookkeeping-wet-fido-treasury.json
index adfabd190..512412e88 100755
--- a/src/manifest.d/bookkeeping-wet-fido-treasury.json
+++ b/src/manifest.d/bookkeeping-wet-fido-treasury.json
@@ -165,7 +165,7 @@
"indexRoute": "Treasurystatuten",
"sidebarProps": {
"tabs": [
- {"id": "documents", "label": "Documents", "icon": "FileDocumentOutline", "order": 80, "widgets": [
+ {"id": "documents", "label": "Documents", "icon": "FileDocumentMultipleOutline", "order": 80, "widgets": [
{"type": "data", "componentName": "openregister-file-references", "props": {"register": "shillinq", "schema": "Treasurystatuut", "objectId": ":id"}}
]},
{"id": "audit", "label": "Audit Trail", "icon": "History", "order": 90, "widgets": [
@@ -226,7 +226,7 @@
"indexRoute": "Leningen",
"sidebarProps": {
"tabs": [
- {"id": "documents", "label": "Documents", "icon": "FileDocumentOutline", "order": 80, "widgets": [
+ {"id": "documents", "label": "Documents", "icon": "FileDocumentMultipleOutline", "order": 80, "widgets": [
{"type": "data", "componentName": "openregister-file-references", "props": {"register": "shillinq", "schema": "Lening", "objectId": ":id"}}
]},
{"id": "audit", "label": "Audit Trail", "icon": "History", "order": 90, "widgets": [
@@ -284,7 +284,7 @@
"indexRoute": "Derivaten",
"sidebarProps": {
"tabs": [
- {"id": "documents", "label": "Documents", "icon": "FileDocumentOutline", "order": 80, "widgets": [
+ {"id": "documents", "label": "Documents", "icon": "FileDocumentMultipleOutline", "order": 80, "widgets": [
{"type": "data", "componentName": "openregister-file-references", "props": {"register": "shillinq", "schema": "Derivaat", "objectId": ":id"}}
]},
{"id": "audit", "label": "Audit Trail", "icon": "History", "order": 90, "widgets": [
diff --git a/src/manifest.d/order-workspace.json b/src/manifest.d/order-workspace.json
index 9d24c5846..8bc66f703 100755
--- a/src/manifest.d/order-workspace.json
+++ b/src/manifest.d/order-workspace.json
@@ -72,7 +72,7 @@
],
"sidebarProps": {
"tabs": [
- {"id": "documents", "label": "Documents", "icon": "FileDocumentOutline", "order": 80, "widgets": [
+ {"id": "documents", "label": "Documents", "icon": "FileDocumentMultipleOutline", "order": 80, "widgets": [
{"type": "data", "componentName": "openregister-file-references", "props": {"register": "shillinq", "schema": "OrderPrimitive", "objectId": ":id"}}
]},
{"id": "audit", "label": "Audit Trail", "icon": "History", "order": 90, "widgets": [
diff --git a/src/manifest.json b/src/manifest.json
index 08edb13d9..e605841f6 100755
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -2177,7 +2177,7 @@
"title": "Projects",
"config": {
"register": "shillinq",
- "schema": "Project",
+ "schema": "engagement",
"columns": [
{
"key": "code",
@@ -2214,7 +2214,7 @@
"register": "shillinq",
"_note": "Project detail β a project costing dimension with contract value, WIP and revenue-recognition (RJ 270). Assignments and WIP history are listed (existing related lists); segment P&L aggregation and audit trail evidence recognition method changes.",
"auditTrail": true,
- "schema": "Project",
+ "schema": "engagement",
"fields": [
{
"key": "code",
@@ -16840,7 +16840,7 @@
{
"id": "cancel",
"label": "Cancel",
- "icon": "Cancel",
+ "icon": "CloseCircleOutline",
"visibleWhen": {
"field": "reconciliationStatus",
"in": [
diff --git a/src/menu-layout.json b/src/menu-layout.json
index 3847dab29..4d42547e0 100644
--- a/src/menu-layout.json
+++ b/src/menu-layout.json
@@ -120,6 +120,21 @@
"ImportBatches",
"ImportMappings"
],
- "_removals_note": "EMPTIED 2026-08-10 (gate-53 / ADR-044 no-functionality-loss). This list held 160 ids and its description called them 'leaf menu-entry ids retired as duplicate navigation'. Measured against the gate's own removals-invariant output, that was true of NONE of them: 140 were the ONLY menu entry reaching their route (ADR-044 errors β retiring them left the page with no navigation at all, reachable only by typing the URL), 11 matched no merged menu entry at all (warns β dead ids), and the remaining 9 were routeless GROUP HEADERS, not leaves. The 9 vanished only as a side effect: applyMenuRemovals never drops a node that still has children, so each was pruned by the empty-shell rule (children.length === 0 && hadChildren && !isClickable) once the 140 had taken all its children away. With the 140 restored, all 9 keep their children and are not dropped by any rule β verified by assembling with removals set to exactly those 9 and finding all 9 still in the menu β so every id in this list was either harmful or inert, and the list is now empty. Do not repopulate it without checking, per id, that another surviving menu entry reaches the same route; that check is what was missing, and it cost 140 pages their nav entry in a single change. relocations (above) is the mechanism for re-homing an entry β it moves navigation, it does not delete it.",
- "removals": []
+ "_removals_note": "ADR-112: a report is a card on the Reporting & Compliance page OR a menu entry, never both. All six pages stay routable β deep links, e2e specs and the cards address them by route name β only their menu entries go. Five were already declared as cards in src/components/reporting/reportViews.js when that file was written from the menu-IA audit; BookkeepingDestructionReport was missed there, so it is added as a card in the same change that retires its entry. Retiring an entry whose card did not exist would have removed the report from the product rather than moving it.",
+ "removals": [
+ "EmuRapportage",
+ "VarianceReport",
+ "ConsolidatedReport",
+ "BookkeepingDestructionReport",
+ "Iv3Rapportages",
+ "SisaRapportages"
+ ],
+ "removalsReplacedBy": {
+ "EmuRapportage": "ReportingComplianceOverview",
+ "VarianceReport": "ReportingComplianceOverview",
+ "ConsolidatedReport": "ReportingComplianceOverview",
+ "BookkeepingDestructionReport": "ReportingComplianceOverview",
+ "Iv3Rapportages": "ReportingComplianceOverview",
+ "SisaRapportages": "ReportingComplianceOverview"
+ }
}
diff --git a/tests/Unit/Repair/RematerialiseConvertedCalculationsTest.php b/tests/Unit/Repair/RematerialiseConvertedCalculationsTest.php
index 406885803..d025d1488 100644
--- a/tests/Unit/Repair/RematerialiseConvertedCalculationsTest.php
+++ b/tests/Unit/Repair/RematerialiseConvertedCalculationsTest.php
@@ -69,7 +69,7 @@ class RematerialiseConvertedCalculationsTest extends TestCase {
'ZzpDeduction',
'SisaReport',
'InventoryReorderRule',
- 'Project',
+ 'engagement',
'ProjectAssignment',
'VatReturn',
'InnovatieboxElection',
@@ -215,7 +215,7 @@ public function testEveryTargetedSchemaIsVisited(): void {
* @return void
*/
public function testObjectWithoutIdIsSkipped(): void {
- $rowsBySchema = ['Project' => [['label' => 'no id here']]];
+ $rowsBySchema = ['engagement' => [['label' => 'no id here']]];
$step = $this->makeStep(rowsBySchema: $rowsBySchema);
$step->run($this->output);
@@ -255,7 +255,7 @@ public function testSaveFailureIsBestEffort(): void {
* @return void
*/
public function testFindAllFailureOnOneSchemaDoesNotBlockOthers(): void {
- $rowsBySchema = ['Project' => [['id' => 'p-1']]];
+ $rowsBySchema = ['engagement' => [['id' => 'p-1']]];
$step = $this->makeStep(rowsBySchema: $rowsBySchema, failFindAllSchemas: ['FixedAsset']);
$this->output->expects(self::atLeastOnce())->method('warning');
diff --git a/tests/Unit/Service/InvoiceFromTimeExpenseFragmentTest.php b/tests/Unit/Service/InvoiceFromTimeExpenseFragmentTest.php
index bca781730..a02f376a9 100644
--- a/tests/Unit/Service/InvoiceFromTimeExpenseFragmentTest.php
+++ b/tests/Unit/Service/InvoiceFromTimeExpenseFragmentTest.php
@@ -192,7 +192,7 @@ public function testFragmentMergesAdditivelyOntoMonolith(): void {
}
// Reuse targets are present (we extend the model, never reinvent them).
- foreach (['RateCard', 'UrenRegistratie', 'Receipt', 'Project'] as $reused) {
+ foreach (['RateCard', 'UrenRegistratie', 'Receipt', 'engagement'] as $reused) {
self::assertArrayHasKey($reused, $schemas, "Reuse target $reused must exist");
}
diff --git a/tests/Unit/Settings/CostCentersDimensionsFragmentTest.php b/tests/Unit/Settings/CostCentersDimensionsFragmentTest.php
index f03464441..8a41abda4 100755
--- a/tests/Unit/Settings/CostCentersDimensionsFragmentTest.php
+++ b/tests/Unit/Settings/CostCentersDimensionsFragmentTest.php
@@ -185,7 +185,7 @@ public function testGlLineCarriesSegmentPnlAggregations(): void {
'byCostCenter MUST join through AnalyticalDimension (REQ-ADIM-101 re-targeting)'
);
self::assertSame(
- 'Project',
+ 'engagement',
$aggs['byProject']['join']['through'],
'byProject MUST join through Project'
);
@@ -243,7 +243,7 @@ public function testSeedObjectsContainRequiredExamples(): void {
// Two project seeds β internal-platform + WBSO research grant.
foreach (['proj-internal-platform', 'proj-grant-research'] as $slug) {
- $proj = $byKey($objects, 'Project', $slug);
+ $proj = $byKey($objects, 'engagement', $slug);
self::assertIsArray($proj, 'Project seed ' . $slug . ' MUST be present');
self::assertSame('active', $proj['lifecycleState']);
self::assertSame('adm-default', $proj['administrationId']);
diff --git a/tests/check-manifest-budget.js b/tests/check-manifest-budget.js
index 5a3d8d0f9..b845e1065 100644
--- a/tests/check-manifest-budget.js
+++ b/tests/check-manifest-budget.js
@@ -173,7 +173,6 @@ function main() {
manifestSize = fs.statSync(MANIFEST_PATH).size
fragmentsSize = sumJsonFileSizes(MANIFEST_D_DIR)
} catch (err) {
- // eslint-disable-next-line no-console
console.error(
`[check-manifest-budget] could not read manifest files: ${err.message}`,
)
@@ -183,14 +182,12 @@ function main() {
const total = manifestSize + fragmentsSize
- // eslint-disable-next-line no-console
console.log(
`[check-manifest-budget] manifest.json=${manifestSize}B manifest.d/=${fragmentsSize}B `
+ `total=${total}B budget=${budget}B`,
)
if (total > budget) {
- // eslint-disable-next-line no-console
console.error(
`[check-manifest-budget] FAIL: combined manifest JSON (${total}B) exceeds the `
+ `${budget}B budget shipped in the main webpack chunk (REQ-MBP-001). Either trim `
@@ -201,7 +198,6 @@ function main() {
return
}
- // eslint-disable-next-line no-console
console.log('[check-manifest-budget] PASS')
}
diff --git a/tests/e2e/AccountantPortalDashboard.spec.js b/tests/e2e/AccountantPortalDashboard.spec.js
index 9a368b609..903631a46 100644
--- a/tests/e2e/AccountantPortalDashboard.spec.js
+++ b/tests/e2e/AccountantPortalDashboard.spec.js
@@ -29,7 +29,7 @@
* @spec openspec/changes/accountant-portal/specs/accountant-portal/spec.md#req-acp-002
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
import { becomesVisible } from './becomes-visible.js'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/DeadlineCalendarSettings.spec.js b/tests/e2e/DeadlineCalendarSettings.spec.js
index 47a501cf4..d1c1af103 100644
--- a/tests/e2e/DeadlineCalendarSettings.spec.js
+++ b/tests/e2e/DeadlineCalendarSettings.spec.js
@@ -30,7 +30,7 @@
* @spec openspec/changes/compliance-deadline-calendar/specs/compliance-deadline-calendar/spec.md#req-cdc-006
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
import { becomesVisible } from './becomes-visible.js'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/NavSixClusters.spec.js b/tests/e2e/NavSixClusters.spec.js
index ad12d1279..049ef39e4 100644
--- a/tests/e2e/NavSixClusters.spec.js
+++ b/tests/e2e/NavSixClusters.spec.js
@@ -35,7 +35,7 @@
* @spec openspec/changes/nav-six-clusters/specs/nav-clusters/spec.md#req-navc-009
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
import { becomesVisible } from './becomes-visible.js'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/ar-invoice-einvoice.spec.ts b/tests/e2e/ar-invoice-einvoice.spec.ts
index eff4413a0..d913c3d31 100644
--- a/tests/e2e/ar-invoice-einvoice.spec.ts
+++ b/tests/e2e/ar-invoice-einvoice.spec.ts
@@ -24,7 +24,9 @@
* @spec openspec/changes/add-invoice-pdf-export-with-ubl-peppol-support/specs/bookkeeping-einvoicing-ubl-peppol/spec.md#req-einv-007
*/
-import { test, expect, type Page } from '@playwright/test'
+import type { Page } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
const ROUTE_AR = '/bookkeeping/accounts-receivable'
diff --git a/tests/e2e/bank-statement-wizard.spec.ts b/tests/e2e/bank-statement-wizard.spec.ts
index 9865d6d64..c9dc474e8 100644
--- a/tests/e2e/bank-statement-wizard.spec.ts
+++ b/tests/e2e/bank-statement-wizard.spec.ts
@@ -26,7 +26,9 @@
* @spec openspec/specs/shillinq-bank-statement-wizard/spec.md
*/
-import { test, expect, type Page } from '@playwright/test'
+import type { Page } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
import { becomesVisible } from './becomes-visible.js'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/base-url.ts b/tests/e2e/base-url.ts
index 4184c025c..d3aeeb1f0 100644
--- a/tests/e2e/base-url.ts
+++ b/tests/e2e/base-url.ts
@@ -60,7 +60,6 @@ export function resolveBaseURL(): string {
}
if (process.env.GITHUB_ACTIONS === 'true' || process.env.CI) {
- // eslint-disable-next-line no-console
console.warn(
`[shillinq e2e] none of ${BASE_URL_ENV_NAMES.join(' / ')} is set; `
+ `falling back to the CI-local ${CI_DEFAULT_BASE_URL}.`,
diff --git a/tests/e2e/bbv-compliance.spec.ts b/tests/e2e/bbv-compliance.spec.ts
index a0ddda74e..98ecb4a88 100644
--- a/tests/e2e/bbv-compliance.spec.ts
+++ b/tests/e2e/bbv-compliance.spec.ts
@@ -21,7 +21,9 @@
* @spec openspec/changes/bookkeeping-bbv-compliance/tasks.md (Tasks 5.13-5.19)
*/
-import { test, expect, type Page } from '@playwright/test'
+import type { Page } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
const ROUTE_IV3_AANLEVERING = '/overheid/iv3-aanlevering'
diff --git a/tests/e2e/bill-import-modal.spec.ts b/tests/e2e/bill-import-modal.spec.ts
index d429f0bd2..7ba42e9dd 100644
--- a/tests/e2e/bill-import-modal.spec.ts
+++ b/tests/e2e/bill-import-modal.spec.ts
@@ -22,7 +22,9 @@
* @spec openspec/specs/shillinq-bill-import-modal/spec.md
*/
-import { test, expect, type Page } from '@playwright/test'
+import type { Page } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
const ROUTE_FINANCIAL = '/financial'
diff --git a/tests/e2e/bookings-calendar.spec.ts b/tests/e2e/bookings-calendar.spec.ts
index be793e247..1d2e99f48 100644
--- a/tests/e2e/bookings-calendar.spec.ts
+++ b/tests/e2e/bookings-calendar.spec.ts
@@ -22,7 +22,7 @@
* scenario against the bookings-resource-calendar spec.
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/bookings-notification-triggers.spec.ts b/tests/e2e/bookings-notification-triggers.spec.ts
index 95adf9de8..50d66d675 100644
--- a/tests/e2e/bookings-notification-triggers.spec.ts
+++ b/tests/e2e/bookings-notification-triggers.spec.ts
@@ -17,7 +17,7 @@
* @spec openspec/changes/bookings-notification-triggers/tasks.md#task-17
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
const NOTIFICATION_TRIGGERS_ROUTE = '/communication/notification-triggers'
diff --git a/tests/e2e/bookings-resource-calendar.spec.ts b/tests/e2e/bookings-resource-calendar.spec.ts
index 7271cd9ec..df4bc81e7 100644
--- a/tests/e2e/bookings-resource-calendar.spec.ts
+++ b/tests/e2e/bookings-resource-calendar.spec.ts
@@ -35,7 +35,9 @@
* @spec openspec/changes/bookings-resource-calendar/tasks.md#task-11
*/
-import { test, expect, type Page } from '@playwright/test'
+import type { Page } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/bookings-screenshots.spec.ts b/tests/e2e/bookings-screenshots.spec.ts
index 74bab86a7..e71313869 100644
--- a/tests/e2e/bookings-screenshots.spec.ts
+++ b/tests/e2e/bookings-screenshots.spec.ts
@@ -26,9 +26,11 @@
* automatically (URL is unchanged from the docs).
*/
-import { test, expect, type Page } from '@playwright/test'
-import * as path from 'path'
+import type { Page } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
import * as fs from 'fs'
+import * as path from 'path'
const SHOT_ROOT = path.resolve(
__dirname,
diff --git a/tests/e2e/bookings-widget-embed.spec.ts b/tests/e2e/bookings-widget-embed.spec.ts
index b76ceac42..9c353a1de 100644
--- a/tests/e2e/bookings-widget-embed.spec.ts
+++ b/tests/e2e/bookings-widget-embed.spec.ts
@@ -30,7 +30,7 @@
* @spec openspec/changes/bookings-self-service-widget/tasks.md#task-21
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
const SERVICES_API = APP + '/api/widget/services'
diff --git a/tests/e2e/bookkeeping-foundation.spec.ts b/tests/e2e/bookkeeping-foundation.spec.ts
index 595c0d962..33dbefeaa 100644
--- a/tests/e2e/bookkeeping-foundation.spec.ts
+++ b/tests/e2e/bookkeeping-foundation.spec.ts
@@ -30,7 +30,7 @@
* "page mounted on the correct route", not "list has N rows".
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/bookkeeping-ifrs15-revenue.spec.ts b/tests/e2e/bookkeeping-ifrs15-revenue.spec.ts
index 07a81cfbf..9ebdbaa10 100644
--- a/tests/e2e/bookkeeping-ifrs15-revenue.spec.ts
+++ b/tests/e2e/bookkeeping-ifrs15-revenue.spec.ts
@@ -26,7 +26,7 @@
* @spec openspec/changes/bookkeeping-ifrs15-revenue/tasks.md#browser-tests
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/bookkeeping-period-close.spec.ts b/tests/e2e/bookkeeping-period-close.spec.ts
index 2e0a7a62f..663cc357a 100644
--- a/tests/e2e/bookkeeping-period-close.spec.ts
+++ b/tests/e2e/bookkeeping-period-close.spec.ts
@@ -27,7 +27,7 @@
* @spec openspec/changes/bookkeeping-period-close/tasks.md#task-16
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
const INDEX_PATH = '/bookkeeping/period-close'
diff --git a/tests/e2e/bookkeeping-tenderned-integratie.spec.ts b/tests/e2e/bookkeeping-tenderned-integratie.spec.ts
index 3bb758964..133c8ca04 100644
--- a/tests/e2e/bookkeeping-tenderned-integratie.spec.ts
+++ b/tests/e2e/bookkeeping-tenderned-integratie.spec.ts
@@ -22,13 +22,13 @@
* @spec openspec/specs/bookkeeping-tenderned-integratie/spec.md#req-001
*/
-import { test, expect } from '@playwright/test'
+import type { Page } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
-const dismissWizard = async (
- page: import('@playwright/test').Page,
-): Promise => {
+async function dismissWizard(page: Page): Promise {
const wizard = page.locator('#firstrunwizard')
if (await wizard.isVisible().catch(() => false)) {
await page.keyboard.press('Escape').catch(() => {})
diff --git a/tests/e2e/bookkeeping-vpb-corporate-tax.spec.ts b/tests/e2e/bookkeeping-vpb-corporate-tax.spec.ts
index f669f0961..b0b6eec19 100644
--- a/tests/e2e/bookkeeping-vpb-corporate-tax.spec.ts
+++ b/tests/e2e/bookkeeping-vpb-corporate-tax.spec.ts
@@ -36,13 +36,13 @@
* @spec openspec/changes/bookkeeping-vpb-corporate-tax/tasks.md#task-42
*/
-import { test, expect } from '@playwright/test'
+import type { Page } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
-const dismissWizard = async (
- page: import('@playwright/test').Page,
-): Promise => {
+async function dismissWizard(page: Page): Promise {
const wizard = page.locator('#firstrunwizard')
if (await wizard.isVisible().catch(() => false)) {
await page.keyboard.press('Escape').catch(() => {})
diff --git a/tests/e2e/bookkeeping-vpb-quarterly-report.spec.ts b/tests/e2e/bookkeeping-vpb-quarterly-report.spec.ts
index a6149ddad..1d0a4a528 100644
--- a/tests/e2e/bookkeeping-vpb-quarterly-report.spec.ts
+++ b/tests/e2e/bookkeeping-vpb-quarterly-report.spec.ts
@@ -34,13 +34,13 @@
* @spec openspec/changes/bookkeeping-vpb-corporate-tax/tasks.md#task-43
*/
-import { test, expect } from '@playwright/test'
+import type { Page } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
-const dismissWizard = async (
- page: import('@playwright/test').Page,
-): Promise => {
+async function dismissWizard(page: Page): Promise {
const wizard = page.locator('#firstrunwizard')
if (await wizard.isVisible().catch(() => false)) {
await page.keyboard.press('Escape').catch(() => {})
diff --git a/tests/e2e/budget-line-commitments.spec.ts b/tests/e2e/budget-line-commitments.spec.ts
index eb324bb48..a9b141b82 100644
--- a/tests/e2e/budget-line-commitments.spec.ts
+++ b/tests/e2e/budget-line-commitments.spec.ts
@@ -31,7 +31,9 @@
* @spec openspec/changes/verplichtingen-commitment-accounting/specs/bookkeeping-verplichtingenadministratie/spec.md#req-vpl-011
*/
-import { test, expect, type Page } from '@playwright/test'
+import type { Page } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
import { becomesVisible } from './becomes-visible.js'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/cashflow-13wk.spec.ts b/tests/e2e/cashflow-13wk.spec.ts
index 66dac4a8e..5ac51654f 100644
--- a/tests/e2e/cashflow-13wk.spec.ts
+++ b/tests/e2e/cashflow-13wk.spec.ts
@@ -13,7 +13,7 @@
* @spec openspec/changes/zzp-cashflow-13wk/tasks.md#task-30
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/cbs-submissions.spec.ts b/tests/e2e/cbs-submissions.spec.ts
index a7d23bb7a..08b61c85a 100644
--- a/tests/e2e/cbs-submissions.spec.ts
+++ b/tests/e2e/cbs-submissions.spec.ts
@@ -42,7 +42,9 @@
* @e2e security-endpoint-guards/req-001/cbs-submissions-delete-own-draft
*/
-import { test, expect, type Page, type APIRequestContext } from '@playwright/test'
+import type { APIRequestContext, Page } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
const LIST_ROUTE = '/bookkeeping/cbs-submissions'
@@ -113,7 +115,6 @@ async function cleanupViaOpenRegister(
headers: { 'OCS-APIRequest': 'true' },
})
if (deleted.ok() === false && deleted.status() !== 404) {
- // eslint-disable-next-line no-console
console.warn(
`[cbs-submissions] failed to clean up seeded submission ${id}: HTTP ${deleted.status()}`,
)
diff --git a/tests/e2e/chart-of-accounts.spec.ts b/tests/e2e/chart-of-accounts.spec.ts
index 68ee66a67..b75723607 100644
--- a/tests/e2e/chart-of-accounts.spec.ts
+++ b/tests/e2e/chart-of-accounts.spec.ts
@@ -10,7 +10,7 @@
* scenario tags are emitted.
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/contracts-single-home.spec.ts b/tests/e2e/contracts-single-home.spec.ts
index 8f8f8770c..d9e3d6cbf 100644
--- a/tests/e2e/contracts-single-home.spec.ts
+++ b/tests/e2e/contracts-single-home.spec.ts
@@ -35,7 +35,9 @@
* @spec openspec/changes/contracts-single-home/specs/contracts-single-home/spec.md
*/
-import { test, expect, Page } from '@playwright/test'
+import type { Page } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/docs-screenshots.spec.ts b/tests/e2e/docs-screenshots.spec.ts
index df6389715..2520f4ada 100644
--- a/tests/e2e/docs-screenshots.spec.ts
+++ b/tests/e2e/docs-screenshots.spec.ts
@@ -38,9 +38,11 @@
* Pattern reference: ADR-030 (hydra/openspec/architecture/).
*/
-import { test, expect, type Page } from '@playwright/test'
-import * as path from 'path'
+import type { Page } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
import * as fs from 'fs'
+import * as path from 'path'
const SHOT_ROOT = path.resolve(
__dirname,
diff --git a/tests/e2e/external-adapters.spec.ts b/tests/e2e/external-adapters.spec.ts
index bbc8bbc5b..5f94a25a8 100644
--- a/tests/e2e/external-adapters.spec.ts
+++ b/tests/e2e/external-adapters.spec.ts
@@ -1,3 +1,5 @@
+import type { ConsoleMessage, Page } from '@playwright/test'
+
/*
* SPDX-FileCopyrightText: 2026 Conduction B.V.
* SPDX-License-Identifier: EUPL-1.2
@@ -33,7 +35,7 @@
*
* @spec openspec/changes/integration-config-to-openconnector/specs/integration-config-to-openconnector/spec.md
*/
-import { test, expect, type Page, type ConsoleMessage } from '@playwright/test'
+import { expect, test } from '@playwright/test'
import { readdirSync, readFileSync, statSync } from 'node:fs'
import { join } from 'node:path'
diff --git a/tests/e2e/financial-dashboard.spec.ts b/tests/e2e/financial-dashboard.spec.ts
index d870f816d..be3c34395 100644
--- a/tests/e2e/financial-dashboard.spec.ts
+++ b/tests/e2e/financial-dashboard.spec.ts
@@ -57,7 +57,7 @@
* @e2e apphost-adoption::app-ui-is-unaffected-by-the-generic-controllers
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/global-setup.ts b/tests/e2e/global-setup.ts
index 45577b60a..b5abe578e 100644
--- a/tests/e2e/global-setup.ts
+++ b/tests/e2e/global-setup.ts
@@ -20,11 +20,13 @@
* adopter).
*/
-import { chromium, request, type FullConfig, type Page } from '@playwright/test'
-import { resolveBaseURL } from './base-url'
+import type { FullConfig, Page } from '@playwright/test'
+
+import { chromium, request } from '@playwright/test'
import { execSync } from 'child_process'
-import * as path from 'path'
import * as fs from 'fs'
+import * as path from 'path'
+import { resolveBaseURL } from './base-url.ts'
const AUTH_DIR = path.resolve(__dirname, '.auth')
const STORAGE_STATE = path.join(AUTH_DIR, 'admin.json')
@@ -72,13 +74,12 @@ function ensureBundleBuilt(): void {
return
}
if (fs.existsSync(BUNDLE_PATH)) {
- // eslint-disable-next-line no-console
console.log(
`[playwright globalSetup] bundle at ${BUNDLE_PATH} is only `
+ `${fs.statSync(BUNDLE_PATH).size} bytes (floor ${MIN_BUNDLE_BYTES}); rebuilding.`,
)
}
- // eslint-disable-next-line no-console
+
console.log(
`[playwright globalSetup] bundle missing at ${BUNDLE_PATH}; running 'npm run build' onceβ¦`,
)
@@ -170,7 +171,7 @@ async function markWalkthroughSeen(page: Page): Promise {
+ 'The walkthrough overlay would intercept pointer events for the whole run.',
)
}
- // eslint-disable-next-line no-console
+
console.log(
`[playwright globalSetup] ${WALKTHROUGH_SEEN_KEY} = ${version} (walkthrough will not auto-start)`,
)
@@ -212,7 +213,7 @@ async function markSetupWizardDismissed(page: Page): Promise {
+ 'The setup wizard would render over the shell and intercept pointer events for the whole run.',
)
}
- // eslint-disable-next-line no-console
+
console.log(
'[playwright globalSetup] cn-setup-wizard-dismissed:shillinq:0..20 = 1 (setup wizard will not auto-open)',
)
diff --git a/tests/e2e/icp-opgaaf.spec.ts b/tests/e2e/icp-opgaaf.spec.ts
index 4883eade6..01481a716 100644
--- a/tests/e2e/icp-opgaaf.spec.ts
+++ b/tests/e2e/icp-opgaaf.spec.ts
@@ -27,7 +27,7 @@
* @spec openspec/changes/bookkeeping-icp-opgaaf/tasks.md
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/innovatiebox-administratie.spec.ts b/tests/e2e/innovatiebox-administratie.spec.ts
index 8607ac6f3..e1404c590 100644
--- a/tests/e2e/innovatiebox-administratie.spec.ts
+++ b/tests/e2e/innovatiebox-administratie.spec.ts
@@ -20,7 +20,7 @@
* register fragment is imported into a running instance.
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/inventory-mobile-scanner.spec.ts b/tests/e2e/inventory-mobile-scanner.spec.ts
index 5251d4b1d..3c44788a7 100644
--- a/tests/e2e/inventory-mobile-scanner.spec.ts
+++ b/tests/e2e/inventory-mobile-scanner.spec.ts
@@ -12,7 +12,7 @@
* @spec openspec/changes/inventory-mobile-scanner/tasks.md
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/invoice-quick-draft.spec.ts b/tests/e2e/invoice-quick-draft.spec.ts
index d9ae455ef..56f322f79 100644
--- a/tests/e2e/invoice-quick-draft.spec.ts
+++ b/tests/e2e/invoice-quick-draft.spec.ts
@@ -31,7 +31,7 @@
* @spec openspec/changes/shillinq-invoice-quick-draft/specs/shillinq-invoice-quick-draft/spec.md
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/l10n-browser-catalogue.spec.ts b/tests/e2e/l10n-browser-catalogue.spec.ts
index 047d6072c..1197e6aab 100644
--- a/tests/e2e/l10n-browser-catalogue.spec.ts
+++ b/tests/e2e/l10n-browser-catalogue.spec.ts
@@ -39,11 +39,10 @@
* nothing about apps whose translations differ.
*/
+import { expect, test } from '@playwright/test'
import { readFileSync } from 'node:fs'
import path from 'node:path'
-import { expect, test } from '@playwright/test'
-
/**
* The app id this repo declares. Resolved from the repo root by walking up
* from this file, so it does not depend on the working directory playwright
diff --git a/tests/e2e/list-views-cndatatable.spec.ts b/tests/e2e/list-views-cndatatable.spec.ts
index 0ba6e75d4..33d9d8ec0 100644
--- a/tests/e2e/list-views-cndatatable.spec.ts
+++ b/tests/e2e/list-views-cndatatable.spec.ts
@@ -24,7 +24,7 @@
* @spec openspec/changes/migrate-list-views-to-cndatatable/specs/list-views-cndatatable/spec.md
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/order-primitive.spec.ts b/tests/e2e/order-primitive.spec.ts
index c8fede7e4..53db47f92 100644
--- a/tests/e2e/order-primitive.spec.ts
+++ b/tests/e2e/order-primitive.spec.ts
@@ -33,8 +33,10 @@
* @e2e order-primitive::a-transition-never-crosses-ordertype-boundaries
*/
-import { test, expect, request as pwRequest } from '@playwright/test'
-import { OrFixtures, UNIQUE_PREFIX } from './workflows/_fixtures'
+import type { APIRequestContext } from '@playwright/test'
+
+import { expect, request as pwRequest, test } from '@playwright/test'
+import { OrFixtures, UNIQUE_PREFIX } from './workflows/_fixtures.ts'
const SCHEMA = 'OrderPrimitive'
const ADMIN_ID = 'ADM-001'
@@ -76,7 +78,7 @@ test.describe('order-primitive β Order fold + orderType-gated lifecycle (#503)
// can manufacture green in CI.
let fx: OrFixtures
- let api: import('@playwright/test').APIRequestContext
+ let api: APIRequestContext
test.beforeAll(async ({ baseURL }) => {
api = await pwRequest.newContext({
diff --git a/tests/e2e/oss-btw-eu.spec.ts b/tests/e2e/oss-btw-eu.spec.ts
index b2fcb2f43..24b4334d6 100644
--- a/tests/e2e/oss-btw-eu.spec.ts
+++ b/tests/e2e/oss-btw-eu.spec.ts
@@ -17,7 +17,7 @@
* emitted by this smoke.
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/pipelinq-customer-bridge.spec.ts b/tests/e2e/pipelinq-customer-bridge.spec.ts
index bf2f12138..ee340f13d 100644
--- a/tests/e2e/pipelinq-customer-bridge.spec.ts
+++ b/tests/e2e/pipelinq-customer-bridge.spec.ts
@@ -31,7 +31,7 @@
* @spec openspec/changes/bookings-pipelinq-customer-bridge-10-integration-e2e-tests/tasks.md
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const SHILLINQ_ADMIN_SETTINGS = '/settings/admin/shillinq'
diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts
index 1e8fbe516..3fe9fe21e 100644
--- a/tests/e2e/playwright.config.ts
+++ b/tests/e2e/playwright.config.ts
@@ -99,8 +99,7 @@
import { defineConfig, devices } from '@playwright/test'
import * as path from 'path'
-
-import { resolveBaseURL } from './base-url'
+import { resolveBaseURL } from './base-url.ts'
/**
* Everything under `tests/e2e` that is NOT part of the CI regression suite.
diff --git a/tests/e2e/provincies-bbv-routes-smoke.spec.ts b/tests/e2e/provincies-bbv-routes-smoke.spec.ts
index 748178a8b..afa64969c 100644
--- a/tests/e2e/provincies-bbv-routes-smoke.spec.ts
+++ b/tests/e2e/provincies-bbv-routes-smoke.spec.ts
@@ -49,7 +49,9 @@
* @spec openspec/changes/bookkeeping-provincies-bbv-variant/tasks.md#task-26
*/
-import { test, expect, type APIRequestContext } from '@playwright/test'
+import type { APIRequestContext } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
const DASHBOARD_ROUTE = APP + '/bbv-provincie/compliance-dashboard'
diff --git a/tests/e2e/provincies-bbv-variant.spec.ts b/tests/e2e/provincies-bbv-variant.spec.ts
index 12c41a9da..edbd46d85 100644
--- a/tests/e2e/provincies-bbv-variant.spec.ts
+++ b/tests/e2e/provincies-bbv-variant.spec.ts
@@ -36,7 +36,9 @@
* @spec openspec/changes/bookkeeping-provincies-bbv-variant/tasks.md
*/
-import { test, expect, type Page } from '@playwright/test'
+import type { Page } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
const DASHBOARD_ROUTE = '/bbv-provincie/compliance-dashboard'
diff --git a/tests/e2e/receipt-extraction-consume.spec.ts b/tests/e2e/receipt-extraction-consume.spec.ts
index cd6a155bd..d8d3cc703 100644
--- a/tests/e2e/receipt-extraction-consume.spec.ts
+++ b/tests/e2e/receipt-extraction-consume.spec.ts
@@ -26,7 +26,9 @@
* @spec openspec/changes/receipt-extraction-consume/specs/receipt-extraction-consume/spec.md#req-rxc-006
*/
-import { test, expect, type Page } from '@playwright/test'
+import type { Page } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/recurring-invoicing.spec.ts b/tests/e2e/recurring-invoicing.spec.ts
index ba50eef56..62438730e 100644
--- a/tests/e2e/recurring-invoicing.spec.ts
+++ b/tests/e2e/recurring-invoicing.spec.ts
@@ -28,7 +28,7 @@
* @e2e recurring-invoicing::dutch-ui-renders-translated-strings-from-english-keys
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/retire-cost-project.spec.ts b/tests/e2e/retire-cost-project.spec.ts
index 4e68794b5..1f10c4119 100644
--- a/tests/e2e/retire-cost-project.spec.ts
+++ b/tests/e2e/retire-cost-project.spec.ts
@@ -25,7 +25,7 @@
* @spec openspec/changes/retire-cost-project/specs/retire-cost-project/spec.md (REQ-RCP-004/REQ-RCP-006)
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/setup-wizard-english.spec.ts b/tests/e2e/setup-wizard-english.spec.ts
index 4f46036f2..317a7cd75 100644
--- a/tests/e2e/setup-wizard-english.spec.ts
+++ b/tests/e2e/setup-wizard-english.spec.ts
@@ -43,12 +43,13 @@
* @spec openspec/changes/setup-wizard-english/specs/setup-wizard-english/spec.md
*/
-import { test, expect, request, type Page } from '@playwright/test'
+import type { Page } from '@playwright/test'
+
+import { expect, request, test } from '@playwright/test'
import { execSync } from 'child_process'
import * as fs from 'fs'
import * as path from 'path'
-
-import { resolveBaseURL } from './base-url'
+import { resolveBaseURL } from './base-url.ts'
const APP = '/apps/shillinq'
const APP_ROOT = path.resolve(__dirname, '..', '..')
@@ -180,7 +181,6 @@ async function restoreCiSeedBaseline(baseURL: string): Promise {
const status = await ctx.get('/index.php/apps/shillinq/api/setup/status')
const body = await status.json().catch(() => ({}))
if (body?.completed !== true) {
- // eslint-disable-next-line no-console
console.warn(
'[setup-wizard-english] afterAll restore did not report completed:true β '
+ `sibling specs may see a blocking setup dialog. status: ${JSON.stringify(body)}`,
@@ -414,7 +414,6 @@ test.describe('Setup wizard β English source text (REQ-SWE-005)', () => {
.getByRole('button', { name: /finish|complete|done|close/i })
.last()
await finishButton.click({ timeout: 10_000 }).catch(() => {
- // eslint-disable-next-line no-console
console.warn(
'[setup-wizard-english] no explicit finish button matched; the wizard may auto-close on `completed: true`.',
)
diff --git a/tests/e2e/spec-coverage/_helpers.ts b/tests/e2e/spec-coverage/_helpers.ts
index c55751161..569bf4054 100644
--- a/tests/e2e/spec-coverage/_helpers.ts
+++ b/tests/e2e/spec-coverage/_helpers.ts
@@ -59,7 +59,9 @@
* relaxed and no failing test can become passing.
*/
-import { expect, type Page } from '@playwright/test'
+import type { Page } from '@playwright/test'
+
+import { expect } from '@playwright/test'
export const APP = '/apps/shillinq'
diff --git a/tests/e2e/spec-coverage/belastingen.spec.ts b/tests/e2e/spec-coverage/belastingen.spec.ts
index b14640f1d..e21bf6799 100644
--- a/tests/e2e/spec-coverage/belastingen.spec.ts
+++ b/tests/e2e/spec-coverage/belastingen.spec.ts
@@ -15,11 +15,11 @@
import { test } from '@playwright/test'
import {
- gotoPage,
assertIndexSurface,
assertNoShillinqFailures,
+ gotoPage,
recordShillinqErrors,
-} from './_helpers'
+} from './_helpers.ts'
const PAGES: Array<{ route: string; title: string; titleRe?: RegExp }> = [
{ route: '/belastingen/kor', title: 'KOR', titleRe: /KOR/i },
diff --git a/tests/e2e/spec-coverage/bookkeeping.spec.ts b/tests/e2e/spec-coverage/bookkeeping.spec.ts
index f026292ee..d157b7a43 100644
--- a/tests/e2e/spec-coverage/bookkeeping.spec.ts
+++ b/tests/e2e/spec-coverage/bookkeeping.spec.ts
@@ -14,11 +14,11 @@
import { test } from '@playwright/test'
import {
- gotoPage,
assertIndexSurface,
assertNoShillinqFailures,
+ gotoPage,
recordShillinqErrors,
-} from './_helpers'
+} from './_helpers.ts'
const PAGES: Array<{ route: string; title: string; titleRe?: RegExp }> = [
{ route: '/chart-of-accounts', title: 'Chart of Accounts' },
diff --git a/tests/e2e/spec-coverage/cashflow-pension.spec.ts b/tests/e2e/spec-coverage/cashflow-pension.spec.ts
index bf1c7ed6e..9007160f3 100644
--- a/tests/e2e/spec-coverage/cashflow-pension.spec.ts
+++ b/tests/e2e/spec-coverage/cashflow-pension.spec.ts
@@ -9,11 +9,11 @@
import { test } from '@playwright/test'
import {
- gotoPage,
assertIndexSurface,
assertNoShillinqFailures,
+ gotoPage,
recordShillinqErrors,
-} from './_helpers'
+} from './_helpers.ts'
const PAGES: Array<{ route: string; title: string; titleRe?: RegExp }> = [
{
diff --git a/tests/e2e/spec-coverage/dashboard-settings.spec.ts b/tests/e2e/spec-coverage/dashboard-settings.spec.ts
index 88f9cbb2b..f8e09d2c1 100644
--- a/tests/e2e/spec-coverage/dashboard-settings.spec.ts
+++ b/tests/e2e/spec-coverage/dashboard-settings.spec.ts
@@ -8,14 +8,14 @@
* Data-independent.
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
import {
APP,
- gotoPage,
- dismissOverlays,
assertNoShillinqFailures,
+ dismissOverlays,
+ gotoPage,
recordShillinqErrors,
-} from './_helpers'
+} from './_helpers.ts'
test.describe('shillinq spec-coverage β Dashboard & Settings', () => {
// No `mode: 'serial'` β see the header of ./_helpers.ts. The Dashboard
diff --git a/tests/e2e/spec-coverage/demo-data-setup-step.spec.ts b/tests/e2e/spec-coverage/demo-data-setup-step.spec.ts
index 16b774837..5dc1767e1 100644
--- a/tests/e2e/spec-coverage/demo-data-setup-step.spec.ts
+++ b/tests/e2e/spec-coverage/demo-data-setup-step.spec.ts
@@ -28,7 +28,9 @@
* (`setup-demo-data-first`) checks it statically on every change. Claiming to
* prove it here would be asserting something this vantage point cannot see.
*/
-import { test, expect, Page } from '@playwright/test'
+import type { Page } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
const BASE = '/apps/shillinq'
@@ -44,12 +46,12 @@ async function api(
method,
headers: {
'Content-Type': 'application/json',
- // eslint-disable-next-line no-undef
+
requesttoken: (window as any).OC?.requestToken || '',
'OCS-APIREQUEST': 'true',
},
})
- let json: any = null
+ let json: any
try {
json = await res.json()
} catch {
diff --git a/tests/e2e/spec-coverage/dimensions.spec.ts b/tests/e2e/spec-coverage/dimensions.spec.ts
index ddccfff24..a24fc8aa7 100644
--- a/tests/e2e/spec-coverage/dimensions.spec.ts
+++ b/tests/e2e/spec-coverage/dimensions.spec.ts
@@ -16,13 +16,13 @@
* page's own "Analytical dimensions", not the old per-route titles.
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
import {
- gotoPage,
assertIndexSurface,
assertNoShillinqFailures,
+ gotoPage,
recordShillinqErrors,
-} from './_helpers'
+} from './_helpers.ts'
const PAGES: Array<{ route: string; title: string; titleRe?: RegExp }> = [
{
diff --git a/tests/e2e/spec-coverage/inkoop.spec.ts b/tests/e2e/spec-coverage/inkoop.spec.ts
index af51a6d26..7d4ecef9b 100644
--- a/tests/e2e/spec-coverage/inkoop.spec.ts
+++ b/tests/e2e/spec-coverage/inkoop.spec.ts
@@ -15,7 +15,7 @@ import {
assertNoShillinqFailures,
gotoPage,
recordShillinqErrors,
-} from './_helpers'
+} from './_helpers.ts'
const PAGES: Array<{ route: string; title: string; titleRe?: RegExp }> = [
{ route: '/inkoop/purchase-orders', title: 'Purchase Orders' },
diff --git a/tests/e2e/spec-coverage/inventory.spec.ts b/tests/e2e/spec-coverage/inventory.spec.ts
index 64866f3ea..00db7b2d1 100644
--- a/tests/e2e/spec-coverage/inventory.spec.ts
+++ b/tests/e2e/spec-coverage/inventory.spec.ts
@@ -10,11 +10,11 @@
import { test } from '@playwright/test'
import {
- gotoPage,
assertIndexSurface,
assertNoShillinqFailures,
+ gotoPage,
recordShillinqErrors,
-} from './_helpers'
+} from './_helpers.ts'
const PAGES: Array<{ route: string; title: string; titleRe?: RegExp }> = [
// β οΈ These two currently FAIL, and that is the spec working β see #860.
diff --git a/tests/e2e/spec-coverage/overheid-compliance.spec.ts b/tests/e2e/spec-coverage/overheid-compliance.spec.ts
index 460f22567..f61114767 100644
--- a/tests/e2e/spec-coverage/overheid-compliance.spec.ts
+++ b/tests/e2e/spec-coverage/overheid-compliance.spec.ts
@@ -10,11 +10,11 @@
import { test } from '@playwright/test'
import {
- gotoPage,
assertIndexSurface,
assertNoShillinqFailures,
+ gotoPage,
recordShillinqErrors,
-} from './_helpers'
+} from './_helpers.ts'
const PAGES: Array<{ route: string; title: string; titleRe?: RegExp }> = [
// Overheid
diff --git a/tests/e2e/spec-coverage/projecten.spec.ts b/tests/e2e/spec-coverage/projecten.spec.ts
index f2be092ca..3fbdbd243 100644
--- a/tests/e2e/spec-coverage/projecten.spec.ts
+++ b/tests/e2e/spec-coverage/projecten.spec.ts
@@ -15,11 +15,11 @@
import { test } from '@playwright/test'
import {
- gotoPage,
assertIndexSurface,
assertNoShillinqFailures,
+ gotoPage,
recordShillinqErrors,
-} from './_helpers'
+} from './_helpers.ts'
const PAGES: Array<{ route: string; title: string; titleRe?: RegExp }> = [
{
diff --git a/tests/e2e/standards-policy-editor.spec.ts b/tests/e2e/standards-policy-editor.spec.ts
index a77d0d93e..64b8dca7a 100644
--- a/tests/e2e/standards-policy-editor.spec.ts
+++ b/tests/e2e/standards-policy-editor.spec.ts
@@ -38,7 +38,7 @@
* @spec openspec/specs/accounting-standards-policy/spec.md#REQ-ASP-002
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
const ROUTE = '/settings/accounting-standards'
diff --git a/tests/e2e/trial-balance.spec.ts b/tests/e2e/trial-balance.spec.ts
index 036ed0bac..1e62270ef 100644
--- a/tests/e2e/trial-balance.spec.ts
+++ b/tests/e2e/trial-balance.spec.ts
@@ -19,7 +19,7 @@
* running instance.
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/visual/_visual-helpers.ts b/tests/e2e/visual/_visual-helpers.ts
index a96bb7b58..0d80da57c 100644
--- a/tests/e2e/visual/_visual-helpers.ts
+++ b/tests/e2e/visual/_visual-helpers.ts
@@ -1,3 +1,5 @@
+import type { Locator, Page } from '@playwright/test'
+
/*
* SPDX-License-Identifier: EUPL-1.2
*
@@ -24,7 +26,7 @@
* own baselines on first run, or (b) stay non-gating until baselined in the CI
* environment. See tests/e2e/visual/README in-repo wiring notes.
*/
-import { expect, type Page, type Locator } from '@playwright/test'
+import { expect } from '@playwright/test'
/** Common screenshot options applied to every visual assertion. */
export const SHOT_OPTIONS = {
diff --git a/tests/e2e/visual/external-adapters.visual.spec.ts b/tests/e2e/visual/external-adapters.visual.spec.ts
index f4cab172d..53202c5e1 100644
--- a/tests/e2e/visual/external-adapters.visual.spec.ts
+++ b/tests/e2e/visual/external-adapters.visual.spec.ts
@@ -1,3 +1,5 @@
+import type { Page } from '@playwright/test'
+
/*
* SPDX-License-Identifier: EUPL-1.2
*
@@ -30,14 +32,14 @@
* See _visual-helpers.ts for the platform-rendering caveat + the shared
* freeze / dismiss / mask determinism guarantees.
*/
-import { test, expect, type Page } from '@playwright/test'
+import { expect, test } from '@playwright/test'
import {
dismissSupportDialog,
- waitForContentReady,
+ dynamicMasks,
freezePage,
SHOT_OPTIONS,
- dynamicMasks,
-} from './_visual-helpers'
+ waitForContentReady,
+} from './_visual-helpers.ts'
// Use the SPA's history-mode base (no /index.php/ prefix) so the deep-link
// matches the vue-router base and does not get reset to the dashboard.
diff --git a/tests/e2e/visual/shillinq.visual.spec.ts b/tests/e2e/visual/shillinq.visual.spec.ts
index 0dd78a603..a72bdb1a6 100644
--- a/tests/e2e/visual/shillinq.visual.spec.ts
+++ b/tests/e2e/visual/shillinq.visual.spec.ts
@@ -11,7 +11,7 @@
* See _visual-helpers.ts for the platform-rendering caveat.
*/
import { test } from '@playwright/test'
-import { shootSurface } from './_visual-helpers'
+import { shootSurface } from './_visual-helpers.ts'
const APP = '/index.php/apps/shillinq'
diff --git a/tests/e2e/waterschappen-bbv-routes-smoke.spec.ts b/tests/e2e/waterschappen-bbv-routes-smoke.spec.ts
index 0ad1c4069..3f243a41b 100644
--- a/tests/e2e/waterschappen-bbv-routes-smoke.spec.ts
+++ b/tests/e2e/waterschappen-bbv-routes-smoke.spec.ts
@@ -30,7 +30,7 @@
* @spec openspec/changes/bookkeeping-waterschappen-bbv-variant-11-testing/tasks.md#smoke-tests
*/
-import { test, expect } from '@playwright/test'
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
diff --git a/tests/e2e/waterschappen-bbv-variant.spec.ts b/tests/e2e/waterschappen-bbv-variant.spec.ts
index f7cbd33fb..8d451b940 100644
--- a/tests/e2e/waterschappen-bbv-variant.spec.ts
+++ b/tests/e2e/waterschappen-bbv-variant.spec.ts
@@ -41,7 +41,9 @@
* @spec openspec/changes/bookkeeping-waterschappen-bbv-variant-11-testing/tasks.md
*/
-import { test, expect, type Page } from '@playwright/test'
+import type { Page } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
const DASHBOARD_ROUTE = '/bbv-dashboard'
@@ -445,7 +447,6 @@ test.describe('BBV mapping detail β edit flow', () => {
{ headers: { 'OCS-APIRequest': 'true' } },
)
if (deleted.ok() === false) {
- // eslint-disable-next-line no-console
console.warn(
`[bbv] failed to clean up seeded mapping ${id}: HTTP ${deleted.status()}`,
)
@@ -533,11 +534,26 @@ test.describe('BBV scoping + validation', () => {
// 2. The scope is re-queryable: arm the response wait BEFORE the click,
// then assert the dashboard endpoint was actually hit again. A
// Refresh button that no longer re-queries fails here.
+ //
+ // Refresh lives ONLY in the page-level Actions overflow menu. The
+ // dashboard used to repeat it as a header button next to that menu,
+ // shipping two Refreshes; the header one is gone and `@refresh` on
+ // CnDashboardPage now routes the menu item to loadProgrammes. The
+ // response assertion below is what proves that rewire is live.
const requery = page.waitForResponse(
(r) => /\/apps\/shillinq\/api\/bbv-dashboard/.test(r.url()),
{ timeout: 20_000 },
)
- await page.getByTestId('bbv-dashboard-refresh').click()
+ await page
+ .getByRole('button', { name: /^Actions$/i })
+ .first()
+ .click()
+ // NcActionButton renders the item as role=menuitem in the popover,
+ // not role=button.
+ await page
+ .getByRole('menuitem', { name: /^Refresh$/i })
+ .first()
+ .click()
const response = await requery
expect(response.status()).toBeLessThan(400)
diff --git a/tests/e2e/workflows/_fixtures.ts b/tests/e2e/workflows/_fixtures.ts
index 343dcee33..f092f35dc 100644
--- a/tests/e2e/workflows/_fixtures.ts
+++ b/tests/e2e/workflows/_fixtures.ts
@@ -35,7 +35,10 @@
* specs run for real the moment the register imports.
*/
-import { APIRequestContext, expect } from '@playwright/test'
+import type { APIResponse } from '@playwright/test'
+import type { APIRequestContext } from '@playwright/test'
+
+import { expect } from '@playwright/test'
/** OpenRegister generic object API base. */
const OR = '/index.php/apps/openregister/api'
@@ -210,10 +213,7 @@ export class OrFixtures {
* @param id The object id (uuid).
* @param action The lifecycle action name (e.g. 'verleen', 'approve').
*/
- async transition(
- id: string,
- action: string,
- ): Promise {
+ async transition(id: string, action: string): Promise {
return this.api.post(`${OR}/objects/${id}/transition`, {
headers: await this.headers(),
data: { action },
diff --git a/tests/e2e/workflows/external-adapters-admin.spec.ts b/tests/e2e/workflows/external-adapters-admin.spec.ts
index 2e736fc92..4b3e28648 100644
--- a/tests/e2e/workflows/external-adapters-admin.spec.ts
+++ b/tests/e2e/workflows/external-adapters-admin.spec.ts
@@ -25,7 +25,9 @@
* @spec openspec/changes/integration-config-to-openconnector/specs/integration-config-to-openconnector/spec.md
*/
-import { test, expect, type Page, type ConsoleMessage } from '@playwright/test'
+import type { ConsoleMessage, Page } from '@playwright/test'
+
+import { expect, test } from '@playwright/test'
const APP = '/apps/shillinq'
const STATUS_ROUTE = `${APP}/external-adapters`
diff --git a/tests/e2e/workflows/fin-account-crud.spec.ts b/tests/e2e/workflows/fin-account-crud.spec.ts
index eede57660..6033652d4 100644
--- a/tests/e2e/workflows/fin-account-crud.spec.ts
+++ b/tests/e2e/workflows/fin-account-crud.spec.ts
@@ -21,8 +21,10 @@
* @spec openspec/changes/bookkeeping-trial-balance/tasks.md
*/
-import { test, expect, request as pwRequest } from '@playwright/test'
-import { UNIQUE_PREFIX, OrFixtures, REGISTER_SLUG } from './_fixtures'
+import type { APIRequestContext } from '@playwright/test'
+
+import { expect, request as pwRequest, test } from '@playwright/test'
+import { OrFixtures, REGISTER_SLUG, UNIQUE_PREFIX } from './_fixtures.ts'
const APP = '/apps/shillinq'
const ADMIN_ID = `${UNIQUE_PREFIX}-adm`
@@ -30,7 +32,7 @@ const NEEDED = ['Account']
test.describe('shillinq finance β ledger Account full CRUD with persistence', () => {
let fx: OrFixtures
- let api: import('@playwright/test').APIRequestContext
+ let api: APIRequestContext
test.beforeAll(async ({ baseURL }) => {
api = await pwRequest.newContext({
diff --git a/tests/e2e/workflows/fin-lease-amortization.spec.ts b/tests/e2e/workflows/fin-lease-amortization.spec.ts
index 650591e30..9a4605b5a 100644
--- a/tests/e2e/workflows/fin-lease-amortization.spec.ts
+++ b/tests/e2e/workflows/fin-lease-amortization.spec.ts
@@ -30,8 +30,10 @@
* @spec openspec/changes/bookkeeping-ifrs-16-lease/specs/bookkeeping-lease-accounting/spec.md
*/
-import { test, expect, request as pwRequest } from '@playwright/test'
-import { OrFixtures, REGISTER_SLUG, UNIQUE_PREFIX, money } from './_fixtures'
+import type { APIRequestContext } from '@playwright/test'
+
+import { expect, request as pwRequest, test } from '@playwright/test'
+import { money, OrFixtures, UNIQUE_PREFIX } from './_fixtures.ts'
const APP = '/apps/shillinq'
const ADMIN_ID = `${UNIQUE_PREFIX}-adm`
@@ -51,7 +53,7 @@ interface ScheduleRow {
test.describe('shillinq finance β IFRS 16 lease amortization (computed numbers)', () => {
let fx: OrFixtures
- let api: import('@playwright/test').APIRequestContext
+ let api: APIRequestContext
test.beforeAll(async ({ baseURL }) => {
api = await pwRequest.newContext({
diff --git a/tests/e2e/workflows/fin-oss-vat-rate.spec.ts b/tests/e2e/workflows/fin-oss-vat-rate.spec.ts
index 171c7d7e8..cc337ba08 100644
--- a/tests/e2e/workflows/fin-oss-vat-rate.spec.ts
+++ b/tests/e2e/workflows/fin-oss-vat-rate.spec.ts
@@ -32,8 +32,10 @@
* @spec openspec/specs/bookkeeping-btw-oss-eu/spec.md#REQ-OSS-001
*/
-import { test, expect, request as pwRequest } from '@playwright/test'
-import { UNIQUE_PREFIX, OrFixtures, money } from './_fixtures'
+import type { APIRequestContext } from '@playwright/test'
+
+import { expect, request as pwRequest, test } from '@playwright/test'
+import { money, OrFixtures, UNIQUE_PREFIX } from './_fixtures.ts'
const APP = '/apps/shillinq'
const ADMIN_ID = `${UNIQUE_PREFIX}-adm`
@@ -41,7 +43,7 @@ const NEEDED = ['EuVatRate']
test.describe('shillinq finance β OSS/BTW VAT rate resolution (computed numbers)', () => {
let fx: OrFixtures
- let api: import('@playwright/test').APIRequestContext
+ let api: APIRequestContext
test.beforeAll(async ({ baseURL }) => {
api = await pwRequest.newContext({
diff --git a/tests/e2e/workflows/fin-trial-balance.spec.ts b/tests/e2e/workflows/fin-trial-balance.spec.ts
index ca24f744a..bce9d9d75 100644
--- a/tests/e2e/workflows/fin-trial-balance.spec.ts
+++ b/tests/e2e/workflows/fin-trial-balance.spec.ts
@@ -39,8 +39,10 @@
* @e2e openspec/specs/bookkeeping-trial-balance/spec.md#balanced-trial-balance-returns-no-invariant-error
*/
-import { test, expect, request as pwRequest } from '@playwright/test'
-import { UNIQUE_PREFIX, OrFixtures, money } from './_fixtures'
+import type { APIRequestContext } from '@playwright/test'
+
+import { expect, request as pwRequest, test } from '@playwright/test'
+import { money, OrFixtures, UNIQUE_PREFIX } from './_fixtures.ts'
const APP = '/apps/shillinq'
const ADMIN_ID = `${UNIQUE_PREFIX}-adm`
@@ -55,7 +57,7 @@ interface TbRow {
test.describe('shillinq finance β trial balance balances (debits == credits)', () => {
let fx: OrFixtures
- let api: import('@playwright/test').APIRequestContext
+ let api: APIRequestContext
test.beforeAll(async ({ baseURL }) => {
api = await pwRequest.newContext({
diff --git a/tests/l10n/check-l10n-parity.js b/tests/l10n/check-l10n-parity.js
index 70e99a9a9..460ccf467 100644
--- a/tests/l10n/check-l10n-parity.js
+++ b/tests/l10n/check-l10n-parity.js
@@ -89,7 +89,7 @@ function loadJsonSet (file) {
/** True when a translation value is empty (string) or has an empty plural. */
function isEmpty (v) {
- if (v == null) {
+ if ((v === null || v === undefined)) {
return true
}
if (Array.isArray(v)) {
@@ -135,8 +135,8 @@ for (const set of sets) {
failures.push({ set: set.kind, loc, kind: 'UNPARSEABLE', detail: e.message })
continue
}
- const missing = enKeys.filter((k) => !Object.prototype.hasOwnProperty.call(locObj, k))
- const empty = enKeys.filter((k) => Object.prototype.hasOwnProperty.call(locObj, k) && isEmpty(locObj[k]))
+ const missing = enKeys.filter((k) => !Object.hasOwn(locObj, k))
+ const empty = enKeys.filter((k) => Object.hasOwn(locObj, k) && isEmpty(locObj[k]))
if (missing.length || empty.length) {
failures.push({ set: set.kind, loc, kind: 'INCOMPLETE', missing, empty, total: enKeys.length })
}
diff --git a/tests/l10n/check-l10n.js b/tests/l10n/check-l10n.js
index 70c2c26c3..922da5d31 100644
--- a/tests/l10n/check-l10n.js
+++ b/tests/l10n/check-l10n.js
@@ -128,7 +128,7 @@ function unescape (s) {
const used = new Map()
function record (key, file, idx, content) {
- if (key == null) {
+ if ((key === null || key === undefined)) {
return
}
const k = unescape(key)
@@ -154,7 +154,7 @@ for (const file of files) {
const missing = []
for (const [key, locations] of used) {
- if (!Object.prototype.hasOwnProperty.call(translations, key)) {
+ if (!Object.hasOwn(translations, key)) {
missing.push({ key, locations: [...locations] })
}
}
diff --git a/tests/nav-reachability-baseline.json b/tests/nav-reachability-baseline.json
index 2b1970227..de998f4ef 100644
--- a/tests/nav-reachability-baseline.json
+++ b/tests/nav-reachability-baseline.json
@@ -26,6 +26,16 @@
"CostCenterDetail": "nav-six-clusters (design.md Β§4 row 3, ADR-097 Decision 5 MERGE): the duplicate CostCenters INDEX page (schema AnalyticalDimension, filter dimensionType=cost-center) was deleted and its menu leaf converted to a menu[].query preset on the canonical AnalyticalDimensions page β but AnalyticalDimensions' own detailRoute is AnalyticalDimensionDetail, a different id, so CostCenterDetail (which had richer fields: budget/spentToDate/enterpriseActivity/responsibleUser not on the generic detail) loses its index-driven reachability. Its page/route are NOT deleted (no route renames, ADR-044) β only reachable by direct URL now. A genuine consolidation cost, not a bug.",
"KostenDragerDetail": "nav-six-clusters (design.md Β§4 row 3, ADR-097 Decision 5 MERGE): same cause as CostCenterDetail β the duplicate KostenDragers index (dimensionType=cost-object) was deleted and converted to a preset on the canonical AnalyticalDimensions page, which points row-clicks at AnalyticalDimensionDetail instead. KostenDragerDetail's page/route are untouched, reachable only by direct URL.",
"ConsolidationsDetail": "nav-six-clusters (design.md Β§4 row 11, ADR-097 Decision 5 MERGE): the duplicate Consolidations index (schema ConsolidationGroup) was deleted in favour of the canonical ConsolidationGroups page, which points row-clicks at ConsolidationGroupDetail (a different, non-superset detail page β Consolidations' own detail listed member GroupEntity rows that ConsolidationGroupDetail's ConsolidationPeriod-focused relatedList does not cover). ConsolidationsDetail's page/route are untouched, reachable only by direct URL β not deleted per ADR-044.",
- "FlowDetail": "Reachable by row click from the Flows index (page id Flows), which is the intended path for a detail page. The link lives in the shared CnFlowsPage component (this.$router.push(`${detailRoute}/${id}`)) rather than a manifest config field, so this static gate cannot see the edge β 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 /flows/:id. Remove this entry once CnFlowsPage accepts a route NAME in config.detailRoute so the edge becomes declarative (tracked: nextcloud-vue)."
+ "FlowDetail": "Reachable by row click from the Flows index (page id Flows), which is the intended path for a detail page. The link lives in the shared CnFlowsPage component (this.$router.push(`${detailRoute}/${id}`)) rather than a manifest config field, so this static gate cannot see the edge β 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 /flows/:id. Remove this entry once CnFlowsPage accepts a route NAME in config.detailRoute so the edge becomes declarative (tracked: nextcloud-vue).",
+ "BookkeepingDestructionReport": "ADR-112: reachable as a CARD on ReportingComplianceOverview, which is itself a live menu entry. The card is declared in src/components/reporting/reportViews.js and navigates by route NAME, so this page is reached in one click from the navigation β the checker reads the manifest menu and cannot see a card declared in a JS module. Its menu entry was retired deliberately (menu-layout.json removals): the report was reachable twice, and the menu copy is the one that never shrinks.",
+ "ConsolidatedReport": "ADR-112: reachable as a CARD on ReportingComplianceOverview, which is itself a live menu entry. The card is declared in src/components/reporting/reportViews.js and navigates by route NAME, so this page is reached in one click from the navigation β the checker reads the manifest menu and cannot see a card declared in a JS module. Its menu entry was retired deliberately (menu-layout.json removals): the report was reachable twice, and the menu copy is the one that never shrinks.",
+ "EmuRapportage": "ADR-112: reachable as a CARD on ReportingComplianceOverview, which is itself a live menu entry. The card is declared in src/components/reporting/reportViews.js and navigates by route NAME, so this page is reached in one click from the navigation β the checker reads the manifest menu and cannot see a card declared in a JS module. Its menu entry was retired deliberately (menu-layout.json removals): the report was reachable twice, and the menu copy is the one that never shrinks.",
+ "Iv3Rapportages": "ADR-112: reachable as a CARD on ReportingComplianceOverview, which is itself a live menu entry. The card is declared in src/components/reporting/reportViews.js and navigates by route NAME, so this page is reached in one click from the navigation β the checker reads the manifest menu and cannot see a card declared in a JS module. Its menu entry was retired deliberately (menu-layout.json removals): the report was reachable twice, and the menu copy is the one that never shrinks.",
+ "SisaRapportages": "ADR-112: reachable as a CARD on ReportingComplianceOverview, which is itself a live menu entry. The card is declared in src/components/reporting/reportViews.js and navigates by route NAME, so this page is reached in one click from the navigation β the checker reads the manifest menu and cannot see a card declared in a JS module. Its menu entry was retired deliberately (menu-layout.json removals): the report was reachable twice, and the menu copy is the one that never shrinks.",
+ "VarianceReport": "ADR-112: reachable as a CARD on ReportingComplianceOverview, which is itself a live menu entry. The card is declared in src/components/reporting/reportViews.js and navigates by route NAME, so this page is reached in one click from the navigation β the checker reads the manifest menu and cannot see a card declared in a JS module. Its menu entry was retired deliberately (menu-layout.json removals): the report was reachable twice, and the menu copy is the one that never shrinks.",
+ "ConsolidatedReportDetail": "ADR-112: reached from its own index page, which is reachable as a CARD on ReportingComplianceOverview. The chain card -> index -> detail is intact; only the index's duplicate MENU entry was retired. Orphaned here solely because its index is.",
+ "EmuRapportageDetail": "ADR-112: reached from its own index page, which is reachable as a CARD on ReportingComplianceOverview. The chain card -> index -> detail is intact; only the index's duplicate MENU entry was retired. Orphaned here solely because its index is.",
+ "Iv3RapportagesDetail": "ADR-112: reached from its own index page, which is reachable as a CARD on ReportingComplianceOverview. The chain card -> index -> detail is intact; only the index's duplicate MENU entry was retired. Orphaned here solely because its index is.",
+ "SisaRapportagesDetail": "ADR-112: reached from its own index page, which is reachable as a CARD on ReportingComplianceOverview. The chain card -> index -> detail is intact; only the index's duplicate MENU entry was retired. Orphaned here solely because its index is."
}
}
diff --git a/tests/unit/customer-bridge-profile-helpers.test.mjs b/tests/unit/customer-bridge-profile-helpers.test.mjs
index 0de542887..2eb139009 100644
--- a/tests/unit/customer-bridge-profile-helpers.test.mjs
+++ b/tests/unit/customer-bridge-profile-helpers.test.mjs
@@ -20,18 +20,17 @@
* @spec openspec/changes/bookings-pipelinq-customer-bridge-06-profile-card-ui/tasks.md
*/
-import { test } from 'node:test'
import assert from 'node:assert/strict'
-
+import { test } from 'node:test'
import {
- classifyContact,
+ buildPipelinqLink,
buildProfileFields,
- selectProfileState,
- selectHistoryState,
+ classifyContact,
formatTransactionAmount,
formatTransactionDate,
nextPageParams,
- buildPipelinqLink,
+ selectHistoryState,
+ selectProfileState,
} from '../../src/composables/usePipelinqProfile.js'
// ---------------------------------------------------------------------------
@@ -97,7 +96,10 @@ test('buildProfileFields omits missing optional fields entirely (no empty labels
found: true,
}
const fields = buildProfileFields(contact)
- assert.deepEqual(fields.map((f) => f.key), ['legalName'])
+ assert.deepEqual(
+ fields.map((f) => f.key),
+ ['legalName'],
+ )
})
test('buildProfileFields returns empty list for not-found contact', () => {
@@ -173,13 +175,15 @@ test('selectProfileState returns error for a missing payload', () => {
// selectHistoryState β history rendering with up to 5 entries + load-more
// ---------------------------------------------------------------------------
-const okPayload = (klantbeeld) => ({
- booking: { appointmentId: 'apt-1', pipelinqContactId: 'cnt-1' },
- contact: { externalId: 'cnt-1', legalName: 'Acme', found: true },
- contactError: null,
- notLinkedToPipelinq: false,
- klantbeeld,
-})
+function okPayload(klantbeeld) {
+ return {
+ booking: { appointmentId: 'apt-1', pipelinqContactId: 'cnt-1' },
+ contact: { externalId: 'cnt-1', legalName: 'Acme', found: true },
+ contactError: null,
+ notLinkedToPipelinq: false,
+ klantbeeld,
+ }
+}
test('selectHistoryState returns ok with up to 5 transactions', () => {
const klantbeeld = {
@@ -199,7 +203,13 @@ test('selectHistoryState returns ok with up to 5 transactions', () => {
})
test('selectHistoryState returns empty when envelope reports empty', () => {
- const klantbeeld = { transactions: [], limit: 5, offset: 0, unavailable: false, empty: true }
+ const klantbeeld = {
+ transactions: [],
+ limit: 5,
+ offset: 0,
+ unavailable: false,
+ empty: true,
+ }
assert.equal(selectHistoryState(okPayload(klantbeeld)), 'empty')
})
@@ -233,14 +243,14 @@ test('selectHistoryState returns hidden when klantbeeld envelope is missing', ()
// ---------------------------------------------------------------------------
test('nextPageParams advances offset by limit', () => {
- assert.deepEqual(
- nextPageParams({ limit: 5, offset: 0 }),
- { limit: 5, offset: 5 },
- )
- assert.deepEqual(
- nextPageParams({ limit: 5, offset: 5 }),
- { limit: 5, offset: 10 },
- )
+ assert.deepEqual(nextPageParams({ limit: 5, offset: 0 }), {
+ limit: 5,
+ offset: 5,
+ })
+ assert.deepEqual(nextPageParams({ limit: 5, offset: 5 }), {
+ limit: 5,
+ offset: 10,
+ })
})
test('nextPageParams falls back to defaults when envelope is missing fields', () => {
@@ -250,10 +260,10 @@ test('nextPageParams falls back to defaults when envelope is missing fields', ()
})
test('nextPageParams clamps limit to >= 1', () => {
- assert.deepEqual(
- nextPageParams({ limit: 0, offset: 0 }),
- { limit: 1, offset: 1 },
- )
+ assert.deepEqual(nextPageParams({ limit: 0, offset: 0 }), {
+ limit: 1,
+ offset: 1,
+ })
})
// ---------------------------------------------------------------------------
@@ -261,8 +271,14 @@ test('nextPageParams clamps limit to >= 1', () => {
// ---------------------------------------------------------------------------
test('formatTransactionAmount renders the row currency with 2 decimals', () => {
- assert.equal(formatTransactionAmount({ amount: 100, currency: 'EUR' }), 'EUR 100.00')
- assert.equal(formatTransactionAmount({ amount: 12.5, currency: 'USD' }), 'USD 12.50')
+ assert.equal(
+ formatTransactionAmount({ amount: 100, currency: 'EUR' }),
+ 'EUR 100.00',
+ )
+ assert.equal(
+ formatTransactionAmount({ amount: 12.5, currency: 'USD' }),
+ 'USD 12.50',
+ )
})
test('formatTransactionAmount falls back to EUR when currency is missing', () => {
@@ -270,7 +286,10 @@ test('formatTransactionAmount falls back to EUR when currency is missing', () =>
})
test('formatTransactionAmount renders 0.00 when the amount is non-numeric', () => {
- assert.equal(formatTransactionAmount({ amount: 'bogus', currency: 'EUR' }), 'EUR 0.00')
+ assert.equal(
+ formatTransactionAmount({ amount: 'bogus', currency: 'EUR' }),
+ 'EUR 0.00',
+ )
assert.equal(formatTransactionAmount({}), 'EUR 0.00')
})
diff --git a/tests/unit/inventory-mobile-scanner-helpers.test.mjs b/tests/unit/inventory-mobile-scanner-helpers.test.mjs
index 30ca15470..48b7297f3 100644
--- a/tests/unit/inventory-mobile-scanner-helpers.test.mjs
+++ b/tests/unit/inventory-mobile-scanner-helpers.test.mjs
@@ -15,9 +15,8 @@
* @spec openspec/changes/inventory-mobile-scanner/tasks.md
*/
-import { test } from 'node:test'
import assert from 'node:assert/strict'
-
+import { test } from 'node:test'
// Import the source modules directly. These are plain ESM so node --test
// can load them without a bundler. useInventorySync.js depends on
// @nextcloud/axios at the top level; we replicate its newTransactionId()
@@ -45,9 +44,9 @@ function newTransactionId() {
} else if (i === 14) {
out += '4'
} else if (i === 19) {
- out += hex[(Math.random() * 4 | 0) + 8]
+ out += hex[((Math.random() * 4) | 0) + 8]
} else {
- out += hex[Math.random() * 16 | 0]
+ out += hex[(Math.random() * 16) | 0]
}
}
return out
@@ -60,9 +59,18 @@ test('composeStockKey produces a stable sku|location composite', () => {
})
test('isStrictlyLater returns true only when a is strictly later than b', () => {
- assert.equal(isStrictlyLater('2026-05-21T14:23:00Z', '2026-05-21T14:22:59Z'), true)
- assert.equal(isStrictlyLater('2026-05-21T14:22:59Z', '2026-05-21T14:23:00Z'), false)
- assert.equal(isStrictlyLater('2026-05-21T14:23:00Z', '2026-05-21T14:23:00Z'), false)
+ assert.equal(
+ isStrictlyLater('2026-05-21T14:23:00Z', '2026-05-21T14:22:59Z'),
+ true,
+ )
+ assert.equal(
+ isStrictlyLater('2026-05-21T14:22:59Z', '2026-05-21T14:23:00Z'),
+ false,
+ )
+ assert.equal(
+ isStrictlyLater('2026-05-21T14:23:00Z', '2026-05-21T14:23:00Z'),
+ false,
+ )
})
test('isStrictlyLater is defensive against missing or unparseable timestamps', () => {
@@ -74,7 +82,10 @@ test('isStrictlyLater is defensive against missing or unparseable timestamps', (
test('newTransactionId returns a UUID-shaped string usable as a dedup key', () => {
const id = newTransactionId()
assert.equal(typeof id, 'string')
- assert.match(id, /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/)
+ assert.match(
+ id,
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
+ )
const id2 = newTransactionId()
assert.notEqual(id, id2, 'consecutive ids must differ')
})
diff --git a/tests/validate-fragment-required.js b/tests/validate-fragment-required.js
index 95b3afc3c..7a42c4408 100644
--- a/tests/validate-fragment-required.js
+++ b/tests/validate-fragment-required.js
@@ -64,7 +64,7 @@ const FRAGMENT_DIR = path.join(SETTINGS_DIR, 'register.d')
// only ever be LOWERED β no new conflicting declaration can land.
const BASELINE = 44
-const loadJson = (file) => {
+function loadJson(file) {
try {
return JSON.parse(fs.readFileSync(file, 'utf8'))
} catch (err) {
@@ -92,7 +92,7 @@ function registerFiles() {
return files
}
-const sameSet = (a, b) => {
+function sameSet(a, b) {
const sa = [...new Set(a)].sort()
const sb = [...new Set(b)].sort()
return sa.length === sb.length && sa.every((v, i) => v === sb[i])
diff --git a/tests/validate-semantic-markers.js b/tests/validate-semantic-markers.js
index 5c8789528..e88f116d7 100644
--- a/tests/validate-semantic-markers.js
+++ b/tests/validate-semantic-markers.js
@@ -69,7 +69,7 @@ function walk(node, file, offenders, contextName) {
}
if (!node || typeof node !== 'object') return
const name = node.slug || node.title || contextName
- if (Object.prototype.hasOwnProperty.call(node, 'x-schema-org')) {
+ if (Object.hasOwn(node, 'x-schema-org')) {
const marker = node['x-schema-org']
if (typeof marker !== 'string' || !CURIE_RE.test(marker)) {
offenders.push({ file, name, marker })
@@ -127,7 +127,7 @@ function countMarkers(node, box) {
return
}
if (!node || typeof node !== 'object') return
- if (Object.prototype.hasOwnProperty.call(node, 'x-schema-org')) box.n += 1
+ if (Object.hasOwn(node, 'x-schema-org')) box.n += 1
for (const value of Object.values(node)) countMarkers(value, box)
}
diff --git a/tests/vitest/accountantPortalRouting.spec.js b/tests/vitest/accountantPortalRouting.spec.js
index bb0741964..720e7b787 100644
--- a/tests/vitest/accountantPortalRouting.spec.js
+++ b/tests/vitest/accountantPortalRouting.spec.js
@@ -25,9 +25,9 @@
* @spec openspec/specs/accountant-portal/spec.md
*/
-import { describe, it, expect } from 'vitest'
import fs from 'fs'
import path from 'path'
+import { describe, expect, it } from 'vitest'
const repoRoot = path.resolve(__dirname, '..', '..')
const routesSource = fs.readFileSync(
diff --git a/tests/vitest/arEInvoiceActions.spec.js b/tests/vitest/arEInvoiceActions.spec.js
index e978b2669..2b2441d3a 100644
--- a/tests/vitest/arEInvoiceActions.spec.js
+++ b/tests/vitest/arEInvoiceActions.spec.js
@@ -11,13 +11,13 @@
* @spec openspec/changes/add-invoice-pdf-export-with-ubl-peppol-support/specs/bookkeeping-einvoicing-ubl-peppol/spec.md#req-einv-007
*/
-import { describe, it, expect } from 'vitest'
+import { describe, expect, it } from 'vitest'
import {
canSendEInvoice,
+ extractSendErrorMessage,
+ mapSendResult,
resolveDeliveryStatus,
sendEInvoiceEndpoint,
- mapSendResult,
- extractSendErrorMessage,
} from '../../src/components/ar-invoice/arEInvoiceActions.js'
// The translate stub returns the source string β keys are English (house rule).
diff --git a/tests/vitest/bankStatementWizard.spec.js b/tests/vitest/bankStatementWizard.spec.js
index 275ddac7a..7bd41cc1c 100644
--- a/tests/vitest/bankStatementWizard.spec.js
+++ b/tests/vitest/bankStatementWizard.spec.js
@@ -16,15 +16,15 @@
* runs the import" β is driven by loadIbanMapping() asserted here.
*/
-import { describe, it, expect, beforeEach } from 'vitest'
+import { beforeEach, describe, expect, it } from 'vitest'
import {
+ BREADCRUMB_FLAG,
+ buildImportPayload,
formatOptions,
- normalizeIban,
loadIbanMapping,
+ normalizeIban,
saveIbanMapping,
- buildImportPayload,
setReturnBreadcrumb,
- BREADCRUMB_FLAG,
} from '../../src/modals/bankStatementWizard.js'
const IBAN_MAP_KEY = 'shillinq:bank-iban-map'
diff --git a/tests/vitest/bbvLinkerFilterBar.spec.js b/tests/vitest/bbvLinkerFilterBar.spec.js
index e24be406a..4439f45d3 100644
--- a/tests/vitest/bbvLinkerFilterBar.spec.js
+++ b/tests/vitest/bbvLinkerFilterBar.spec.js
@@ -26,7 +26,7 @@
* @spec openspec/specs/bookkeeping-provincies-bbv-variant/spec.md
*/
-import { describe, it, expect, beforeEach, vi } from 'vitest'
+import { beforeEach, describe, expect, it, vi } from 'vitest'
import BbvLinkerFilterBar from '../../src/components/bbv-provincie/BbvLinkerFilterBar.vue'
/** The page's declared facets, verbatim from the manifest fragment. */
diff --git a/tests/vitest/billImportModal.spec.js b/tests/vitest/billImportModal.spec.js
index df941cf91..496328881 100644
--- a/tests/vitest/billImportModal.spec.js
+++ b/tests/vitest/billImportModal.spec.js
@@ -13,26 +13,26 @@
* invoiceQuickDraft.js pattern).
*/
-import { describe, it, expect } from 'vitest'
+import { describe, expect, it } from 'vitest'
import {
- detectFormat,
- isDeferredPdf,
buildImportFormData,
- reviewFormFromRecord,
canSaveReview,
- importErrorMessage,
- refreshEventPayload,
+ confidenceForField,
CREDITORS_WIDGET,
- PDF_DEFERRAL_MESSAGE,
+ detectFormat,
+ glAccountSuggestionSummary,
+ hasKnownExtractionId,
+ importErrorMessage,
+ isDeferredPdf,
isExtractionDraft,
- confidenceForField,
isFieldCorrected,
- requiresExplicitReview,
+ ONE_CLICK_CONFIDENCE_GATE,
+ PDF_DEFERRAL_MESSAGE,
pendingDraftSummary,
+ refreshEventPayload,
+ requiresExplicitReview,
REVIEW_THRESHOLD,
- ONE_CLICK_CONFIDENCE_GATE,
- hasKnownExtractionId,
- glAccountSuggestionSummary,
+ reviewFormFromRecord,
} from '../../src/modals/billImportModal.js'
describe('billImportModal β format detection', () => {
diff --git a/tests/vitest/bookingsCalendarView.spec.js b/tests/vitest/bookingsCalendarView.spec.js
index 5ed8356ff..83dafcc97 100644
--- a/tests/vitest/bookingsCalendarView.spec.js
+++ b/tests/vitest/bookingsCalendarView.spec.js
@@ -24,7 +24,7 @@
* @spec openspec/changes/bookings-resource-calendar/tasks.md#task-5
*/
-import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import CalendarView from '../../src/views/bookings/CalendarView.vue'
const { fetchBookings, bookingId, isConflict } = CalendarView.methods
diff --git a/tests/vitest/budgetLineCommitmentsHelpers.spec.js b/tests/vitest/budgetLineCommitmentsHelpers.spec.js
index 826de3abb..d5cff705e 100644
--- a/tests/vitest/budgetLineCommitmentsHelpers.spec.js
+++ b/tests/vitest/budgetLineCommitmentsHelpers.spec.js
@@ -10,11 +10,11 @@
* @spec openspec/changes/verplichtingen-commitment-accounting/specs/bookkeeping-verplichtingenadministratie/spec.md#req-vpl-011
*/
-import { describe, it, expect } from 'vitest'
+import { describe, expect, it } from 'vitest'
import {
- normaliseBudgetLineRows,
- formatAmount,
drilldownFilters,
+ formatAmount,
+ normaliseBudgetLineRows,
} from '../../src/views/budgetLineCommitmentsHelpers.js'
describe('budgetLineCommitmentsHelpers β normaliseBudgetLineRows', () => {
diff --git a/tests/vitest/deadlineCalendarSettings.spec.js b/tests/vitest/deadlineCalendarSettings.spec.js
index 34c38bebd..c21a3c4b9 100644
--- a/tests/vitest/deadlineCalendarSettings.spec.js
+++ b/tests/vitest/deadlineCalendarSettings.spec.js
@@ -10,9 +10,9 @@
import { describe, expect, it } from 'vitest'
import {
+ buildSavePayload,
CATEGORY_META,
normaliseSettings,
- buildSavePayload,
} from '../../src/views/deadlineCalendarSettingsHelpers.js'
describe('CATEGORY_META', () => {
diff --git a/tests/vitest/externalAdapters.spec.js b/tests/vitest/externalAdapters.spec.js
index 1ac6af5b2..b905bd05a 100644
--- a/tests/vitest/externalAdapters.spec.js
+++ b/tests/vitest/externalAdapters.spec.js
@@ -27,8 +27,8 @@
* the global `t()` translator and `axios.get` are mocked per-test.
*/
-import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import axios from '@nextcloud/axios'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import StatusView from '../../src/views/external-adapters/ExternalAdaptersStatus.vue'
/** Identity translator: returns the source string (or fills {placeholders}). */
diff --git a/tests/vitest/financialSeries.spec.js b/tests/vitest/financialSeries.spec.js
index 2e2fed456..1db162df6 100644
--- a/tests/vitest/financialSeries.spec.js
+++ b/tests/vitest/financialSeries.spec.js
@@ -9,25 +9,25 @@
* mapping and the one-request-per-schema guarantee.
*/
-import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import axios from '@nextcloud/axios'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import {
- monthKey,
- lastMonths,
- classifyAccounts,
- postedLinesByMonth,
- signedAmount,
- monthlyFinancialSeries,
billableSeries,
- forecastByMonth,
- openArRows,
- openApRows,
+ classifyAccounts,
computeKpis,
computeRangeKpis,
+ forecastByMonth,
+ lastMonths,
+ monthKey,
+ monthlyFinancialSeries,
+ openApRows,
+ openArRows,
+ postedLinesByMonth,
+ signedAmount,
} from '../../src/components/dashboard/financial/financialSeries.js'
import {
- useFinancialData,
resetFinancialData,
+ useFinancialData,
} from '../../src/components/dashboard/financial/useFinancialData.js'
const ACCOUNTS = [
diff --git a/tests/vitest/generateManifestShell.spec.js b/tests/vitest/generateManifestShell.spec.js
index a7083f9f9..56b3f8de9 100644
--- a/tests/vitest/generateManifestShell.spec.js
+++ b/tests/vitest/generateManifestShell.spec.js
@@ -10,11 +10,10 @@
* @spec openspec/changes/shillinq-manifest-boot-payload-reduction/specs/manifest-boot-performance/spec.md#req-mbp-001
*/
-import { describe, it, expect } from 'vitest'
import fs from 'fs'
import os from 'os'
import path from 'path'
-// eslint-disable-next-line n/no-unpublished-require
+import { describe, expect, it } from 'vitest'
const {
generateShellDocument,
buildShellFragment,
diff --git a/tests/vitest/invoiceQuickDraft.spec.js b/tests/vitest/invoiceQuickDraft.spec.js
index c076ebc67..bbe3a9024 100644
--- a/tests/vitest/invoiceQuickDraft.spec.js
+++ b/tests/vitest/invoiceQuickDraft.spec.js
@@ -8,16 +8,16 @@
* localStorage preference round-trip with TTL expiry.
*/
-import { describe, it, expect, beforeEach, vi } from 'vitest'
+import { beforeEach, describe, expect, it } from 'vitest'
import {
- defaultDraftLine,
+ buildInvoicePayload,
computeTotals,
- paymentTermDays,
+ defaultDraftLine,
dueDateFromTerms,
- buildInvoicePayload,
+ loadQuickDraftPrefs,
+ paymentTermDays,
periodIdFromDate,
provisionalInvoiceNumber,
- loadQuickDraftPrefs,
saveQuickDraftPrefs,
} from '../../src/modals/invoiceQuickDraft.js'
diff --git a/tests/vitest/listViewsCnDataTable.spec.js b/tests/vitest/listViewsCnDataTable.spec.js
index 915cb8579..ebcf90122 100644
--- a/tests/vitest/listViewsCnDataTable.spec.js
+++ b/tests/vitest/listViewsCnDataTable.spec.js
@@ -22,12 +22,12 @@
* @spec openspec/changes/migrate-list-views-to-cndatatable/specs/list-views-cndatatable/spec.md
*/
-import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
-import AdminInvoiceList from '../../src/views/invoice/AdminInvoiceList.vue'
-import DocumentsView from '../../src/views/bookkeeping/DocumentsView.vue'
-import TransactionsView from '../../src/views/bookkeeping/TransactionsView.vue'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import ThreeWayMatchIndex from '../../src/components/three-way-match/ThreeWayMatchIndex.vue'
import VendorPerformanceIndex from '../../src/components/vendor-performance/VendorPerformanceIndex.vue'
+import DocumentsView from '../../src/views/bookkeeping/DocumentsView.vue'
+import TransactionsView from '../../src/views/bookkeeping/TransactionsView.vue'
+import AdminInvoiceList from '../../src/views/invoice/AdminInvoiceList.vue'
/** Identity translator: returns the source string (or fills {placeholders}). */
function tIdentity(app, text, vars) {
diff --git a/tests/vitest/mergeFragmentIntoManifest.spec.js b/tests/vitest/mergeFragmentIntoManifest.spec.js
index f62f667d6..6fb6cfb25 100644
--- a/tests/vitest/mergeFragmentIntoManifest.spec.js
+++ b/tests/vitest/mergeFragmentIntoManifest.spec.js
@@ -11,11 +11,11 @@
* @spec openspec/changes/shillinq-manifest-boot-payload-reduction/specs/manifest-boot-performance/spec.md#req-mbp-001
*/
-import { describe, it, expect } from 'vitest'
+import { describe, expect, it } from 'vitest'
import { computed, reactive, toRaw } from 'vue'
import {
- mergeFullFragmentIntoManifest,
buildPageFragmentIndex,
+ mergeFullFragmentIntoManifest,
} from '../../src/utils/mergeFragmentIntoManifest.js'
describe('mergeFullFragmentIntoManifest β reactivity (the load-bearing contract)', () => {
diff --git a/tests/vitest/receiptCapture.spec.js b/tests/vitest/receiptCapture.spec.js
index d66f6969e..ab5757f32 100644
--- a/tests/vitest/receiptCapture.spec.js
+++ b/tests/vitest/receiptCapture.spec.js
@@ -7,12 +7,12 @@
* save gate, and the correction-commit payload shape (REQ-RXC-003 / REQ-RXC-004).
*/
-import { describe, it, expect } from 'vitest'
+import { describe, expect, it } from 'vitest'
import {
- reviewFormFromReceipt,
- canSaveReceipt,
buildReceiptConfirmPayload,
+ canSaveReceipt,
receiptErrorMessage,
+ reviewFormFromReceipt,
} from '../../src/views/receiptCapture.js'
describe('receiptCapture β review form (REQ-RXC-003)', () => {
diff --git a/tests/vitest/recurringInvoiceProfile.spec.js b/tests/vitest/recurringInvoiceProfile.spec.js
index 454e925eb..c3552cc53 100644
--- a/tests/vitest/recurringInvoiceProfile.spec.js
+++ b/tests/vitest/recurringInvoiceProfile.spec.js
@@ -7,12 +7,12 @@
* and the profile payload shape (always status draft on create).
*/
-import { describe, it, expect } from 'vitest'
+import { describe, expect, it } from 'vitest'
import {
+ buildProfilePayload,
defaultRecurringLine,
perPeriodNet,
validateProfile,
- buildProfilePayload,
} from '../../src/modals/recurringInvoiceProfile.js'
describe('recurringInvoiceProfile β defaults + totals', () => {
diff --git a/tests/vitest/settingsStore.spec.js b/tests/vitest/settingsStore.spec.js
index 4180f4d82..70f1b0a9c 100644
--- a/tests/vitest/settingsStore.spec.js
+++ b/tests/vitest/settingsStore.spec.js
@@ -9,8 +9,8 @@
* is aliased to a stub.
*/
-import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { createPinia, setActivePinia } from 'pinia'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { useSettingsStore } from '../../src/store/modules/settings.js'
function mockFetchOnce({ ok = true, json = {} }) {
diff --git a/tests/vitest/spendAnalyticsPanel.spec.js b/tests/vitest/spendAnalyticsPanel.spec.js
index 1ac909ac4..6face1ea2 100644
--- a/tests/vitest/spendAnalyticsPanel.spec.js
+++ b/tests/vitest/spendAnalyticsPanel.spec.js
@@ -29,8 +29,8 @@
* @spec openspec/changes/spend-analytics-ui/specs/spend-analytics/spec.md
*/
-import { beforeEach, describe, expect, it, vi } from 'vitest'
import axios from '@nextcloud/axios'
+import { beforeEach, describe, expect, it, vi } from 'vitest'
import SpendAnalyticsPanel, {
SPEND_DIMENSIONS,
} from '../../src/components/spend-analytics/SpendAnalyticsPanel.vue'