Skip to content

feat(demo): a demo caseload, and the three silent defects that kept it invisible - #1676

Merged
rubenvdlinde merged 18 commits into
developmentfrom
feat/demo-caseload-seed
Sep 3, 2026
Merged

rubenvdlinde merged 18 commits into
developmentfrom
feat/demo-caseload-seed

Conversation

@rubenvdlinde

@rubenvdlinde rubenvdlinde commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Why

The Tasks page, My Work and five dashboard widgets were empty. The ask was demo data. The data turned out to be the smaller half of the problem.

What was actually wrong

Three defects, none of which errored, logged, or failed a test.

1. A schema slug is not unique, and Dossiq resolved it instance-wide.

Three schemas carried the slug task on a normal dev instance:

schema register owner
52 another register another app's InterneTaak
146 another register duplicate
173 dossiq (23) ours

SchemaMapper::find('task') returns whichever row it fetches first. It returned 52. Two call sites resolved slugs that way and both landed on the foreign schema:

  • SchemaKeyReconciler wrote task_schema = 52, so all seven consumers that create or read case tasks wrote into another app's register. The Tasks page read 0 rows, which looks exactly like "no data".
  • SchemaAnnotationReconciler merged Dossiq's x-openregister-calculations onto that same foreign schema. Measured before the fix:
schema 52  (foreign) -> isTerminalStatus, daysUntilDue, daysOverdue
schema 173 (dossiq)  -> blocksCase

So isTerminalStatus never materialised on our tasks. All 7 completed tasks read false, and "My Tasks" and "Task Due Reminders", whose entire filter is isTerminalStatus = false, kept showing finished work. daysUntilDue did not exist to extend, so every due-date column rendered blank.

The rule now lives in one class, SchemaSlugResolver. Two call sites each resolving slugs their own way is the defect: they disagreed about which schema task meant, so the config keys and the annotations landed on different rows.

The unscoped lookup stays as a fallback, deliberately. Dossiq points appointment, location and catalog at schemas owned by other apps; those slugs are unique instance-wide, and removing the fallback would blank all three. Measured: 107 of 111 schema keys already resolved inside register 23, those three are the intended exceptions, and task was the only real collision.

2. The alert widgets could not show what they are for.

openspec/specs/signalering-widgets/spec.md requires Deadline Alerts to list cases approaching their deadline and cases already overdue. Both widgets do filter for that, then cap at 5 with overdue listed first. So from the fifth overdue case onward, no at-risk case can appear at all, which breaks the requirement's first scenario. It also made both alert widgets render the same rows as the Overdue and My Tasks widgets beside them, which is how it surfaced.

Raised both caps to 10, in the manifest and in the two Vue widgets. Deliberately not narrowing the filter to a forward-only window: that reads as the obvious fix and would contradict the spec.

What this adds

occ dossiq:demo:seed: 18 cases across the four shipped case types plus 32 tasks, positioned so every widget has rows. Idempotent by case title.

Two things it had to work around:

  • Dates are relative day offsets, never absolute. A fixture with absolute dates is right the day it is written and wrong every day after, which is how "the demo dashboard is empty again" turns out to be a stale file.
  • A case's deadline cannot be written, only caused. deadline is a materialised calculation over startDate plus the case type's processingDeadline, so a written deadline is overwritten on save. The seed backdates startDate by the case type's own processing deadline instead.

--verify-only reports the buckets by reading the register back, not by counting the input, which would agree with the input by construction.

Verified

On the dev instance, not inferred:

  • Seed created 18 cases and 32 tasks; a re-run skipped all 18.
  • Arithmetic end to end: startDate 2026-06-17 + P56D materialised deadline 2026-08-12, exactly the requested 21 days overdue.
  • After the fix the calculations sit on 173 only, the strays are gone from 52, all 7 completed tasks read isTerminalStatus = true, and daysUntilDue returns real numbers.
  • Re-running both reconcilers writes 0 and 0, so nothing re-pollutes the foreign schema.
  • Deadline Alerts now shows the 5 overdue and the 4 at-risk cases (Due today, 1, 2 and 3 days remaining) and no longer duplicates its neighbour.
  • create() probed against the live ObjectService, not only the unit fake: real uuid returned, row found with its parent case reference intact, cleanup clean.

