The behandel werkbak now refreshes itself while it is open, so a registration that reaches
beoordeling after the behandelaar opened the page shows up on its own — no reload.
interval(WERKBAK_REFRESH_MS) (5 s) re-reads the existing BFF endpoint, scoped to the page with takeUntilDestroyed(). A background read leaves the rows and states on screen alone until it has
an answer, so a tick never flashes the loading state over rows being read and one failed poll never
swaps the list for the error alert; a read that comes back also clears an earlier failure, so the
view recovers on its own rather than needing the very reload this slice removes.
No new endpoint, dependency or server-side state, and no service boundary moves — rxjs and GET /behandel/werkbak are both already here. ADR-0032 records why polling rather than a pushed
stream: nothing notifies the BFF either, so SSE/WebSockets would poll the domain inside the BFF for
the same freshness, plus connection lifecycle, nginx buffering and a stateful BFF. Proposal: #163.
Implementation makes the test pass; refactor commit if structure improved.
Conventional Commits referencing the issue (refs #162).
CI green — all Gitea Actions jobs.
docker compose up from a fresh clone reaches green health checks within 3 minutes (unchanged; only the behandel bundle differs).
Docs updated if behaviour, contracts, or operations changed.
ADR added in docs/architecture/ (ADR-0032).
Demo note in docs/demo-script.md (user-visible).
Notes for reviewers
The e2e is the real acceptance test, and it took two goes to make it one. Simply dropping the staff.reload() from the happy path proved nothing: the werkbak was visited after the documents
were supplied, so the row was already there at page load. The spec now logs the behandelaar in first, asserts the row is not there yet, and only then has the citizen supply the documents that
route it to Beoordelen — so the row can only reach that already-open, never-reloaded page via the
refresh. Verified both ways against a live stack: with the interval stubbed out it fails at Goedkeuren <ref> … element(s) not found after 30 s; with it, the behandel nginx logs the poll that
delivers the row. The page is foregrounded before the assertion because Chromium throttles timers in
a hidden tab.
Ceiling (named in the ADR): a fixed 5 s interval, per open page, that keeps polling in a
background tab; each tick costs one Flowable task query plus a store read per open task. Upgrade
path: publish task events from the domain, then swap the interval for a stream — the endpoint
contract and the rendering stay put. Gate on document.visibilityState first if request volume is
the concern.
Two housekeeping notes, neither blocking:
#162 is on no milestone (DoD item 1). It is portal UX, so it fits neither Data Governance
nor Production Posture cleanly — your call where it lands.
The issue titles itself S-26, which already belongs to the self-service resume slice (#111, BACKLOG.md). Everything here references #162; worth renumbering the title if the S-ids are
meant to stay unique. BACKLOG.md is untouched for the same reason (it mirrors the active
milestone, and this slice is on none).
## What & why
The behandel werkbak now **refreshes itself** while it is open, so a registration that reaches
beoordeling after the behandelaar opened the page shows up on its own — no reload.
`interval(WERKBAK_REFRESH_MS)` (5 s) re-reads the existing BFF endpoint, scoped to the page with
`takeUntilDestroyed()`. A *background* read leaves the rows and states on screen alone until it has
an answer, so a tick never flashes the loading state over rows being read and one failed poll never
swaps the list for the error alert; a read that comes back also clears an earlier failure, so the
view recovers on its own rather than needing the very reload this slice removes.
No new endpoint, dependency or server-side state, and no service boundary moves — rxjs and
`GET /behandel/werkbak` are both already here. **ADR-0032** records why polling rather than a pushed
stream: nothing notifies the BFF either, so SSE/WebSockets would poll the domain *inside* the BFF for
the same freshness, plus connection lifecycle, nginx buffering and a stateful BFF. Proposal: #163.
Closes #162
## Definition of Done
- [x] Linked Gitea issue (above).
- [x] Failing test committed before the implementation.
- [x] Implementation makes the test pass; refactor commit if structure improved.
- [x] Conventional Commits referencing the issue (`refs #162`).
- [x] CI green — all Gitea Actions jobs.
- [x] `docker compose up` from a fresh clone reaches green health checks within 3 minutes (unchanged; only the behandel bundle differs).
- [x] Docs updated if behaviour, contracts, or operations changed.
- [x] ADR added in `docs/architecture/` (ADR-0032).
- [x] Demo note in `docs/demo-script.md` (user-visible).
## Notes for reviewers
**The e2e is the real acceptance test, and it took two goes to make it one.** Simply dropping the
`staff.reload()` from the happy path proved nothing: the werkbak was visited *after* the documents
were supplied, so the row was already there at page load. The spec now logs the behandelaar in
**first**, asserts the row is not there yet, and only then has the citizen supply the documents that
route it to Beoordelen — so the row can only reach that already-open, never-reloaded page via the
refresh. Verified both ways against a live stack: with the interval stubbed out it fails at
`Goedkeuren <ref> … element(s) not found` after 30 s; with it, the behandel nginx logs the poll that
delivers the row. The page is foregrounded before the assertion because Chromium throttles timers in
a hidden tab.
**Ceiling (named in the ADR):** a fixed 5 s interval, per open page, that keeps polling in a
background tab; each tick costs one Flowable task query plus a store read per open task. Upgrade
path: publish task events from the domain, then swap the `interval` for a stream — the endpoint
contract and the rendering stay put. Gate on `document.visibilityState` first if request volume is
the concern.
**Two housekeeping notes, neither blocking:**
- #162 is on **no milestone** (DoD item 1). It is portal UX, so it fits neither *Data Governance*
nor *Production Posture* cleanly — your call where it lands.
- The issue titles itself **S-26**, which already belongs to the self-service resume slice (#111,
`BACKLOG.md`). Everything here references **#162**; worth renumbering the title if the S-ids are
meant to stay unique. `BACKLOG.md` is untouched for the same reason (it mirrors the active
milestone, and this slice is on none).
A registration only reaches the Beoordelen task after the citizen supplies its
documents, so it lands in an already-open werkbak asynchronously — today a
behandelaar has to reload to see it.
Component spec: one interval passes and the werkbak has re-read itself, the new
row is on screen, and the loading state has not flashed over the rows being
read. Fails with "expected vi.fn() to be called 2 times, but got 1 times".
Two guards go with it — a failing background poll must not replace the list
with the load-failure alert, and the refresh must stop when the page is
destroyed.
E2E: the happy path now waits for the werkbak row WITHOUT reloading the page.
The removed `staff.reload()` is the assertion.
Refs the accepted mechanism in #163 (ADR-0032, landing with the implementation).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`interval(WERKBAK_REFRESH_MS)` re-reads the existing BFF werkbak endpoint,
scoped to the page's lifetime with `takeUntilDestroyed()`. A background read
leaves the rows and states on screen alone until it has an answer, so a tick
neither flashes the loading state over rows being read nor swaps the list for
the failure alert on a blip — only a foreground read (on open, after a
decision) speaks for whether the werkbak is readable at all.
No new endpoint, dependency or server state: rxjs and the endpoint are both
already here (ADR-0032).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
If the first read fails the werkbak shows its error until the behandelaar
reloads — the very thing this slice removes. Fails with "expected <p
utrecht-paragraph …> to be null".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Records why the werkbak polls rather than being pushed to (proposal #163):
nothing notifies the BFF either, so SSE/WebSockets would poll the domain
inside the BFF for the same freshness plus connection state, a dependency and
a stateful BFF. Names the ceiling (fixed 5s interval, per open page, keeps
polling in a hidden tab) and the upgrade path (publish task events, then swap
the interval for a stream).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The happy path visited the behandel portal only after the documents were
supplied, so the row was already in the werkbak at page load — dropping the
reload proved nothing. Now the behandelaar logs in first, asserts the row is
NOT there yet, and only then does the citizen supply the documents that route
it to Beoordelen. The row can therefore only reach that already-open,
never-reloaded page by the werkbak refreshing itself.
Verified both ways against a live stack: with the interval stubbed out the
spec fails at "Goedkeuren <ref> … element(s) not found" after 30s; with it,
the behandel nginx logs the poll that delivers the row.
The werkbak page is foregrounded before the assertion — Chromium throttles
timers in a hidden tab.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What & why
The behandel werkbak now refreshes itself while it is open, so a registration that reaches
beoordeling after the behandelaar opened the page shows up on its own — no reload.
interval(WERKBAK_REFRESH_MS)(5 s) re-reads the existing BFF endpoint, scoped to the page withtakeUntilDestroyed(). A background read leaves the rows and states on screen alone until it hasan answer, so a tick never flashes the loading state over rows being read and one failed poll never
swaps the list for the error alert; a read that comes back also clears an earlier failure, so the
view recovers on its own rather than needing the very reload this slice removes.
No new endpoint, dependency or server-side state, and no service boundary moves — rxjs and
GET /behandel/werkbakare both already here. ADR-0032 records why polling rather than a pushedstream: nothing notifies the BFF either, so SSE/WebSockets would poll the domain inside the BFF for
the same freshness, plus connection lifecycle, nginx buffering and a stateful BFF. Proposal: #163.
Closes #162
Definition of Done
refs #162).docker compose upfrom a fresh clone reaches green health checks within 3 minutes (unchanged; only the behandel bundle differs).docs/architecture/(ADR-0032).docs/demo-script.md(user-visible).Notes for reviewers
The e2e is the real acceptance test, and it took two goes to make it one. Simply dropping the
staff.reload()from the happy path proved nothing: the werkbak was visited after the documentswere supplied, so the row was already there at page load. The spec now logs the behandelaar in
first, asserts the row is not there yet, and only then has the citizen supply the documents that
route it to Beoordelen — so the row can only reach that already-open, never-reloaded page via the
refresh. Verified both ways against a live stack: with the interval stubbed out it fails at
Goedkeuren <ref> … element(s) not foundafter 30 s; with it, the behandel nginx logs the poll thatdelivers the row. The page is foregrounded before the assertion because Chromium throttles timers in
a hidden tab.
Ceiling (named in the ADR): a fixed 5 s interval, per open page, that keeps polling in a
background tab; each tick costs one Flowable task query plus a store read per open task. Upgrade
path: publish task events from the domain, then swap the
intervalfor a stream — the endpointcontract and the rendering stay put. Gate on
document.visibilityStatefirst if request volume isthe concern.
Two housekeeping notes, neither blocking:
nor Production Posture cleanly — your call where it lands.
BACKLOG.md). Everything here references #162; worth renumbering the title if the S-ids aremeant to stay unique.
BACKLOG.mdis untouched for the same reason (it mirrors the activemilestone, and this slice is on none).