refactor: strip WP-/RB- ticket refs from apps and libs (RD-18)

204 WP-NN/RB-NN comments named a closed ticket instead of the code they
sit next to. git blame already records history and stays correct when
code moves; the comment does not. This sweep removes the reference and
keeps the sentence, across 95 files in apps/ and libs/ plus the
behaviour-spec generator's header text.

Eleven references stay: five story files justify an a11y disable per
the README's rule, and one line in a11y.mdx documents that convention.
Two sentences needed a rewrite, not a deletion, so the reference's
meaning survives its removal. behaviour-spec.mdx is regenerated, not
hand-edited.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
eho
2026-09-04 21:23:07 +02:00
co-authored by Claude Sonnet 5
parent 3895588b9a
commit dd11eafe50
102 changed files with 361 additions and 197 deletions
@@ -16,7 +16,7 @@ export interface DebouncedSave {
}
/**
* The debounced-autosave timer shared by the editor stores (WP-31). It owns ONLY the timer
* The debounced-autosave timer shared by the editor stores. It owns ONLY the timer
* bookkeeping; the actual write + save-state transitions live in the caller's `flush`
* (store-specific — it touches that store's SaveState + adapter). The handle is
* nulled the moment it fires, so `hasPendingSave()` means "a write is still owed". Integrates
@@ -10,7 +10,7 @@ type Err = Error | undefined;
const SET_FAILED = $localize`:@@flags.set.failed:De functievlag kon niet worden opgeslagen.`;
/**
* Runtime feature-flag state (WP-47) — one root singleton, mirroring `AccessStore`. Loads the
* Runtime feature-flag state — one root singleton, mirroring `AccessStore`. Loads the
* resolved flag set once from `GET /flags`; `enabled(key)` gates a feature (deny-by-default:
* false until loaded / unknown key). `set()` is the admin toggle (PUT + reload). The catalog is
* server-owned; the FE only mirrors + renders it.
+2 -2
View File
@@ -18,8 +18,8 @@ export interface History<T> {
* restore a returned snapshot by re-dispatching a `Seed`-style Msg — this helper only
* shuffles references, it never mutates them, so the caller must hold copy-on-write state
* (every edit produces a fresh value). Both stacks are capped so a long session can't grow
* unbounded. Extracted from BriefStore's WP-27 undo/redo (WP-31); reused by the stamdata
* editor (WP-32).
* unbounded. Extracted from BriefStore's undo/redo; reused by the stamdata
* editor.
*/
export function createHistory<T>(cap = 50): History<T> {
const past = signal<readonly T[]>([]);
+1 -1
View File
@@ -31,7 +31,7 @@ export function fromResource<T>(
* Project an Elm-machine's load lifecycle onto `RemoteData`, for the `<app-async>` seam. The
* machine keeps owning its own domain lifecycle (draft/submitted/…); this is purely the
* Loading/Failed/Loaded → async mapping, which was byte-identical across BriefStore,
* OrgTemplateStore and StamdataStore (WP-31). A `RemoteData` constructor, not a sixth
* OrgTemplateStore and StamdataStore. A `RemoteData` constructor, not a sixth
* encoding — wrap the call in a `computed`.
*/
export function fromLoadLifecycle<
+1 -1
View File
@@ -7,7 +7,7 @@ import { currentIdempotencyKey } from '@shared/infrastructure/api-client.provide
// So calling it twice inside the same `fn` tells us, behaviourally, whether a key was
// minted for this call: two reads agreeing means one pending key was reused; two reads
// disagreeing means there was no pending key at all — each call fell back to its own
// random one. This is the seam RB-17 exists to keep separated, so it is asserted
// random one. This is a deliberate seam, kept separate so it can be asserted
// directly rather than via a mock (relative-import mocking is off-limits under this
// repo's Angular/vitest setup — see role.interceptor.spec.ts).
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* The letter workflow's acting role: drafter or approver for the two-person
* compose/review flow, admin for org-template management (WP-23, Brief v2).
* compose/review flow, admin for org-template management (Brief v2).
* A pure domain type (no framework, no reading mechanism) — the `?role=` reader and
* the X-Role header live in shared/infrastructure/role.ts. Consumers (brief.store,
* letter-composer) depend on this type, not on how the role is obtained.
@@ -1,7 +1,7 @@
import { describe, it, expect } from 'vitest';
import { stripDevParams } from './dev-params';
describe('stripDevParams (WP-37)', () => {
describe('stripDevParams', () => {
it('removes ?scenario and ?role so the stored dev value wins on reload', () => {
expect(stripDevParams('http://localhost:4200/dashboard?scenario=slow&role=admin')).toBe(
'http://localhost:4200/dashboard',
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Remove the dev-only `?scenario=` and `?role=` params from a URL (WP-37). Once the
* Remove the dev-only `?scenario=` and `?role=` params from a URL. Once the
* dev switcher (debug-state) has been used, sessionStorage is the authoritative source
* for both — `currentScenario()`/`currentRole()` read the URL FIRST, so a stale param
* left in the address bar would override the switcher on reload (the "stuck on slow"
@@ -4,7 +4,7 @@ import { ApiClient } from '@shared/infrastructure/api-client';
import { FeatureFlag } from '@shared/domain/feature-flag';
/**
* Infrastructure adapter for feature flags (WP-47): `GET /flags` (resolved set, drives FE gating)
* Infrastructure adapter for feature flags: `GET /flags` (resolved set, drives FE gating)
* and the admin `PUT /admin/flags/{key}`. The single place the ApiClient lives for flags; the
* store parses at the boundary.
*/
@@ -11,18 +11,18 @@ describe('parseMe (trust boundary)', () => {
expect(parseMe({ capabilities: [] })).toEqual({ ok: true, value: [] });
});
it('recognizes the admin org-template capability (WP-23)', () => {
it('recognizes the admin org-template capability', () => {
expect(parseMe({ capabilities: ['orgtemplate:edit'] })).toEqual({
ok: true,
value: ['orgtemplate:edit'],
});
});
// Regression: WP-66's `aanvraag:beoordelen` (behandelportal) shipped on the `Capability`
// Regression: `aanvraag:beoordelen` (behandelportal) shipped on the `Capability`
// type but was never added to this trust-boundary's runtime KNOWN list, so a real
// behandelaar's `/me` response had the capability silently dropped and the werkvoorraad
// page always denied — every `Capability` union member belongs in KNOWN too.
it('recognizes the behandelportal besluit capability (WP-66)', () => {
it('recognizes the behandelportal besluit capability', () => {
expect(parseMe({ capabilities: ['aanvraag:beoordelen'] })).toEqual({
ok: true,
value: ['aanvraag:beoordelen'],
@@ -41,7 +41,7 @@ describe('roleInterceptor', () => {
it.each([
'/api/v1/brief',
'/api/v1/admin/org-template',
'/api/v1/stamdata', // WP-29: the admin stamdata reads 403 without X-Role
'/api/v1/stamdata', // the admin stamdata reads 403 without X-Role
'/api/v1/stamdata/professions?peildatum=1999-01-01',
'/api/v1/me',
])('stamps X-Role on the role-aware endpoint %s', (url) => {
@@ -4,9 +4,9 @@ import { currentRole } from './role';
/**
* Dev-only: stamps role-aware requests with the current `?role=` as an `X-Role`
* header so the backend can enforce the drafter/approver/admin rules. Only the
* brief, org-template, stamdata and /me endpoints carry it (WP-23 widened the set
* /me must see the role or `AccessStore` could never learn a capability; WP-29 added
* /stamdata, whose admin-only reads 403 without it); everything else is untouched.
* brief, org-template, stamdata and /me endpoints carry it (the set was widened
* /me must see the role or `AccessStore` could never learn a capability; /stamdata was
* added later, since its admin-only reads 403 without it); everything else is untouched.
* A new admin-gated endpoint MUST be added here or its page silently 403s.
*/
const ROLE_AWARE = [
+1 -1
View File
@@ -40,7 +40,7 @@ export function currentRole(): Role {
return isRole(stored) ? stored : 'drafter';
}
/** Dev switcher entry point: persist the chosen role for the tab (WP-33). */
/** Dev switcher entry point: persist the chosen role for the tab. */
export function setRole(r: Role): void {
sessionStorage.setItem(STORAGE_KEY, r);
}
+1 -1
View File
@@ -39,7 +39,7 @@ export function currentScenario(): Scenario {
return isScenario(stored) ? stored : 'default';
}
/** Dev switcher entry point: persist the chosen scenario for the tab (WP-33). */
/** Dev switcher entry point: persist the chosen scenario for the tab. */
export function setScenario(s: Scenario): void {
sessionStorage.setItem(STORAGE_KEY, s);
}
@@ -42,7 +42,7 @@ describe('subjectInterceptor', () => {
expect(forward('/api/v1/registratie/concept').headers.get('X-Subject')).toBe('111222333');
});
it('keeps stamping later requests on the same tab after the query param is gone (WP-33-style stickiness)', () => {
it('keeps stamping later requests on the same tab after the query param is gone (sticky per tab)', () => {
window.history.replaceState({}, '', '/?subject=111222333');
forward('/api/v1/me');
window.history.replaceState({}, '', '/dashboard'); // navigation drops the query param
@@ -2,7 +2,7 @@ import { HttpInterceptorFn } from '@angular/common/http';
import { currentSubject } from './subject';
/**
* Dev-only (WP-74): stamps every API request with `X-Subject`, the BSN
* Dev-only: stamps every API request with `X-Subject`, the BSN
* `StubIdentityProvider` (backend) resolves the caller's `ZorgverlenerCaller` from —
* every owner-keyed store (`ApplicationStore`, `DocumentStore`, `BriefStore`) reads
* off that resolved identity, so this is the seam that lets e2e specs log in as
+1 -1
View File
@@ -8,7 +8,7 @@ import { isDevMode } from '@angular/core';
* comment) — so `subject.interceptor.ts` can't reach it without a layering
* violation (`libs/shared` may not depend on an app-local `auth` context). Instead a
* `?subject=<bsn>` query param, seen once on any navigation, is remembered for the
* tab in sessionStorage — the exact `?role=` trick `role.ts` already uses (WP-33).
* tab in sessionStorage — the exact `?role=` trick `role.ts` already uses.
*
* Two consumers read this, both dev/e2e-only: `subject.interceptor.ts` (every
* `HttpClient` request) and `letter-preview.adapter.ts` (`/brief/preview`'s
@@ -139,7 +139,7 @@ export class UploadAdapter {
});
xhr.open('POST', `${environment.apiBaseUrl}/api/v1/uploads`);
// WP-74: this XHR bypasses `HttpClient`'s `subjectInterceptor` (the same reason
// This XHR bypasses `HttpClient`'s `subjectInterceptor` (the same reason
// `letter-preview.adapter.ts` sets `X-Role` explicitly) — without `X-Subject` a
// document always uploaded under `DocumentStore.DemoOwner` regardless of who was
// actually logged in, so a submission attempted under any other BSN would find
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* PII masking — pure functional core (WP-40). Data-minimisation helpers shared by the app
* PII masking — pure functional core. Data-minimisation helpers shared by the app
* (dev state panel, the masked-value atom, anywhere sensitive data is shown). No framework,
* no domain imports. The backend keeps a `MaskTail` twin in sync (see Program.cs).
*/
@@ -9,7 +9,7 @@ const meta: Meta<ShellComponent> = {
title: 'Design System/Templates/Shell',
component: ShellComponent,
// The persistent header injects AccessStore (for its capability-gated admin links) and
// FeatureFlagStore (WP-47, for the Inschrijven nav gate); stub both so the story needs no
// FeatureFlagStore (for the Inschrijven nav gate); stub both so the story needs no
// HTTP/ApiClient. `can` false → no admin links; `enabled` true → Inschrijven stays visible.
decorators: [
applicationConfig({
@@ -4,7 +4,7 @@ import { Capability } from '@shared/domain/capability';
export interface HeaderNavItem {
readonly label: string;
readonly to: string;
/** Hidden when this feature flag is off (e.g. WP-47's Inschrijven gate). Omit for an
/** Hidden when this feature flag is off (e.g. the Inschrijven gate). Omit for an
always-visible item. */
readonly flag?: string;
}
@@ -98,7 +98,7 @@ export class SiteHeaderComponent {
private rawNavItems = inject(HEADER_NAV_ITEMS);
private rawAdminLinks = inject(HEADER_ADMIN_LINKS);
/** Hides an item whose `flag` is off (e.g. the SSP's Inschrijven gate, WP-47) — which
/** Hides an item whose `flag` is off (e.g. the SSP's Inschrijven gate) — which
items exist, and which carry a flag, is entirely up to the app that provided them. */
protected readonly navItems = computed(() =>
this.rawNavItems.filter((i) => !i.flag || this.flags.enabled(i.flag)),
@@ -8,7 +8,7 @@ import { HEADER_ADMIN_LINKS, HEADER_NAV_ITEMS } from './nav-config';
import { SiteHeaderComponent } from './site-header.component';
// The header injects AccessStore for the capability-gated admin links and FeatureFlagStore
// (WP-47, for the Inschrijven nav gate); stub both so the story needs no HTTP/ApiClient.
// (for the Inschrijven nav gate); stub both so the story needs no HTTP/ApiClient.
// `can` decides which admin links appear; `enabled` true keeps Inschrijven visible. Nav/admin
// links are app-provided (HEADER_NAV_ITEMS/HEADER_ADMIN_LINKS) — this story supplies a
// representative sample rather than importing a real app's config, keeping the story
+1 -1
View File
@@ -13,7 +13,7 @@ const meta: Meta<AlertComponent> = {
export default meta;
type Story = StoryObj<AlertComponent>;
// role assertions guard the polite/assertive split (WP-16): errors interrupt, others don't.
// role assertions guard the polite/assertive split: errors interrupt, others don't.
export const Info: Story = {
args: { type: 'info' },
play: async ({ canvasElement }) => {
@@ -6,7 +6,7 @@ import { HeadingComponent } from '@shared/ui/heading/heading.component';
`.block-wrapper` panel with a `<dl>` of projected `<app-data-row>`s. Use `stacked`
(`.data-block--stacked`) when labels/values are long and should stack. This is the
single data surface (a generic white `app-card` used to exist but was unused and
removed — see WP-12); the datablock carries its own surface, so it is not nested in
removed); the datablock carries its own surface, so it is not nested in
another one. When there is no visible `heading`, pass an `ariaLabel` so the definition
list is announced. */
@Component({
@@ -24,8 +24,8 @@ type Story = StoryObj<FormFieldComponent>;
export const Default: Story = {
args: { label: 'BSN', fieldId: 'bsn', description: '9 cijfers', required: true },
// Composition contract: fieldId must equal the input's id — enforced here, not by DI
// (see WP-16). Catches drift in the description→aria-describedby wiring.
// Composition contract: fieldId must equal the input's id — enforced here, not by DI.
// Catches drift in the description→aria-describedby wiring.
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = canvas.getByRole('textbox');
@@ -3,7 +3,7 @@ import { ButtonComponent } from '@shared/ui/button/button.component';
/**
* Atom: a possibly-masked sensitive value (BSN, BIG-nummer, …) with an optional, audited
* reveal affordance (WP-40). The value arrives masked from the server (data-minimisation)
* reveal affordance. The value arrives masked from the server (data-minimisation)
* and is swapped for the full value on reveal; the reveal button shows only when the value
* is still masked AND the caller says the principal may reveal it. Centralises the
* masked-detection that consumers used to sniff inline. The atom only emits `reveal`; the
@@ -16,7 +16,7 @@ export interface PlaceholderOption {
// CIBG-GAP EXTENSION: Tekstgebied — CIBG has no rich-text/WYSIWYG pattern (a
// contenteditable editor with formatting + placeholder chips); hand-rolled
// surface (toolbar + chip styling), see cibg-gaps.mdx. Buttons still use the
// vendored .btn-ghost class (WP-10).
// vendored .btn-ghost class.
/**
* Molecule: a minimal no-dependency WYSIWYG editor over a `RichTextBlock`.
*