Both schema fixes carry a negative control: with the fix disabled the new tests fail with '52' where '173' is required, and the date test fails when the interval is added instead of subtracted.

Checks

  • phpcs, phpmd, psalm clean.
  • 2759 unit tests pass (3 skipped, pre-existing).
  • Hydra gates: all 78 applicable green.
  • New Playwright spec tests/e2e/demo-caseload.spec.ts, 4 passed, and the register was left with exactly the 32 tasks it started with. It seeds under a per-run prefix and removes it again, so it does not depend on demo data existing.

A sixth defect, found while verifying the demo

The _filters[x] query parameter is inert. useObjectStore.fetchCollection
passes its params straight to the query string and OpenRegister reads a bare
field name, so nothing using that form has ever filtered. Measured against the
live API:

request result
_filters[assignee]=rbac-editor 32 rows, every assignee
assignee=rbac-editor 2 rows, only that user
_filters[caseType]=<uuid> 46 statusTypes, 6 matching
caseType=<uuid> 6 statusTypes

41 call sites used it, and none narrowed the result afterwards:

  • My Tasks / Task Reminders fetched every user's tasks. On this instance
    admin owns 30 of 32, which is exactly why it looked correct.
  • Cases overview had no status filter at all, so recently created-and-closed
    rows pushed every live case out of all 7 slots.
  • 25 _filters[caseType] sites: every case-type settings tab listed every
    case type's statuses, results, roles and document types instead of the
    selected one's.
  • 9 _filters[case] sites: case-scoped reads returned every case's rows, and
    bezwaar.js takes objections?.[0] from that unfiltered list, so a case
    detail page could attribute another case's objection to the case being viewed.

All 41 now use bare field names. The rewrite is mechanical and the diff is only
that: every added line is a bare field key. Verified with prettier, eslint (0
errors), 360 vitest tests, a production build, and in the browser: the case
detail page lists its own three tasks and none of the other 29.

Not done here

The Tasks index shows case as a raw uuid rather than the case title. CnIndexPage columns have no resolver (the resolve config exists only on detail-page objectField), so fixing it means a change in the shared nextcloud-vue library. Out of scope for this PR, worth an issue.

🤖 Generated with Claude Code

Three schemas carried the slug `task` on a normal dev instance (ids 52, 146
and 173). Only 173 belongs to dossiq's register, but SchemaKeyReconciler
resolved slugs with SchemaMapper::find(), which is instance-wide, and that
returned 52: an InterneTaak schema owned by another app in another register.

So `task_schema` pointed outside dossiq's own register. All seven consumers
that create or read case tasks (flow human steps, reassignment, work queue,
substitution, transition task creation, checklist guard) wrote to a foreign
schema. Nothing threw and nothing logged. The Tasks page simply stayed empty,
which reads as "no data" rather than "wrong schema".

Resolve the slug among the register's own schemas first, via OpenRegister's
findBySlugInIds(), which was built for exactly this collision. Keep the
unscoped lookup as a fallback: dossiq deliberately points appointment,
location and catalog at schemas owned by other apps, those slugs are unique
instance-wide, and dropping the fallback would blank all three.

Measured on the dev instance: 107 of 111 schema keys already resolved inside
register 23, appointment/location/catalog are the intended cross-register
three, and task was the only real collision.
The Tasks page, My Work and five dashboard widgets were empty because the
register held 0 tasks and none of the 14 existing cases had an assignee. Every
one of those surfaces filters on assignee = the current user.

Adds `occ dossiq:demo:seed`: 18 cases across the four shipped case types plus
32 tasks, positioned so Overdue, Deadline Alerts, My Tasks, Task Due Reminders,
Stalled Cases and the Completed KPI all have rows. Idempotent by case title, so
re-running skips what is already there.

