Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
062bfa8
chore(sync): carry beta back into development
github-actions[bot] Aug 30, 2026
3633139
Merge pull request #602 from ConductionNL/sync/beta-to-development-20…
rubenvdlinde Aug 30, 2026
e113e27
chore(release): 0.2.4-unstable.20260830175353 (#605)
github-actions[bot] Aug 30, 2026
0f1920f
chore(release): sync main back into development
github-actions[bot] Aug 30, 2026
79972b9
Merge pull request #607 from ConductionNL/sync/main-to-development-0.2.4
rubenvdlinde Aug 30, 2026
46fb1a3
Merge pull request #603 from ConductionNL/development
rubenvdlinde Aug 30, 2026
56b8c54
chore(release): sync beta back into development
github-actions[bot] Aug 30, 2026
3b44308
chore(sync): carry main back into beta
github-actions[bot] Aug 30, 2026
0ff77d9
Merge pull request #611 from ConductionNL/sync/main-to-beta-202608301…
rubenvdlinde Aug 30, 2026
a616f4b
chore(sync): carry beta back into development
github-actions[bot] Aug 30, 2026
522d89e
Merge pull request #612 from ConductionNL/sync/beta-to-development-20…
rubenvdlinde Aug 30, 2026
7dcbe85
chore(deps): take @conduction/nextcloud-vue 2.25.1 for the KPI card (…
rubenvdlinde Aug 31, 2026
323b6a2
fix(store): import useObjectStore, which was called but never importe…
rubenvdlinde Aug 31, 2026
95d1000
fix(l10n): cover the schema strings that broke the check (#622)
rubenvdlinde Aug 31, 2026
6b687ed
chore(register): drop the stale larping_register.json (#624)
rubenvdlinde Aug 31, 2026
8774f39
fix(register): drop the pre-rename register file the gate rejects (#625)
rubenvdlinde Aug 31, 2026
e204ed9
Merge pull request #610 from ConductionNL/sync/beta-to-development-0.…
rubenvdlinde Aug 31, 2026
482cfe2
chore(release): 0.2.6-unstable.20260831051044 (#627)
github-actions[bot] Aug 31, 2026
d772447
chore(deps): refresh the shared Conduction locks (#628)
rubenvdlinde Aug 31, 2026
75d798f
chore(deps): take @conduction/nextcloud-vue 2.27.0 (#631)
rubenvdlinde Aug 31, 2026
4e46264
Merge pull request #614 from ConductionNL/development
rubenvdlinde Aug 31, 2026
c21a58d
chore(release): sync beta back into development
github-actions[bot] Aug 31, 2026
72d8fac
fix(kpi): drop hardcoded KPI colours so the cards follow the theme (#…
rubenvdlinde Aug 31, 2026
3afd995
fix(composer): declare ext-xsl so dependabot can resolve (#633)
rubenvdlinde Aug 31, 2026
489b564
Merge pull request #635 from ConductionNL/sync/beta-to-development-0.…
rubenvdlinde Aug 31, 2026
7790097
chore(deps): take @conduction/nextcloud-vue 2.27.2 (#640)
rubenvdlinde Aug 31, 2026
f28d403
chore(release): 0.2.7-unstable.20260831114923 (#642)
github-actions[bot] Aug 31, 2026
65e8d2a
fix(deps): make the npm overrides follow their direct dependency (#643)
rubenvdlinde Aug 31, 2026
e416b7e
chore(release): 0.2.8-unstable.20260831165721 (#644)
github-actions[bot] Aug 31, 2026
a6d50f5
chore(register): rename the skill slug to larping_skill (#645)
rubenvdlinde Sep 1, 2026
8652f52
fix(dashboard): drop the second Refresh from the dashboard header (#646)
rubenvdlinde Sep 1, 2026
e400d49
fix(e2e): expect the slug the register actually ships, larping_skill …
rubenvdlinde Sep 1, 2026
ec29913
feat(router): give larpinq the SPA catch-all, and move it to clean pa…
rubenvdlinde Sep 1, 2026
602fa3a
fix(routes): guard the AppHost builder with class_exists (#653)
rubenvdlinde Sep 1, 2026
fe200fc
chore(lint): let the linter see tests/ and scripts/ (#655)
rubenvdlinde Sep 1, 2026
09ae8ee
fix(settings): add the load() the canonical route already points at, …
rubenvdlinde Sep 1, 2026
77cf9e5
test(e2e): navigate the router, not the URL fragment (#660)
rubenvdlinde Sep 1, 2026
7c80a99
fix(e2e): assert path URLs, not hash, now the router runs in history …
rubenvdlinde Sep 1, 2026
ab20a2f
fix(routes): stop routing health and metrics larpinq cannot serve, an…
rubenvdlinde Sep 1, 2026
d278b95
test: collect the unit tests that were sitting in an uncollected dire…
rubenvdlinde Sep 1, 2026
bf3c73e
fix(observability): declare the health checks the endpoint reports (#…
rubenvdlinde Sep 1, 2026
de14fd6
Takes nextcloud-vue 2.31.1 so a hovered KPI tile stops drawing a card…
rubenvdlinde Sep 2, 2026
3a6b566
fix(ci): a standing release PR ran every development push twice
rubenvdlinde Sep 3, 2026
85afbbf
feat(chrome): give larpinq a Reports page with three declarative reports
Sep 3, 2026
94ed8fd
style(chrome): run prettier over the larpinq chrome spec
Sep 3, 2026
61b1f73
Merge pull request #677 from ConductionNL/feat/app-chrome-reports
rubenvdlinde Sep 3, 2026
6aaa8ab
fix(e2e): make the chrome specs pass in a real browser
Sep 3, 2026
e7d6403
Merge pull request #679 from ConductionNL/fix/app-chrome-spec-selectors
rubenvdlinde Sep 4, 2026
99f684b
feat(chrome): give larpinq a Store
Sep 4, 2026
643dcdb
Merge pull request #681 from ConductionNL/feat/store-surface
rubenvdlinde Sep 4, 2026
00e1fa9
feat(setup): ask which example data to load, as cards, instead of a b…
rubenvdlinde Sep 5, 2026
095202a
fix(e2e): the demo-data test arranges its choice, and the Suggest-fea…
rubenvdlinde Sep 5, 2026
075f772
Merge pull request #637 from ConductionNL/development
rubenvdlinde Sep 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ permissions:

jobs:
quality:
if: (github.event_name != 'pull_request' || github.head_ref != 'development')
# ConductionNL, NOT Conduction. e2faa092 ("org rename") pointed all eight
# reusable-workflow calls in this repo at `Conduction/.github`, which
# Actions cannot resolve — so from 2026-06-01 every run of this workflow
Expand Down
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Maak een [featureverzoek](https://github.com/ConductionNL/larpinq/issues)

**Support:** neem voor support contact op met support@conduction.nl. Voor een Service Level Agreement (SLA), neem contact op met sales@conduction.nl.
]]></description>
<version>0.2.3-unstable.20260830082603</version>
<version>0.2.8-unstable.20260831165721</version>
<licence>EUPL-1.2</licence>
<author mail="info@conduction.nl" homepage="https://www.conduction.nl/">Conduction</author>
<namespace>Larpinq</namespace>
Expand Down
152 changes: 127 additions & 25 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
@@ -1,28 +1,130 @@
<?php

return [
'routes' => [
// Page routes
['name' => 'dashboard#page', 'url' => '/', 'verb' => 'GET'],
['name' => 'characters#downloadPdf', 'url' => '/characters/{id}/download/{template}', 'verb' => 'GET'],
['name' => 'events#downloadRunsheet', 'url' => '/events/{id}/runsheet/{template}', 'verb' => 'GET'],
['name' => 'events#roster', 'url' => '/api/events/{id}/roster', 'verb' => 'GET'],
['name' => 'events#recordAttendance', 'url' => '/api/events/{id}/attendance', 'verb' => 'POST'],
['name' => 'characters#requirementReport', 'url' => '/api/characters/{id}/requirement-report', 'verb' => 'GET'],
['name' => 'settings#index', 'url' => 'api/settings', 'verb' => 'GET'],
['name' => 'settings#create', 'url' => 'api/settings', 'verb' => 'POST'],
// Canonical AppHost settings write (OpenRegister\AppHost\Routes::standard()).
// `settings#create` above stays as the legacy POST alias; both reach the
// same SettingsController::update(). URL spelled without a leading slash
// to match its two siblings — RouteParser ltrims it either way.
['name' => 'settings#update', 'url' => 'api/settings', 'verb' => 'PUT'],
['name' => 'settings#reimport', 'url' => 'api/settings/reimport', 'verb' => 'POST'],
// First-time setup wizard (ADR-042).
['name' => 'setup#status', 'url' => '/api/setup/status', 'verb' => 'GET'],
['name' => 'setup#saveConfig', 'url' => '/api/setup/config', 'verb' => 'POST'],
['name' => 'setup#runAction', 'url' => '/api/setup/action/{actionId}', 'verb' => 'POST'],
// Generic per-user preferences (used by shared nextcloud-vue widgets, e.g. CnSupportDialog).
['name' => 'preferences#getPreference', 'url' => '/api/preferences/{key}', 'verb' => 'GET'],
['name' => 'preferences#setPreference', 'url' => '/api/preferences/{key}', 'verb' => 'PUT'],
],
declare(strict_types=1);

/*
* Larpinq route table.
*
* Built through \OCA\OpenRegister\AppHost\Routes::standard(), which appends
* the SPA catch-all (`dashboard#catchAll` on `/{path}`) after every route
* below. Without that catch-all the server has no handler for
* `/apps/larpinq/<route>`, so deep links and reloads 404 before the SPA loads
* — measured 2026-09-01, larpinq was the ONLY one of the fleet's seven
* hash-routed apps whose sub-paths returned 404 rather than the app shell,
* which is what blocked it from moving to history routing.
*
* Routes listed here are passed as `$extra`; `standard()` lets an `$extra`
* route override a canonical one of the same name, so the existing
* `dashboard#page`, `settings#*` and `preferences#*` entries below keep their
* exact URLs and verbs. Domain routes are inserted BEFORE the catch-all, so
* they keep priority over the `/{path}` fallback.
*
* This file references no OCA\OpenRegister symbol other than the pure array
* builder Routes::standard(), so it is safe to require even when OpenRegister
* is disabled.
*/

$extra = [
// Page routes
['name' => 'dashboard#page', 'url' => '/', 'verb' => 'GET'],
['name' => 'characters#downloadPdf', 'url' => '/characters/{id}/download/{template}', 'verb' => 'GET'],
['name' => 'events#downloadRunsheet', 'url' => '/events/{id}/runsheet/{template}', 'verb' => 'GET'],
['name' => 'events#roster', 'url' => '/api/events/{id}/roster', 'verb' => 'GET'],
['name' => 'events#recordAttendance', 'url' => '/api/events/{id}/attendance', 'verb' => 'POST'],
['name' => 'characters#requirementReport', 'url' => '/api/characters/{id}/requirement-report', 'verb' => 'GET'],
['name' => 'settings#index', 'url' => 'api/settings', 'verb' => 'GET'],
['name' => 'settings#create', 'url' => 'api/settings', 'verb' => 'POST'],
// Canonical AppHost settings write (OpenRegister\AppHost\Routes::standard()).
// `settings#create` above stays as the legacy POST alias; both reach the
// same SettingsController::update(). URL spelled without a leading slash
// to match its two siblings — RouteParser ltrims it either way.
['name' => 'settings#update', 'url' => 'api/settings', 'verb' => 'PUT'],
['name' => 'settings#reimport', 'url' => 'api/settings/reimport', 'verb' => 'POST'],
// First-time setup wizard (ADR-042).
['name' => 'setup#status', 'url' => '/api/setup/status', 'verb' => 'GET'],
['name' => 'setup#saveConfig', 'url' => '/api/setup/config', 'verb' => 'POST'],
['name' => 'setup#runAction', 'url' => '/api/setup/action/{actionId}', 'verb' => 'POST'],
// Generic per-user preferences (used by shared nextcloud-vue widgets, e.g. CnSupportDialog).
['name' => 'preferences#getPreference', 'url' => '/api/preferences/{key}', 'verb' => 'GET'],
['name' => 'preferences#setPreference', 'url' => '/api/preferences/{key}', 'verb' => 'PUT'],
];

// ⚠️ The AppHost builder is invoked through a `class_exists()` guard.
//
// Nextcloud `include`s this file for EVERY larpinq request, and PHPUnit
// includes it without booting sibling apps at all. An unguarded static call to
// a class owned by another app therefore fatals — measured: four PHPUnit
// errors reading `Class "OCA\OpenRegister\AppHost\Routes" not found` the
// moment this file started calling it. In production the same shape makes
// every route in the app 500 when openregister is absent, not just the AppHost
// ones, and larpinq does not declare `<app>openregister</app>`, so an admin can
// create exactly that configuration.
//
// `class_exists()` autoloads without fatalling when the class is unavailable.
// The fallback below reproduces `Routes::standard()`'s output locally, so
// larpinq still routes — catch-all included — without openregister.
if (class_exists('OCA\OpenRegister\AppHost\Routes') === true) {
return \OCA\OpenRegister\AppHost\Routes::standard($extra);
}

$canonicalRoutes = [
['name' => 'dashboard#page', 'url' => '/', 'verb' => 'GET'],
['name' => 'settings#index', 'url' => '/api/settings', 'verb' => 'GET'],
['name' => 'settings#create', 'url' => '/api/settings', 'verb' => 'POST'],
['name' => 'settings#update', 'url' => '/api/settings', 'verb' => 'PUT'],
['name' => 'settings#load', 'url' => '/api/settings/load', 'verb' => 'POST'],
['name' => 'preferences#getPreference', 'url' => '/api/preferences/{key}', 'verb' => 'GET'],
['name' => 'preferences#setPreference', 'url' => '/api/preferences/{key}', 'verb' => 'PUT'],
// ⚠️ The health and metrics routes are DELIBERATELY absent here, and their
// absence is the point of this comment. Routes::standard() supplies both on
// the branch above, where OpenRegister's AppHost aliases its generic
// health/metrics controllers onto larpinq's conventional class names —
// which is why /api/health and /api/metrics answer 200 on a normal instance
// even though this repo ships neither controller.
//
// This fallback runs ONLY when OpenRegister is absent, and then nothing
// aliases them: declaring those routes would advertise two endpoints whose
// target classes do not exist, so a request to either would fatal rather
// than 404. gate-14 (route-reachability) reported exactly that.
//
// ⚠️ And do NOT write their route slugs (`<controller>` + `#` + `<method>`)
// into this comment. gate-14 reads this file statically and matches that
// shape anywhere in it, comments included — spelling them out here made the
// gate go on reporting both long after the routes themselves were gone,
// with the finding pointing at controller files that do not exist.
];

$catchAllRoute = [
'name' => 'dashboard#catchAll',
'url' => '/{path}',
'verb' => 'GET',
// Mirrors Routes::standard()'s own requirement, lookahead included.
// Nextcloud's RouteParser processes `routes` before `resources` and Symfony
// matches in insertion order, and `.+` matches slashes — so a bare `.+`
// catch-all swallows unmatched `api/...` paths and answers the SPA shell at
// HTTP 200, handing JSON callers HTML with nothing erroring
// (openregister#3270, zaakafhandelapp#619).
'requirements' => ['path' => '(?!api/).+'],
'defaults' => ['path' => ''],
];

$extraNames = [];
foreach ($extra as $extraRoute) {
if (isset($extraRoute['name']) === true) {
$extraNames[(string) $extraRoute['name']] = true;
}
}

$mergedRoutes = [];
foreach ($canonicalRoutes as $canonicalRoute) {
if (isset($extraNames[$canonicalRoute['name']]) === true) {
continue;
}

$mergedRoutes[] = $canonicalRoute;
}

$mergedRoutes = array_merge($mergedRoutes, $extra);
$mergedRoutes[] = $catchAllRoute;

return ['routes' => $mergedRoutes];
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@
"optimize-autoloader": true,
"sort-packages": true,
"platform": {
"php": "8.3"
"php": "8.3",
"ext-xsl": "1"
}
}
}
17 changes: 9 additions & 8 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

123 changes: 120 additions & 3 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,26 @@ export default [
// resolves a rule's plugin from the object the rule sits in, so an override
// must name files whose config already registers it.
files: ['**/*.js', '**/*.mjs', '**/*.ts', '**/*.tsx', '**/*.vue'],
// 🔴 THE `ignores` ARE PART OF THAT SCOPE, NOT AN OPINION. v9 registers
// the jsdoc plugin ONLY inside `nextcloud/documentation/*`, and every one
// of those blocks carries exactly this ignore list: Nextcloud does not
// require JSDoc in tests. Naming a `jsdoc/*` rule for a test file
// therefore points at a plugin that is not registered there, and eslint
// refuses to run AT ALL rather than reporting a finding.
//
// Measured here: with the list absent, `eslint tests` died with "A
// configuration object specifies rule 'jsdoc/check-tag-names', but could
// not find plugin 'jsdoc'" and linted nothing. `eslint src` was green
// throughout, because it never reached a test file.
ignores: [
'**/*.test.*',
'**/*.spec.*',
'**/*.cy.*',
'**/test/**',
'**/tests/**',
'**/__tests__/**',
'**/__mocks__/**',
],
rules: {
// `@spec` (hydra gate-16 / gate-19 traceability) and `@visual` (the
// visual-coverage gate) are this project's own JSDoc tags. v9 sets
Expand Down Expand Up @@ -110,16 +130,113 @@ export default [
},

{
// Node-side CLI tools (build / validate scripts) legitimately use console
// and process.exit, and ship as plain JS with no shebang.
files: ['tests/validate-manifest.js', 'tests/validate-register.js', 'tests/validate-json-strict.js'],
// Node-side CLI checkers under tests/ legitimately use console and
// process.exit, and ship as plain JS with no shebang.
//
// 🔴 A GLOB, NOT A FILE LIST. This block used to name three files by hand
// (`validate-manifest.js`, `validate-register.js`, `validate-json-strict.js`)
// and had silently stopped covering every checker added since. That is the
// failure mode a hand-maintained list always has: adding a file does not
// add it to the list, and the omission is invisible.
files: ['tests/**/*.js', 'tests/**/*.mjs', 'tests/**/*.ts'],
rules: {
'no-console': 'off',
'n/no-process-exit': 'off',
'n/hashbang': 'off',
// Tests import devDependencies by definition; this rule is about what
// ships in the published package, which tests/ never does.
'n/no-unpublished-import': 'off',
},
},

{
// 🔴 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'],
},


// eslint-config-prettier LAST OF ALL, and it has to be last: it only turns
// rules OFF, and what it turns off is everything prettier owns — including
// the `@stylistic/*` family v9 introduces (`indent`, `quotes`, `semi`).
Expand Down
2 changes: 1 addition & 1 deletion l10n/.schema-l10n-baseline.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"uncovered": 134
"uncovered": 125
}
Loading
Loading