Add dev-only state debug view (Elm-style "show the Model")
A floating, read-only panel that renders the current root-store state (SessionStore + BigProfileStore) live via the json pipe. Whole thing is gated by isDevMode() so it never renders or ships in production. - Observer only — no new store/library/state pattern (PRD prime directive). - BigProfileStore resolved lazily on first open; its httpResources fetch eagerly on construction, so we avoid a personal-data fetch on pages that don't use it. - bsn masked before render; no persistence/logging/network in the feature. - maskBsn has a unit spec; UI exercised via a Devtools Storybook story, per repo convention (no TestBed component tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { Component, Injector, computed, inject, isDevMode, signal } from '@angular/core';
|
||||
import { JsonPipe } from '@angular/common';
|
||||
import { SessionStore } from '@auth/application/session.store';
|
||||
import { Session } from '@auth/domain/session';
|
||||
import { BigProfileStore } from '@registratie/application/big-profile.store';
|
||||
import { maskBsn } from './mask';
|
||||
|
||||
/**
|
||||
* Dev-only "show the current Model" panel (Elm-debugger style, read-only).
|
||||
* Observes the root singletons and renders them via the json pipe. Never a
|
||||
* product feature: the whole template is gated by isDevMode(), so it does not
|
||||
* render and is unreachable in a production build.
|
||||
*
|
||||
* ponytail: dev tool — intentionally off-theme (not Rijkshuisstijl) and exempt
|
||||
* from the design-token convention; plain values keep it visually distinct.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-debug-state',
|
||||
imports: [JsonPipe],
|
||||
styles: [`
|
||||
.fab{position:fixed;bottom:1rem;right:1rem;z-index:9999;font:600 12px/1 monospace;
|
||||
background:#1e1e1e;color:#9cdcfe;border:1px solid #444;border-radius:4px;
|
||||
padding:.5rem .75rem;cursor:pointer}
|
||||
.panel{position:fixed;bottom:3.25rem;right:1rem;z-index:9999;width:min(90vw,28rem);
|
||||
max-height:70vh;overflow:auto;background:#1e1e1e;color:#d4d4d4;border:1px solid #444;
|
||||
border-radius:6px;box-shadow:0 6px 24px rgba(0,0,0,.4)}
|
||||
.panel pre{margin:0;padding:.75rem;font:12px/1.5 monospace;white-space:pre-wrap;
|
||||
word-break:break-word}
|
||||
`],
|
||||
template: `
|
||||
@if (isDev) {
|
||||
<button type="button" class="fab" (click)="toggle()">
|
||||
{{ visible() ? '× state' : '⚙ state' }}
|
||||
</button>
|
||||
@if (visible()) {
|
||||
<div class="panel"><pre>{{ snapshot() | json }}</pre></div>
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
export class DebugStateComponent {
|
||||
protected readonly isDev = isDevMode();
|
||||
protected readonly visible = signal(false);
|
||||
|
||||
private session = inject(SessionStore);
|
||||
private injector = inject(Injector);
|
||||
// Resolved on first open only — BigProfileStore's httpResources fetch eagerly
|
||||
// on construction, so we must not instantiate it until the dev asks to look.
|
||||
private profileStore?: BigProfileStore;
|
||||
|
||||
protected readonly snapshot = computed(() => ({
|
||||
session: maskSession(this.session.session()),
|
||||
profile: this.profileStore?.profile(),
|
||||
decisions: this.profileStore?.decisions(),
|
||||
aantekeningen: this.profileStore?.aantekeningen(),
|
||||
pendingHerregistratie: this.profileStore?.pendingHerregistratie(),
|
||||
}));
|
||||
|
||||
toggle(): void {
|
||||
if (!this.visible()) this.profileStore ??= this.injector.get(BigProfileStore);
|
||||
this.visible.update((v) => !v);
|
||||
}
|
||||
}
|
||||
|
||||
function maskSession(s: Session | null): Session | null {
|
||||
return s ? { ...s, bsn: maskBsn(s.bsn) } : null;
|
||||
}
|
||||
Reference in New Issue
Block a user