Two things this had to work around.

Dates in the dataset are relative day offsets, never absolute dates. A fixture
with absolute dates is right on the day it is written and wrong every day
after, which is how "the demo dashboard is empty again" turns out to be a stale
file rather than a bug.

A case's deadline cannot be written, only caused: `deadline` is a materialised
OpenRegister calculation over startDate plus the case type's processingDeadline,
so a written deadline is overwritten on save. The seed backdates startDate by
the case type's own processing deadline instead, and `--verify-only` reports the
buckets by READING THE REGISTER BACK rather than by counting the input, which
would agree with the input by construction.

Verified on the dev instance: 18 cases and 32 tasks created, re-run skipped all
18, and the read-back reports 5 overdue, 4 within three days, 4 closed and 25
open tasks. Spot-checked the arithmetic end to end: startDate 2026-06-17 plus
P56D materialised deadline 2026-08-12, exactly the requested 21 days overdue.
…app's schema

Same defect as the previous commit, second call site, worse blast radius.

SchemaAnnotationReconciler also resolved schema slugs with the instance-wide
SchemaMapper::find(). For the slug `task` that returned schema 52, an
InterneTaak schema owned by another app in another register, so dossiq merged
its own x-openregister-calculations onto SOMEBODY ELSE'S schema. Measured on
the dev instance before the fix:

  schema 52  (foreign) -> isTerminalStatus, daysUntilDue, daysOverdue
  schema 173 (dossiq)  -> blocksCase

Two visible consequences, neither of which errored. isTerminalStatus never
materialised on dossiq's tasks, so all 7 completed tasks read isTerminalStatus
= false and stayed in "My Tasks" and "Task Due Reminders", whose whole filter
is isTerminalStatus = false. And daysUntilDue did not exist to extend, so every
due-date column on those widgets rendered blank.

The rule now lives in one place, SchemaSlugResolver, because two call sites
each resolving slugs their own way IS the defect: they disagreed about which
schema `task` meant, and the config keys and the annotations landed on
different rows. Both reconcilers share one instance.

Verified on the dev instance: after the fix the calculations sit on 173 only,
the stray copies are gone from 52, all 7 completed tasks now read
isTerminalStatus = true, daysUntilDue returns real numbers, and re-running both
reconcilers writes 0 and 0, so nothing re-pollutes the foreign schema.
…erdue

openspec/specs/signalering-widgets/spec.md requires Deadline Alerts to list
cases approaching their deadline AND cases already overdue, and Task Due
Reminders to do the same for tasks. Both widgets filter correctly for that, but
capped the result at 5 rows ordered by deadline ascending.

So once five cases are overdue, the five slots are full and no at-risk case can
appear at all. That breaks the widget's first scenario, which says the two
cases due within the warning threshold MUST be displayed. It also made the two
alert widgets render exactly the same rows as the Overdue and My Tasks widgets
next to them, which is what surfaced it.

Raise both limits to 10 so overdue and at-risk both fit. Deliberately NOT
narrowing the filter to a forward-only window: that would read as the obvious
fix and would contradict the spec, which wants overdue cases in this widget.
…s with e2e

The two Nextcloud dashboard widgets concatenate overdue items ahead of at-risk
ones and then slice(0, 5). Once five items are overdue the slice is full, so no
at-risk item can ever be shown, and both widgets render the same rows as the
Overdue Cases and My Tasks widgets beside them. The spec requires both groups
in these widgets, so this is the same defect already fixed in the manifest for
the in-app dashboard, on the Nextcloud dashboard surface.

Verified on the dev instance after rebuilding the bundle: Deadline Alerts now
lists the 5 overdue cases AND the 4 at-risk ones (Due today, 1, 2 and 3 days
remaining), overdue first, and no longer duplicates the widget next to it.

Adds tests/e2e/demo-caseload.spec.ts, which pins the three regressions behind
the empty Tasks page rather than the symptom:

  - a completed task must materialise isTerminalStatus = true, which is what
    the open-work filters read
  - daysUntilDue must compute, asserted as a NUMBER because the defect
    returned null and an existence check would have passed
  - the Tasks page must render rows instead of its empty state

