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:
@@ -6,7 +6,7 @@ import { AuditAdapter, parseAuditEntries } from '@beheer/infrastructure/audit.ad
|
||||
type Err = Error | undefined;
|
||||
|
||||
/**
|
||||
* Admin view of the persisted authz/PII-reveal audit trail (WP-41/42). One root singleton
|
||||
* Admin view of the persisted authz/PII-reveal audit trail. One root singleton
|
||||
* owning the list as a RemoteData signal, parsed at the trust boundary. Read-only.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
|
||||
@@ -44,7 +44,7 @@ function setup(blobPresenter?: BlobPresenter): StamdataStore {
|
||||
return TestBed.inject(StamdataStore);
|
||||
}
|
||||
|
||||
describe('StamdataStore undo/redo (WP-32)', () => {
|
||||
describe('StamdataStore undo/redo', () => {
|
||||
it('records a cell edit, undoes and redoes it', async () => {
|
||||
const store = setup();
|
||||
await store.load();
|
||||
@@ -82,10 +82,10 @@ describe('StamdataStore undo/redo (WP-32)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// --- RB-28 (TE-006): download() ends in BLOB_PRESENTER.download, not raw DOM calls,
|
||||
// --- TE-006: download() ends in BLOB_PRESENTER.download, not raw DOM calls,
|
||||
// so the seam makes both the guard's branches and the success path assertable. ---
|
||||
|
||||
describe('StamdataStore.download (RB-28)', () => {
|
||||
describe('StamdataStore.download', () => {
|
||||
it('does not call the presenter while the two-clause guard blocks (nothing dirty yet)', async () => {
|
||||
// Given a freshly loaded table with no edits — canDownload() is false.
|
||||
const { presenter, downloaded } = fakeBlobPresenter();
|
||||
|
||||
@@ -99,7 +99,7 @@ export class StamdataStore {
|
||||
this.previewDate.set(date);
|
||||
}
|
||||
|
||||
/** Undo/redo over the edited rows (WP-32): the document snapshot is `rows`; restore via
|
||||
/** Undo/redo over the edited rows: the document snapshot is `rows`; restore via
|
||||
the existing `Seed` msg. Only real edits are recorded (a no-op reduce leaves no step). */
|
||||
private history = createHistory<readonly StamRow[]>(50);
|
||||
readonly canUndo = this.history.canUndo;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** One authz/PII-reveal audit row as the FE sees it (WP-41 backend → WP-42 view). Pure
|
||||
/** One authz/PII-reveal audit row as the FE sees it. Pure
|
||||
type; data-minimised (no PII) by construction on the server. */
|
||||
export interface AuditEntry {
|
||||
at: string; // ISO timestamp
|
||||
|
||||
@@ -5,8 +5,8 @@ import type { AuthzAuditDto } from '@shared/infrastructure/api-client';
|
||||
import { AuditEntry } from '@beheer/domain/audit-entry';
|
||||
|
||||
/**
|
||||
* Infrastructure adapter for the admin authz/PII-reveal audit trail (`GET /admin/audit`,
|
||||
* WP-41). The single place the ApiClient lives for audit; the store parses at the boundary.
|
||||
* Infrastructure adapter for the admin authz/PII-reveal audit trail (`GET /admin/audit`).
|
||||
* The single place the ApiClient lives for audit; the store parses at the boundary.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AuditAdapter {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { successOr } from '@shared/application/remote-data';
|
||||
import { AuditStore } from '@beheer/application/audit.store';
|
||||
|
||||
/**
|
||||
* Admin page: the persisted authz/PII-reveal audit trail (WP-41/42) — data-minimised, no PII.
|
||||
* Admin page: the persisted authz/PII-reveal audit trail — data-minimised, no PII.
|
||||
* Deny-by-default capability gate (`cases:manage`, reused for admin audit read). Read-only table.
|
||||
*/
|
||||
@Component({
|
||||
|
||||
@@ -7,7 +7,7 @@ import { AccessStore } from '@shared/application/access.store';
|
||||
import { FeatureFlagStore } from '@shared/application/feature-flags.store';
|
||||
|
||||
/**
|
||||
* Admin page: toggle runtime feature flags (WP-47). Deny-by-default capability gate
|
||||
* Admin page: toggle runtime feature flags. Deny-by-default capability gate
|
||||
* (`flags:manage`). The catalog is server-owned (code); this only flips the on/off state, which
|
||||
* the whole app reads via the same `FeatureFlagStore`.
|
||||
*/
|
||||
|
||||
@@ -242,13 +242,13 @@ export class StamdataTableEditorComponent {
|
||||
protected expireLabel = $localize`:@@beheer.expire:Sluiten per vandaag`;
|
||||
private removeConfirm = $localize`:@@beheer.removeConfirm:Rij verwijderen? Als andere gegevens ernaar verwijzen, faalt de build-controle (CI). Bij een tabel met een geldigheidsperiode kunt u de rij beter sluiten (geldig tot) in plaats van verwijderen.`;
|
||||
|
||||
/** Deletions can orphan a reference (the CI gate catches it); confirm first (WP-48). */
|
||||
/** Deletions can orphan a reference (the CI gate catches it); confirm first. */
|
||||
protected onRemove(index: number) {
|
||||
if (confirm(this.removeConfirm)) this.rowRemoved.emit(index);
|
||||
}
|
||||
|
||||
/** Steer temporal tables toward expiring (close the validity per today) over hard delete —
|
||||
preserves history and can't orphan a reference that was valid earlier (WP-48). */
|
||||
preserves history and can't orphan a reference that was valid earlier. */
|
||||
protected onExpire(index: number) {
|
||||
const col = this.table().columns.find((c) => /geldigtot/i.test(c.name));
|
||||
if (col) this.cellEdited.emit({ row: index, column: col.name, value: this.today });
|
||||
|
||||
@@ -79,7 +79,7 @@ export class StamdataPage {
|
||||
constructor() {
|
||||
// Load once the capability resolves to `allowed` (a 403 GET would be wasted otherwise).
|
||||
// Depends only on canEdit() + a plain flag — never on the store model, so dispatching
|
||||
// `Loading` inside load() can't retrigger this effect (the WP-26 runaway-loop lesson).
|
||||
// `Loading` inside load() can't retrigger this effect (the runaway-loop lesson).
|
||||
effect(() => {
|
||||
if (this.canEdit() && !this.loadRequested) {
|
||||
this.loadRequested = true;
|
||||
@@ -92,7 +92,7 @@ export class StamdataPage {
|
||||
void this.store.load();
|
||||
}
|
||||
|
||||
/** Ctrl/Cmd+Z undo, Ctrl/Cmd+Shift+Z redo (WP-32). Ignored while focus is in a grid
|
||||
/** Ctrl/Cmd+Z undo, Ctrl/Cmd+Shift+Z redo. Ignored while focus is in a grid
|
||||
cell input so the browser's native text-undo still works there (mirrors brief.page). */
|
||||
protected onKeydown(e: KeyboardEvent) {
|
||||
if (!this.canEdit() || !(e.ctrlKey || e.metaKey) || (e.key !== 'z' && e.key !== 'Z')) return;
|
||||
|
||||
@@ -11,13 +11,13 @@ what the ones below/above it can't.
|
||||
|
||||
## The layers
|
||||
|
||||
1. **Axe on every story** (WP-01) — `@storybook/addon-a11y` in the panel, plus
|
||||
1. **Axe on every story** — `@storybook/addon-a11y` in the panel, plus
|
||||
`@storybook/test-runner` + `axe-playwright` gating CI (`npm run test-storybook:ci`).
|
||||
Catches structural/contrast/ARIA-shape violations on every component, automatically,
|
||||
as soon as a story exists. Escape hatch: `parameters: { a11y: { disable: true } }`,
|
||||
only with an inline justification comment + a cross-reference to the WP that will fix
|
||||
it (see e.g. `task-list.stories.ts`).
|
||||
2. **Template a11y lint** (WP-17) — `angular-eslint`'s `templateAccessibility` config
|
||||
2. **Template a11y lint** — `angular-eslint`'s `templateAccessibility` config
|
||||
(`alt-text`, `label-has-associated-control`, `click`/`mouse-events-have-key-events`,
|
||||
`interactive-supports-focus`, `valid-aria`, `no-autofocus`, …) running on every inline
|
||||
template via `angular.processInlineTemplates` (this repo has no `.html` files — every
|
||||
@@ -25,7 +25,7 @@ what the ones below/above it can't.
|
||||
into a virtual file the template rules can lint). Catches missing alt text, unlabelled
|
||||
controls, and interactive elements that can't be reached by keyboard — at lint time,
|
||||
before a story even exists.
|
||||
3. **Play tests** (WP-16) — Storybook stories assert the wiring axe/lint can't see:
|
||||
3. **Play tests** — Storybook stories assert the wiring axe/lint can't see:
|
||||
`form-field.stories.ts`'s canonical composition asserts `aria-describedby` joins
|
||||
`-desc`/`-error` in the right order; `alert.stories.ts` asserts `role="alert"` for
|
||||
errors vs `role="status"` for info/ok/warning. These run as part of the same
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{/* GENERATED by `npm run gen:behaviour-spec` (scripts/gen-behaviour-spec.mjs) — do not
|
||||
edit. Every bullet below is a real `it()` title or backend test method name, extracted
|
||||
verbatim from the suite. The team rejected Cucumber/Gherkin for BDD scenarios (runtime string
|
||||
matching undoes the compile-time guarantees WP-70 bought, and needs two frameworks for
|
||||
matching undoes the compile-time guarantees the TypeScript compiler bought, and needs two frameworks for
|
||||
.NET+TS) — this page is the replacement: business-readable documentation generated FROM test
|
||||
names, so it can never drift from what the suite actually asserts. A test name changing (or a
|
||||
test being added/removed) is the only way this page changes; hand-editing it is pointless,
|
||||
@@ -108,13 +108,13 @@ classes.
|
||||
|
||||
### beheer
|
||||
|
||||
#### StamdataStore undo/redo (WP-32)
|
||||
#### StamdataStore undo/redo
|
||||
|
||||
- records a cell edit, undoes and redoes it
|
||||
- records addRow and undoes it
|
||||
- clears history when switching table
|
||||
|
||||
#### StamdataStore.download (RB-28)
|
||||
#### StamdataStore.download
|
||||
|
||||
- does not call the presenter while the two-clause guard blocks (nothing dirty yet)
|
||||
- does not call the presenter while previewing a date, even with edits
|
||||
@@ -186,14 +186,14 @@ classes.
|
||||
- flushes a pending debounced edit immediately and clears the pending flag
|
||||
- is a no-op when no edit is pending
|
||||
|
||||
#### BriefStore.load — 404 tolerance (RB-22)
|
||||
#### BriefStore.load — 404 tolerance
|
||||
|
||||
- a 404 drives exactly one reset(), which populates the store
|
||||
- a second 404 does not drive a second reset()
|
||||
|
||||
#### BriefStore.previewLetter
|
||||
|
||||
- opens the composed letter via BLOB_PRESENTER on success (RB-28)
|
||||
- opens the composed letter via BLOB_PRESENTER on success
|
||||
- surfaces the error without opening a tab on failure
|
||||
|
||||
#### BriefStore.revealBigNummer (PRD-0002 §5c)
|
||||
@@ -206,7 +206,7 @@ classes.
|
||||
- sends no X-Role/X-Subject headers outside isDevMode()
|
||||
- sends X-Role (and X-Subject when known) under isDevMode()
|
||||
|
||||
#### OrgTemplateStore.proefbrief (RB-28)
|
||||
#### OrgTemplateStore.proefbrief
|
||||
|
||||
- opens the rendered proefbrief via BLOB_PRESENTER on success
|
||||
- surfaces the error without opening a tab on failure
|
||||
@@ -372,7 +372,7 @@ classes.
|
||||
- low uren requires the scholing question, and punten only once scholing is followed
|
||||
- buitenland gewerkt requires land and hours abroad before advancing
|
||||
- gaNaarStap corrects an earlier answer without losing later ones
|
||||
- raising uren above the threshold after answering scholing drops both fields (WP-69 §6)
|
||||
- raising uren above the threshold after answering scholing drops both fields
|
||||
- SetPolicy (server-owned threshold) can turn an already-answered uren into one that now requires scholing
|
||||
|
||||
#### intake hasProgress
|
||||
@@ -419,8 +419,8 @@ classes.
|
||||
- reaches Submitting ONLY with valid answers
|
||||
- punten is required only when aanvullende scholing was gevolgd
|
||||
- low hours requires the scholing answer before submit
|
||||
- does not require punten for a hidden question (WP-69 §6)
|
||||
- drops punten when raising uren hides the question (WP-69 §6)
|
||||
- does not require punten for a hidden question
|
||||
- drops punten when raising uren hides the question
|
||||
- resolve maps Submitting to Submitted on a successful submit
|
||||
- resolve maps Submitting to Failed on a failed submit
|
||||
|
||||
@@ -501,7 +501,7 @@ classes.
|
||||
|
||||
- resolves ok with the server response on success
|
||||
- folds a rejected submit into a Result error, never throwing
|
||||
- recovers from a create conflict by adopting the existing Concept (WP-35)
|
||||
- recovers from a create conflict by adopting the existing Concept
|
||||
|
||||
#### createSubmitChangeRequest
|
||||
|
||||
@@ -863,8 +863,8 @@ classes.
|
||||
|
||||
- parses a known capability list
|
||||
- parses an empty list (drafter — no capabilities)
|
||||
- recognizes the admin org-template capability (WP-23)
|
||||
- recognizes the behandelportal besluit capability (WP-66)
|
||||
- recognizes the admin org-template capability
|
||||
- recognizes the behandelportal besluit capability
|
||||
- drops unrecognized capability strings instead of rejecting the response
|
||||
- rejects malformed responses instead of trusting them
|
||||
|
||||
@@ -950,7 +950,7 @@ classes.
|
||||
- falls back to default when nothing is set or the value is invalid
|
||||
- setScenario persists the chosen scenario
|
||||
|
||||
#### stripDevParams (WP-37)
|
||||
#### stripDevParams
|
||||
|
||||
- removes ?scenario and ?role so the stored dev value wins on reload
|
||||
- keeps unrelated query params and the path/hash
|
||||
@@ -959,7 +959,7 @@ classes.
|
||||
#### subjectInterceptor
|
||||
|
||||
- stamps X-Subject on an /api/v1/ request once ?subject= has been seen
|
||||
- keeps stamping later requests on the same tab after the query param is gone (WP-33-style stickiness)
|
||||
- keeps stamping later requests on the same tab after the query param is gone (sticky per tab)
|
||||
- leaves a non-API request untouched even when a subject is known
|
||||
- sends no header at all when no subject has ever been seen
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ placed above the `@Component` decorator, plus `parameters: { cibgGap: true }` an
|
||||
| --------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `skeleton` | Laadindicatie | No loading-skeleton class in the vendored build. |
|
||||
| `spinner` | Laadindicatie | No loading-spinner class in the vendored build. |
|
||||
| `rich-text-editor` | Tekstgebied | No rich-text/WYSIWYG pattern; toolbar buttons still use vendored `.btn-ghost` (WP-10). |
|
||||
| `rich-text-editor` | Tekstgebied | No rich-text/WYSIWYG pattern; toolbar buttons still use vendored `.btn-ghost`. |
|
||||
| `wizard-shell` (error summary only) | Foutmelding | No error-summary/Veldvalidatie list class; renders inside a vendored `.feedback-error` alert. |
|
||||
| `application-link` (non-navigating row) | Aanvragen | The vendored `.dashboard-block.applications li a` chain only styles `<a>`; `.static-row` mirrors it from tokens for the informational (non-link) case. |
|
||||
| `debug-state` | n/a | Dev-only tool, deliberately off-theme — see the component's own `ponytail:` note. |
|
||||
@@ -36,7 +36,7 @@ placed above the `@Component` decorator, plus `parameters: { cibgGap: true }` an
|
||||
Not a gap: `confirmation` renders entirely with vendored `.confirmation*` classes (no `styles:
|
||||
[...]` block) — its header comment names the pattern, no marker needed. The `upload/` suite
|
||||
renders entirely with vendored classes (`.file-picker-drop-area`, `.btn-upload`, …) — reworked
|
||||
onto them rather than marked (see WP-11's correction note). `task-list`, `application-list`, and
|
||||
onto them rather than marked. `task-list`, `application-list`, and
|
||||
`choice-list` each wrap a distinct vendored pattern (Keuzelijst / Aanvragen / Keuzelijst) and name
|
||||
it in their own header comment — no marker needed, they don't hand-roll surface CSS.
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ context, so the per-context scoping rule never applies to it in the first place.
|
||||
|
||||
`npm run lint` (`eslint.config.mjs`) is a separate gate — mainly the `any`-free rule —
|
||||
and no longer carries the import-boundary rules above (moved to dependency-cruiser,
|
||||
WP-38/WP-67, so they don't have to be hand-copied per context).
|
||||
so they don't have to be hand-copied per context).
|
||||
|
||||
## The English/Dutch seam
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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[]>([]);
|
||||
|
||||
@@ -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<
|
||||
|
||||
@@ -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,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,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 = [
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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`.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user