From e38bf62b438e7c99943784a6eb004b936bc4e18e Mon Sep 17 00:00:00 2001 From: Niek Otten Date: Fri, 4 Sep 2026 10:46:02 +0200 Subject: [PATCH] feat(portal-behandel): refresh the werkbak on an interval while it is open (refs #162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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) --- apps/behandel/src/app/werkbak/werkbak-page.ts | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/apps/behandel/src/app/werkbak/werkbak-page.ts b/apps/behandel/src/app/werkbak/werkbak-page.ts index d7af7d4..5c0ba4a 100644 --- a/apps/behandel/src/app/werkbak/werkbak-page.ts +++ b/apps/behandel/src/app/werkbak/werkbak-page.ts @@ -1,4 +1,6 @@ import { Component, inject, signal } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { interval } from 'rxjs'; import { BffApiV1Service, type WerkbakItem } from 'api-client'; import { UtrechtComponentsModule } from 'ui'; @@ -16,6 +18,11 @@ type Besluit = 'goedkeuren' | 'afwijzen'; * Flowable `Beoordelen` tasks, read through the domain) and decides each — goedkeuren or afwijzen. A * decision posts to the BFF, which applies the domain transition and completes the workflow task * (ADR-0013; S-12). After a decision the werkbak refreshes so the handled item drops off the list. + * + * The page also re-reads itself every {@link WERKBAK_REFRESH_MS} while it is open, so a registration + * that reaches beoordeling after the behandelaar opened the werkbak shows up on its own — no reload + * (S-26/#162). Polling rather than a pushed stream: nothing notifies the BFF either, so a stream + * would poll the domain in the BFF instead and add connection state for the same freshness (ADR-0032). */ @Component({ selector: 'app-werkbak-page', @@ -33,11 +40,25 @@ export class WerkbakPage { constructor() { this.load(); + // ponytail: a fixed interval, polled while the page lives — it keeps refreshing in a background + // tab. Gate on `document.visibilityState` if the request volume ever matters. + interval(WERKBAK_REFRESH_MS) + .pipe(takeUntilDestroyed()) + .subscribe(() => this.load({ background: true })); } - load(): void { - this.loading.set(true); - this.failed.set(false); + /** + * Read the werkbak. A `background` read is the interval refresh: it leaves the rows and the states + * the behandelaar is looking at alone until it has an answer — no loading flash on every tick, and + * a blip does not swap the list for the failure alert (the next tick recovers). Only a foreground + * read — on open, or after a decision — speaks for whether the werkbak is readable at all. + */ + load(options: { background?: boolean } = {}): void { + const background = options.background ?? false; + if (!background) { + this.loading.set(true); + this.failed.set(false); + } this.bff.getBehandelWerkbak().subscribe({ next: (rows: WerkbakItem[]) => { this.items.set(rows); @@ -46,6 +67,7 @@ export class WerkbakPage { }, // Surface the failure (e.g. 403 for a non-behandelaar) instead of swallowing it. error: () => { + if (background) return; this.items.set([]); this.loading.set(false); this.loaded.set(true);