It seeds what it needs under the per-run prefix and removes it again, so it
does not depend on demo data and cannot be satisfied by anyone else's rows.
The row assertion is deliberately not a title assertion: the index pages at 20,
so a title would depend on how many tasks the instance holds. The seeded task's
own visibility is pinned on its detail page instead.

4 passed against the dev instance, and the register was left with exactly the
32 tasks it started with.
Two gate findings, both real rather than noise.

gate-17 (redundant-controller) flagged `create()` as a literal pass-through to
ObjectService. It was: it wrapped saveObject and handed the entity straight
back, so every call site had to follow it with idOf(). Return the new object's
ID instead, which is the only thing either caller actually wants, and both call
sites lose a step. A failure is now '' rather than null, which the task counter
reads the same way.

gate-66 (openregister-dependency-shape, ADR-083) flagged the unguarded
container lookup. OpenRegister is optional here, so establish availability with
IAppManager::isInstalled() first and keep the lookup, which is the escape the
gate names. It also splits two different problems that previously produced one
message: OpenRegister absent, versus present but unable to construct.

Verified against the live ObjectService rather than only the unit fake, because
the fake cannot prove the entity shape: create() returned a real uuid, the row
was found with its parent case reference intact, and cleanup left nothing. The
same probe confirms schemaIds() now resolves task to schema 173.
The Frontend Check (format) job runs `prettier --check` across the tree and
flagged the new spec's trailing blank line.
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ eb34e8f

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue3-compile
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 540/540
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright ⏭️ deferred — runs on the promotion into beta/main, not on a pull request into development
Hydra gates

Quality workflow — 2026-09-02 13:20 UTC

Download the full PDF report from the workflow artifacts.

Both were reconstructed from scratch to prepare a demo. The seed command and
the mail setup are useless to the next person if the steps live only in a
terminal history.
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ 017c295

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint ⏭️
stylelint
build
check-manifest
check-vue3-compile
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 540/540
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-09-02 13:22 UTC

Download the full PDF report from the workflow artifacts.

The case schema declares x-openregister-archival, so OpenRegister refuses a
user-driven delete. A case a spec creates can therefore never be cleaned up,
and cleanupRunObjects tolerates the failure silently, so every run added one
permanently. Measured on the dev instance: 17 of its 37 cases were exactly that
residue, and they crowd real data out of Open Cases and Stalled Cases.

Hang the tasks off an existing case instead, and create one only where the
register is genuinely empty. Tasks carry no archival rule and are still torn
down. `case` leaves SEEDED_SCHEMAS as well, since listing it there was a
cleanup that quietly failed every run.

Verified: 4 passed, and the register held 37 cases before the run and 37 after.
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ 7bacac3

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue3-compile
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 540/540
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright
Hydra gates

Quality workflow — 2026-09-02 13:25 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ 37c2c41

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue3-compile
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 540/540
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright ⏭️ deferred — runs on the promotion into beta/main, not on a pull request into development
Hydra gates

Quality workflow — 2026-09-02 13:30 UTC

Download the full PDF report from the workflow artifacts.

SettingsService constructs the resolver, so every SettingsServiceTest and
VthSettingsServiceTest case executes it. With beStrictAboutCoverageMetadata and
failOnRisky both on, an executed class that no annotation lists makes the test
RISKY, and 10 of them turned the whole PHPUnit matrix red on all six cells
while reporting zero failures.

⚠️ It passes locally without this. The strict-coverage check only runs when a
coverage driver is collecting, and CI generates a Clover report while a plain
`vendor/bin/phpunit` does not. Reproduce it with
`php -d pcov.enabled=1 vendor/bin/phpunit --coverage-clover=…`, which is how
this was confirmed fixed: 2759 tests, zero risky, exit 0.

The two sibling reconcilers were already declared the same way.
Both were inconsistent with the widgets beside them, and one was visibly wrong.

