From fe9e3121c72da79ff99c2ceee07938482116cf97 Mon Sep 17 00:00:00 2001 From: Edwin van den Houdt Date: Thu, 23 Jul 2026 13:34:01 +0200 Subject: [PATCH] =?UTF-8?q?fix(dev):=20WP-37=20=E2=80=94=20dev-switcher=20?= =?UTF-8?q?resets=20scenario/role=20instead=20of=20sticking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit currentScenario()/currentRole() read the URL param before sessionStorage, so a stale ?scenario=/?role= in the address bar overrode the switcher on reload ("stuck on slow"). The switcher now strips both dev params from the URL (pure stripDevParams + history.replaceState) before reloading, so the stored value wins. Co-Authored-By: Claude Opus 4.8 --- .../backlog/WP-37-dev-switcher-reset.md | 31 +++++++++++++++++++ .../shared/infrastructure/dev-params.spec.ts | 22 +++++++++++++ src/app/shared/infrastructure/dev-params.ts | 14 +++++++++ .../ui/debug-state/debug-state.component.ts | 11 ++++++- 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 docs/project/backlog/WP-37-dev-switcher-reset.md create mode 100644 src/app/shared/infrastructure/dev-params.spec.ts create mode 100644 src/app/shared/infrastructure/dev-params.ts diff --git a/docs/project/backlog/WP-37-dev-switcher-reset.md b/docs/project/backlog/WP-37-dev-switcher-reset.md new file mode 100644 index 0000000..7208856 --- /dev/null +++ b/docs/project/backlog/WP-37-dev-switcher-reset.md @@ -0,0 +1,31 @@ +# WP-37 — Dev-switcher reset fix (scenario/role) + +Status: done +Phase: 8 — platform/DX/showcase + +## Why + +The WP-33 dev switcher can't reset scenario/role back to `default`/`drafter` — it gets "stuck" +(e.g. on `slow`). Cause: `currentScenario()`/`currentRole()` read the URL `?scenario=`/`?role=` +param **before** sessionStorage, so once a param is in the address bar, `location.reload()` (same +URL) re-reads the stale value and overrides what the switcher just stored. + +## Decisions + +- Once the switcher is used, **sessionStorage is authoritative**; a leftover URL param must not + win. Strip both dev params from the URL (`history.replaceState`) before reloading. +- Extract the URL rewrite as a **pure** `stripDevParams(href)` so it's unit-testable without + touching `location.reload()`. + +## Files + +- `src/app/shared/infrastructure/dev-params.ts` (+ `dev-params.spec.ts`) — pure `stripDevParams`. +- `src/app/shared/ui/debug-state/debug-state.component.ts` — `switchRole`/`switchScenario` call + `applyAndReload()` (replaceState with stripped URL, then reload). + +## Acceptance criteria + +- [x] Switching scenario/role to any value (incl. default/drafter) sticks after reload, even when + a `?scenario=`/`?role=` param was in the URL. +- [x] `stripDevParams` removes both params, keeps other params + path/hash (spec). +- [x] `npm run ci` green. diff --git a/src/app/shared/infrastructure/dev-params.spec.ts b/src/app/shared/infrastructure/dev-params.spec.ts new file mode 100644 index 0000000..820b743 --- /dev/null +++ b/src/app/shared/infrastructure/dev-params.spec.ts @@ -0,0 +1,22 @@ +import { describe, it, expect } from 'vitest'; +import { stripDevParams } from './dev-params'; + +describe('stripDevParams (WP-37)', () => { + 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', + ); + }); + + it('keeps unrelated query params and the path/hash', () => { + expect(stripDevParams('http://localhost:4200/beheer/zaken?scenario=error&tab=2#top')).toBe( + 'http://localhost:4200/beheer/zaken?tab=2#top', + ); + }); + + it('is a no-op when neither param is present', () => { + expect(stripDevParams('http://localhost:4200/dashboard')).toBe( + 'http://localhost:4200/dashboard', + ); + }); +}); diff --git a/src/app/shared/infrastructure/dev-params.ts b/src/app/shared/infrastructure/dev-params.ts new file mode 100644 index 0000000..10a88c1 --- /dev/null +++ b/src/app/shared/infrastructure/dev-params.ts @@ -0,0 +1,14 @@ +/** + * Remove the dev-only `?scenario=` and `?role=` params from a URL (WP-37). 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" + * bug). Stripping the params before reload lets the stored value win. Pure: returns the + * rewritten href, mutates nothing. + */ +export function stripDevParams(href: string): string { + const url = new URL(href); + url.searchParams.delete('scenario'); + url.searchParams.delete('role'); + return url.toString(); +} diff --git a/src/app/shared/ui/debug-state/debug-state.component.ts b/src/app/shared/ui/debug-state/debug-state.component.ts index f102065..474002f 100644 --- a/src/app/shared/ui/debug-state/debug-state.component.ts +++ b/src/app/shared/ui/debug-state/debug-state.component.ts @@ -7,6 +7,7 @@ import { map } from '@shared/application/remote-data'; import { Role } from '@shared/domain/role'; import { ROLES, currentRole, setRole } from '@shared/infrastructure/role'; import { Scenario, SCENARIOS, currentScenario, setScenario } from '@shared/infrastructure/scenario'; +import { stripDevParams } from '@shared/infrastructure/dev-params'; import { maskBsn, redactProfile } from './mask'; // CIBG-GAP EXTENSION: n/a — devtool, no corresponding CIBG concept; deliberately @@ -143,10 +144,18 @@ export class DebugStateComponent { switchRole(r: Role): void { setRole(r); - location.reload(); + this.applyAndReload(); } switchScenario(s: Scenario): void { setScenario(s); + this.applyAndReload(); + } + + // Strip the dev params from the URL before reloading (WP-37) so a stale ?scenario=/?role= + // in the address bar can't override the value the switcher just stored (currentScenario/ + // currentRole read the URL first) — otherwise a switch to "default"/"drafter" gets stuck. + private applyAndReload(): void { + history.replaceState({}, '', stripDevParams(window.location.href)); location.reload(); }