The Overdue KPI filtered `deadline < today` but not `isFinalStatus`, while the
Overdue table beneath it filters both. Measured on the dev instance: the card
read 8 against a list of 5, because three closed cases still had a past
deadline. openspec/specs/dashboard/spec.md DASH-001b defines overdue as
`deadline < today` AND status not final.

The Open Cases table had no filter at all, so a panel titled "Open Cases"
listed closed ones. Every sibling case widget already filters isFinalStatus.
openspec/specs/dashboard/spec.md REQ-DASH-001 names five KPI cards. Three of
the four that ship disagreed with it, and two disagreed with the list rendered
directly beneath them.

DASH-001a. The first card was titled "New cases" and counted startDate inside
the date picker's window. That answers a different question, and left the
dashboard with no headline for open workload at all. It is now "Open Cases",
counting every case whose status is not final, and deliberately not scoped to
the date range: the spec defines this count over all open cases.

DASH-001b. The Overdue card was scoped to the date range while the Overdue
table beside it was not. On the default month preset the card read 0 against a
list of 5, because every overdue case necessarily started before the window it
is overdue in. The range scope is gone and the caption is the spec's "action
needed".

DASH-001d. My Tasks counted `assignee = @me` AND a dueDate inside the range, so
a task with no due date, or one due outside the window, was invisible. The spec
counts the user's tasks in a non-terminal status. Now filtered on
`isTerminalStatus: false`, which is the same rule the My Tasks widget uses.

Measured after the change: Open Cases 16, Overdue 5 (matching its table
exactly), My Tasks 23.

NOT done here, and deliberately. DASH-001a's "+3 today", DASH-001c's "avg 18
days" and DASH-001e's SLA Compliance card all need a second computed number on
one tile. CnStatWidget interpolates a caption ONLY in endpointSource mode, so
each needs a Dossiq KPI endpoint to read from. That is a feature with its own
controller, route and spec work, not a manifest edit. DASH-001c is also left
alone because "Completed This Month" and the shipped date-range picker want
different windows, which is a product decision rather than a defect.
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ 053ac9e

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue3-compile
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 540/540
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright ⏭️ deferred — runs on the promotion into beta/main, not on a pull request into development
Hydra gates

Quality workflow — 2026-09-02 17:43 UTC

Download the full PDF report from the workflow artifacts.

…t too

SettingsServiceReconcileRegressionTest also constructs SettingsService, so it
executes SchemaSlugResolver and was RISKY for the same reason as the other two.
CI went from 10 risky to 2; these are the last 2.

⚠️ THE PREVIOUS COMMIT CLAIMED A LOCAL REPRODUCTION THAT NEVER HAPPENED. The
`php -d pcov.enabled=1` run reported no risky tests because PHPUnit answered
"No code coverage driver available" and carried on: neither pcov nor xdebug is
installed on this machine or in the dev container. A run with no driver reports
zero risky whether or not the problem is there, so it proved nothing.

There is no local instrument for this check, so the argument is static instead,
and it covers the class rather than the instance. The finding is precisely
"executed a class not listed as covered or used", and `requireCoverageMetadata`
is false, so it can only fire on a test that HAS @Covers. Every test carrying
@Covers that constructs SettingsService or either reconciler now declares the
resolver; the one remaining file has no coverage metadata and is exempt.
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ 3ebf5a1

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue3-compile
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 540/540
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright ⏭️ deferred — runs on the promotion into beta/main, not on a pull request into development
Hydra gates

Quality workflow — 2026-09-02 17:51 UTC

Download the full PDF report from the workflow artifacts.

…s ignored their filter

useObjectStore.fetchCollection passes its params straight to the query string,
and OpenRegister reads a BARE field name. Measured against the live API:

  _filters[assignee]=rbac-editor  -> 32 rows, every assignee
  assignee=rbac-editor            ->  2 rows, only that user
  _filters[isFinalStatus]=false   -> 37 rows, closed cases included
  isFinalStatus=false             -> 16 rows, open only

So the filter never applied. MyTasksWidget and TaskRemindersWidget fetched
EVERY user's tasks and then narrowed only by status, which is not something a
widget titled "My Tasks" may do: on a real team it shows other people's work.
On this instance admin happens to own 30 of 32 tasks, which is exactly why it
looked right.

CasesOverviewWidget had no status filter at all, so it listed closed cases; and
because it orders by startDate desc, a burst of recently created-and-closed
rows pushed every live case out of all 7 slots.

All three now filter server-side on bare field names. Verified in the browser:
Cases overview lists only open demo cases, My Tasks lists 7 open tasks, and the
dashboard no longer shows a single e2e row.

⚠️ 38 OTHER CALL SITES USE THE SAME INERT IDIOM (settings tabs, workflow
editors, the workflow store), so `_filters[caseType]` is not scoping those
lists to the selected case type either. They are left alone here deliberately:
that is a separate change across a dozen files with its own testing, not a
rider on a demo-data PR.
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ 605847c

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue3-compile
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 540/540
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright ⏭️ deferred — runs on the promotion into beta/main, not on a pull request into development
Hydra gates

Quality workflow — 2026-09-02 18:15 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ a119946

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue3-compile
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 540/540
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright ⏭️ deferred — runs on the promotion into beta/main, not on a pull request into development
Hydra gates

Quality workflow — 2026-09-02 19:06 UTC

Download the full PDF report from the workflow artifacts.

38 remaining call sites across 19 files, all silently unfiltered.
useObjectStore.fetchCollection passes params straight to the query string and
OpenRegister reads a bare field name, so `_filters[caseType]` was never a
filter. Measured against the live API:

  _filters[caseType]=<uuid>  -> 46 statusTypes, only 6 of them matching
  caseType=<uuid>            ->  6 statusTypes

Two consequences, both wrong data rather than slow queries.

25 `_filters[caseType]` sites: every case-type settings tab (statuses, results,
roles, document types, decision types, properties, workflow) listed EVERY case
type's children instead of the selected one's, and the workflow editor and
store did the same.

9 `_filters[case]` sites: case-scoped reads returned every case's rows. None of
them narrowed the result afterwards, and bezwaar.js takes `objections?.[0]`
from that unfiltered list, so a case detail page could attribute ANOTHER case's
objection to the case being viewed. inspection.js and advice.js assigned the
whole unfiltered list straight to their state.

The rewrite is mechanical, one form to another, and the diff is only that: every
added line is a bare field key and nothing else moved. `case` as a property key
is legal, and the build confirms it.

Verified: prettier clean, eslint 0 errors, 360 vitest tests pass, production
build succeeds. In the browser the case detail page now lists its own three
tasks and none of the other 29, and the API returns 6 statuses for a case type
where it previously returned all 46.
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ 03fac61

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue3-compile
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 540/540
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright ⏭️ deferred — runs on the promotion into beta/main, not on a pull request into development
Hydra gates

Quality workflow — 2026-09-03 04:54 UTC

Download the full PDF report from the workflow artifacts.

gate-16 is diff-scoped, so changing one line inside these three methods made
them 'changed' and required a @SPEC tag. Each points at the spec its own file
already references, and each docblock records why the line changed: the
`_filters[x]` form they used is inert, so all three were reading every case's
or every case type's rows.

Verified: all 78 applicable hydra gates green, prettier clean, 360 vitest tests
pass.
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ 232bba7

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue3-compile
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 540/540
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright ⏭️ deferred — runs on the promotion into beta/main, not on a pull request into development
Hydra gates

Quality workflow — 2026-09-03 04:59 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/dossiq @ 16827da

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
check-vue3-compile
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 106/106
npm ✅ 540/540
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman ⏭️
Playwright ⏭️ deferred — runs on the promotion into beta/main, not on a pull request into development
Hydra gates

Quality workflow — 2026-09-03 05:03 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit 355d1ec into development Sep 3, 2026
49 checks passed
@rubenvdlinde
rubenvdlinde deleted the feat/demo-caseload-seed branch September 3, 2026 05:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant