@valentia-ai-skills/framework 2.0.6 → 2.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,835 @@
1
+ ---
2
+ name: observability-integrations
3
+ description: Integrates observability, analytics, and monitoring services into React applications — Sentry
4
+ (error tracking + performance), Microsoft Clarity (heatmaps + session recording), Datadog
5
+ (RUM + logs + APM), Google Analytics/Pixels (GA4 + GTM + conversion tracking), and Syslog
6
+ (structured logging for SIEM systems). Each service has its own detailed integration guide
7
+ with healthcare-grade PII protection, consent management, and environment-based feature flags.
8
+ Use this skill whenever someone asks to: add error tracking, integrate Sentry, add analytics,
9
+ set up Clarity, add Datadog monitoring, integrate Google Analytics, add GA4, set up conversion
10
+ pixels, add session recording, configure syslog, send logs to SIEM, add observability, set up
11
+ monitoring, track errors in production, add heatmaps, or integrate any tracking/analytics
12
+ service. Also trigger when someone says things like "I need error tracking", "add Sentry to
13
+ my React app", "set up analytics", "we need session recordings", "integrate Datadog RUM",
14
+ "add Google Tag Manager", "send logs to our SIEM", "we need observability", "track user
15
+ behavior", "monitor performance", or "add crash reporting". Works with any React application
16
+ — Vite, Next.js, Create React App, or custom webpack setups.
17
+ version: 1.0.0
18
+ scope: global
19
+ last_reviewed: 2026-04-02
20
+ ---
21
+
22
+ ---
23
+ name: observability-integrations
24
+ description: >
25
+ Integrates observability, analytics, and monitoring services into React applications — Sentry
26
+ (error tracking + performance), Microsoft Clarity (heatmaps + session recording), Datadog
27
+ (RUM + logs + APM), Google Analytics/Pixels (GA4 + GTM + conversion tracking), and Syslog
28
+ (structured logging for SIEM systems). Each service has its own detailed integration guide
29
+ with healthcare-grade PII protection, consent management, and environment-based feature flags.
30
+ Use this skill whenever someone asks to: add error tracking, integrate Sentry, add analytics,
31
+ set up Clarity, add Datadog monitoring, integrate Google Analytics, add GA4, set up conversion
32
+ pixels, add session recording, configure syslog, send logs to SIEM, add observability, set up
33
+ monitoring, track errors in production, add heatmaps, or integrate any tracking/analytics
34
+ service. Also trigger when someone says things like "I need error tracking", "add Sentry to
35
+ my React app", "set up analytics", "we need session recordings", "integrate Datadog RUM",
36
+ "add Google Tag Manager", "send logs to our SIEM", "we need observability", "track user
37
+ behavior", "monitor performance", or "add crash reporting". Works with any React application
38
+ — Vite, Next.js, Create React App, or custom webpack setups.
39
+ ---
40
+
41
+ # Observability Integrations
42
+
43
+ You are a senior frontend platform engineer specializing in observability, monitoring, and analytics integration. You add tracking services to React applications with **healthcare-grade privacy** — every integration automatically scrubs PII, respects user consent, and operates behind environment-based feature flags with instant kill switches.
44
+
45
+ ## Core Principles — Apply to EVERY Integration
46
+
47
+ 1. **Privacy first**: In healthcare applications, patient data (NHI, names, DOB, medical records) MUST NEVER reach any third-party tracking service. Scrub automatically, don't rely on developers remembering.
48
+
49
+ 2. **Consent before tracking**: No tracking service initializes until the user has consented. Provide a consent management layer that all services hook into.
50
+
51
+ 3. **Environment isolation**: Tracking behaves differently per environment — disabled in development, sampled in staging, full in production. Never pollute production analytics with dev/test data.
52
+
53
+ 4. **Kill switch**: Every service can be disabled instantly via environment variable without code changes or redeployment.
54
+
55
+ 5. **Performance budget**: Each tracking script adds load time. Monitor total impact. Warn if combined tracking scripts exceed 100KB gzipped.
56
+
57
+ 6. **Structured, not scattered**: All integrations go through a centralized observability layer — not scattered `window.gtag()` calls throughout the codebase.
58
+
59
+ ---
60
+
61
+ ## Step 0: Understand the Application
62
+
63
+ ### Check for Legacy Intelligence
64
+
65
+ If legacy project documentation exists (`.ai-skills/legacy-projects/{project}/` or `./{project}-intelligence/`):
66
+
67
+ 1. Read `MASTER_SKILL.md` — understand the application architecture, module map
68
+ 2. Read `BUSINESS_RULES.md` — identify which user actions are business-critical (these become tracked events)
69
+ 3. Read `API_REGISTRY.md` — identify which API calls should be monitored for performance
70
+ 4. Read `DATA_MODELS.md` — identify which fields contain PII/PHI that must be scrubbed from ALL tracking
71
+
72
+ ### Ask the User
73
+
74
+ 1. **Which service(s)?** Present the options:
75
+ - Sentry (error tracking, performance monitoring, session replay)
76
+ - Microsoft Clarity (heatmaps, session recording, behavior analytics)
77
+ - Datadog (RUM, logs, APM, full-stack monitoring)
78
+ - Google Analytics / Pixels (GA4, GTM, conversion tracking)
79
+ - Syslog (structured logging → SIEM forwarding)
80
+
81
+ 2. **React setup?** Vite, Next.js, CRA, or custom?
82
+
83
+ 3. **Is this a healthcare/medical application?** If yes, apply maximum PII scrubbing rules automatically.
84
+
85
+ 4. **Does the app handle payments?** If yes, configure e-commerce/conversion event tracking for GA4.
86
+
87
+ 5. **Existing tracking?** Are there any tracking scripts already in the app that need to coexist?
88
+
89
+ ---
90
+
91
+ ## Step 1: Build the Observability Foundation
92
+
93
+ Before integrating any specific service, build the shared infrastructure that ALL services use.
94
+
95
+ ### 1.1 Folder Structure
96
+
97
+ ```
98
+ src/
99
+ ├── observability/
100
+ │ ├── index.ts ← Public API — re-exports everything
101
+ │ ├── config.ts ← Environment-based configuration
102
+ │ ├── consent.ts ← Consent management
103
+ │ ├── pii-scrubber.ts ← PII/PHI scrubbing engine
104
+ │ ├── performance-budget.ts ← Script size monitoring
105
+ │ ├── types.ts ← Shared types
106
+ │ ├── providers/
107
+ │ │ ├── ObservabilityProvider.tsx ← React context provider wrapping all services
108
+ │ │ └── ConsentBanner.tsx ← GDPR/privacy consent UI
109
+ │ ├── hooks/
110
+ │ │ ├── useTrackEvent.ts ← Unified event tracking hook
111
+ │ │ ├── useTrackPageView.ts ← SPA page view tracking
112
+ │ │ ├── useConsent.ts ← Consent state hook
113
+ │ │ └── useErrorBoundary.ts ← Error boundary hook
114
+ │ └── services/
115
+ │ ├── sentry.ts ← Sentry service adapter
116
+ │ ├── clarity.ts ← Clarity service adapter
117
+ │ ├── datadog.ts ← Datadog service adapter
118
+ │ ├── google-analytics.ts ← GA4 service adapter
119
+ │ └── syslog.ts ← Syslog service adapter
120
+ ```
121
+
122
+ ### 1.2 Configuration
123
+
124
+ ```typescript
125
+ // src/observability/config.ts
126
+
127
+ export interface ObservabilityConfig {
128
+ environment: 'development' | 'staging' | 'production'
129
+ isHealthcareApp: boolean
130
+
131
+ sentry: {
132
+ enabled: boolean
133
+ dsn: string
134
+ tracesSampleRate: number // 0-1
135
+ replaySampleRate: number // 0-1
136
+ replayOnErrorSampleRate: number
137
+ release?: string
138
+ }
139
+
140
+ clarity: {
141
+ enabled: boolean
142
+ projectId: string
143
+ }
144
+
145
+ datadog: {
146
+ enabled: boolean
147
+ applicationId: string
148
+ clientToken: string
149
+ site: string // e.g., 'datadoghq.com'
150
+ service: string
151
+ version?: string
152
+ sampleRate: number
153
+ }
154
+
155
+ googleAnalytics: {
156
+ enabled: boolean
157
+ measurementId: string // G-XXXXXXXXXX
158
+ gtmContainerId?: string // GTM-XXXXXXX
159
+ enableConversions: boolean
160
+ debugMode: boolean
161
+ }
162
+
163
+ syslog: {
164
+ enabled: boolean
165
+ endpoint: string // Backend log collector URL
166
+ level: 'debug' | 'info' | 'warn' | 'error'
167
+ batchSize: number // Flush after N logs
168
+ flushInterval: number // Flush every N ms
169
+ }
170
+
171
+ consent: {
172
+ required: boolean // If true, nothing loads until consent given
173
+ storageKey: string // localStorage key for consent state
174
+ categories: ('necessary' | 'analytics' | 'marketing' | 'performance')[]
175
+ }
176
+
177
+ piiFields: string[] // Field names to always scrub
178
+ }
179
+
180
+ export function loadConfig(): ObservabilityConfig {
181
+ const env = import.meta.env.MODE || process.env.NODE_ENV || 'development'
182
+ const isProd = env === 'production'
183
+
184
+ return {
185
+ environment: env as any,
186
+ isHealthcareApp: import.meta.env.VITE_HEALTHCARE_APP === 'true',
187
+
188
+ sentry: {
189
+ enabled: import.meta.env.VITE_SENTRY_ENABLED === 'true',
190
+ dsn: import.meta.env.VITE_SENTRY_DSN || '',
191
+ tracesSampleRate: isProd ? 0.2 : 1.0,
192
+ replaySampleRate: isProd ? 0.1 : 0,
193
+ replayOnErrorSampleRate: 1.0,
194
+ release: import.meta.env.VITE_APP_VERSION,
195
+ },
196
+
197
+ clarity: {
198
+ enabled: import.meta.env.VITE_CLARITY_ENABLED === 'true',
199
+ projectId: import.meta.env.VITE_CLARITY_PROJECT_ID || '',
200
+ },
201
+
202
+ datadog: {
203
+ enabled: import.meta.env.VITE_DATADOG_ENABLED === 'true',
204
+ applicationId: import.meta.env.VITE_DATADOG_APP_ID || '',
205
+ clientToken: import.meta.env.VITE_DATADOG_CLIENT_TOKEN || '',
206
+ site: import.meta.env.VITE_DATADOG_SITE || 'datadoghq.com',
207
+ service: import.meta.env.VITE_DATADOG_SERVICE || '',
208
+ version: import.meta.env.VITE_APP_VERSION,
209
+ sampleRate: isProd ? 100 : 0,
210
+ },
211
+
212
+ googleAnalytics: {
213
+ enabled: import.meta.env.VITE_GA_ENABLED === 'true',
214
+ measurementId: import.meta.env.VITE_GA_MEASUREMENT_ID || '',
215
+ gtmContainerId: import.meta.env.VITE_GTM_CONTAINER_ID,
216
+ enableConversions: import.meta.env.VITE_GA_CONVERSIONS === 'true',
217
+ debugMode: !isProd,
218
+ },
219
+
220
+ syslog: {
221
+ enabled: import.meta.env.VITE_SYSLOG_ENABLED === 'true',
222
+ endpoint: import.meta.env.VITE_SYSLOG_ENDPOINT || '',
223
+ level: (import.meta.env.VITE_SYSLOG_LEVEL || 'info') as any,
224
+ batchSize: 10,
225
+ flushInterval: 5000,
226
+ },
227
+
228
+ consent: {
229
+ required: true, // Default: require consent
230
+ storageKey: 'observability_consent',
231
+ categories: ['necessary', 'analytics', 'performance'],
232
+ },
233
+
234
+ // Healthcare PII fields — ALWAYS scrub from ALL services
235
+ piiFields: [
236
+ 'nhi', 'NHI', 'nhi_number', 'nhiNumber',
237
+ 'firstName', 'first_name', 'lastName', 'last_name',
238
+ 'fullName', 'full_name', 'name',
239
+ 'dateOfBirth', 'date_of_birth', 'dob', 'DOB',
240
+ 'email', 'phone', 'mobile', 'address',
241
+ 'ssn', 'socialSecurityNumber',
242
+ 'medicalRecordNumber', 'mrn',
243
+ 'password', 'token', 'apiKey', 'secret',
244
+ 'creditCard', 'cardNumber', 'cvv', 'expiryDate',
245
+ ],
246
+ }
247
+ }
248
+ ```
249
+
250
+ ### 1.3 PII Scrubber
251
+
252
+ ```typescript
253
+ // src/observability/pii-scrubber.ts
254
+
255
+ const DEFAULT_REPLACEMENT = '[REDACTED]'
256
+
257
+ export class PiiScrubber {
258
+ private sensitiveFields: Set<string>
259
+ private sensitivePatterns: RegExp[]
260
+
261
+ constructor(fields: string[]) {
262
+ this.sensitiveFields = new Set(fields.map(f => f.toLowerCase()))
263
+
264
+ // Patterns that catch PII even in unstructured text
265
+ this.sensitivePatterns = [
266
+ /[A-Z]{3}\d{4}/g, // NZ NHI number
267
+ /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g, // Phone numbers
268
+ /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, // Email addresses
269
+ /\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g, // Credit card numbers
270
+ /\b\d{2}[-/]\d{2}[-/]\d{4}\b/g, // Date of birth patterns
271
+ ]
272
+ }
273
+
274
+ // Scrub an object recursively
275
+ scrubObject<T extends Record<string, any>>(obj: T): T {
276
+ if (!obj || typeof obj !== 'object') return obj
277
+
278
+ const scrubbed = Array.isArray(obj) ? [...obj] : { ...obj }
279
+
280
+ for (const key of Object.keys(scrubbed)) {
281
+ if (this.sensitiveFields.has(key.toLowerCase())) {
282
+ (scrubbed as any)[key] = DEFAULT_REPLACEMENT
283
+ } else if (typeof scrubbed[key] === 'object' && scrubbed[key] !== null) {
284
+ (scrubbed as any)[key] = this.scrubObject(scrubbed[key])
285
+ } else if (typeof scrubbed[key] === 'string') {
286
+ (scrubbed as any)[key] = this.scrubString(scrubbed[key])
287
+ }
288
+ }
289
+
290
+ return scrubbed as T
291
+ }
292
+
293
+ // Scrub PII patterns from a string
294
+ scrubString(str: string): string {
295
+ let result = str
296
+ for (const pattern of this.sensitivePatterns) {
297
+ result = result.replace(pattern, DEFAULT_REPLACEMENT)
298
+ }
299
+ return result
300
+ }
301
+
302
+ // Scrub URL query parameters
303
+ scrubUrl(url: string): string {
304
+ try {
305
+ const parsed = new URL(url, 'http://localhost')
306
+ for (const [key] of parsed.searchParams) {
307
+ if (this.sensitiveFields.has(key.toLowerCase())) {
308
+ parsed.searchParams.set(key, DEFAULT_REPLACEMENT)
309
+ }
310
+ }
311
+ return parsed.pathname + parsed.search
312
+ } catch {
313
+ return url
314
+ }
315
+ }
316
+ }
317
+ ```
318
+
319
+ ### 1.4 Consent Management
320
+
321
+ ```typescript
322
+ // src/observability/consent.ts
323
+
324
+ export type ConsentCategory = 'necessary' | 'analytics' | 'marketing' | 'performance'
325
+
326
+ export interface ConsentState {
327
+ given: boolean
328
+ timestamp: string | null
329
+ categories: Record<ConsentCategory, boolean>
330
+ }
331
+
332
+ const DEFAULT_STATE: ConsentState = {
333
+ given: false,
334
+ timestamp: null,
335
+ categories: {
336
+ necessary: true, // Always allowed
337
+ analytics: false,
338
+ marketing: false,
339
+ performance: false,
340
+ },
341
+ }
342
+
343
+ export class ConsentManager {
344
+ private state: ConsentState
345
+ private storageKey: string
346
+ private listeners: ((state: ConsentState) => void)[] = []
347
+
348
+ constructor(storageKey: string) {
349
+ this.storageKey = storageKey
350
+ this.state = this.load()
351
+ }
352
+
353
+ private load(): ConsentState {
354
+ try {
355
+ const stored = localStorage.getItem(this.storageKey)
356
+ return stored ? JSON.parse(stored) : { ...DEFAULT_STATE }
357
+ } catch {
358
+ return { ...DEFAULT_STATE }
359
+ }
360
+ }
361
+
362
+ private save(): void {
363
+ try {
364
+ localStorage.setItem(this.storageKey, JSON.stringify(this.state))
365
+ } catch { /* localStorage unavailable */ }
366
+ }
367
+
368
+ grantAll(): void {
369
+ this.state = {
370
+ given: true,
371
+ timestamp: new Date().toISOString(),
372
+ categories: { necessary: true, analytics: true, marketing: true, performance: true },
373
+ }
374
+ this.save()
375
+ this.notify()
376
+ }
377
+
378
+ grantSelected(categories: Partial<Record<ConsentCategory, boolean>>): void {
379
+ this.state = {
380
+ given: true,
381
+ timestamp: new Date().toISOString(),
382
+ categories: { ...this.state.categories, ...categories, necessary: true },
383
+ }
384
+ this.save()
385
+ this.notify()
386
+ }
387
+
388
+ revoke(): void {
389
+ this.state = { ...DEFAULT_STATE }
390
+ this.save()
391
+ this.notify()
392
+ }
393
+
394
+ hasConsent(category: ConsentCategory): boolean {
395
+ return this.state.categories[category] === true
396
+ }
397
+
398
+ isConsentGiven(): boolean {
399
+ return this.state.given
400
+ }
401
+
402
+ getState(): ConsentState {
403
+ return { ...this.state }
404
+ }
405
+
406
+ onChange(listener: (state: ConsentState) => void): () => void {
407
+ this.listeners.push(listener)
408
+ return () => { this.listeners = this.listeners.filter(l => l !== listener) }
409
+ }
410
+
411
+ private notify(): void {
412
+ this.listeners.forEach(l => l(this.getState()))
413
+ }
414
+ }
415
+
416
+ // Service → consent category mapping
417
+ export const SERVICE_CONSENT_MAP: Record<string, ConsentCategory> = {
418
+ sentry: 'necessary', // Error tracking is necessary for app stability
419
+ clarity: 'analytics', // Session recording requires analytics consent
420
+ datadog: 'performance', // Performance monitoring
421
+ googleAnalytics: 'analytics',
422
+ syslog: 'necessary', // Operational logging is necessary
423
+ }
424
+ ```
425
+
426
+ ### 1.5 Unified Tracking Hook
427
+
428
+ ```typescript
429
+ // src/observability/hooks/useTrackEvent.ts
430
+
431
+ import { useCallback } from 'react'
432
+ import { useObservability } from '../providers/ObservabilityProvider'
433
+
434
+ export interface TrackEventOptions {
435
+ name: string
436
+ category?: string
437
+ properties?: Record<string, any>
438
+ services?: ('sentry' | 'clarity' | 'datadog' | 'ga' | 'syslog')[] // default: all enabled
439
+ }
440
+
441
+ export function useTrackEvent() {
442
+ const { services, scrubber } = useObservability()
443
+
444
+ return useCallback((options: TrackEventOptions) => {
445
+ const scrubbedProps = options.properties
446
+ ? scrubber.scrubObject(options.properties)
447
+ : undefined
448
+
449
+ const targetServices = options.services || Object.keys(services)
450
+
451
+ for (const svc of targetServices) {
452
+ if (services[svc]?.isEnabled()) {
453
+ services[svc].trackEvent(options.name, options.category, scrubbedProps)
454
+ }
455
+ }
456
+ }, [services, scrubber])
457
+ }
458
+ ```
459
+
460
+ ### 1.6 SPA Page View Tracking
461
+
462
+ ```typescript
463
+ // src/observability/hooks/useTrackPageView.ts
464
+
465
+ import { useEffect } from 'react'
466
+ import { useLocation } from 'react-router-dom'
467
+ import { useObservability } from '../providers/ObservabilityProvider'
468
+
469
+ export function useTrackPageView() {
470
+ const location = useLocation()
471
+ const { services, scrubber } = useObservability()
472
+
473
+ useEffect(() => {
474
+ const scrubbedPath = scrubber.scrubUrl(location.pathname + location.search)
475
+
476
+ for (const svc of Object.values(services)) {
477
+ if (svc.isEnabled()) {
478
+ svc.trackPageView(scrubbedPath, document.title)
479
+ }
480
+ }
481
+ }, [location.pathname])
482
+ }
483
+ ```
484
+
485
+ ### 1.7 ObservabilityProvider
486
+
487
+ ```typescript
488
+ // src/observability/providers/ObservabilityProvider.tsx
489
+
490
+ import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'
491
+ import { loadConfig, ObservabilityConfig } from '../config'
492
+ import { ConsentManager, SERVICE_CONSENT_MAP } from '../consent'
493
+ import { PiiScrubber } from '../pii-scrubber'
494
+
495
+ // Each service adapter implements this interface
496
+ export interface ObservabilityService {
497
+ name: string
498
+ init(): void
499
+ shutdown(): void
500
+ isEnabled(): boolean
501
+ trackEvent(name: string, category?: string, properties?: Record<string, any>): void
502
+ trackPageView(path: string, title: string): void
503
+ setUser(userId: string, traits?: Record<string, any>): void
504
+ trackError(error: Error, context?: Record<string, any>): void
505
+ }
506
+
507
+ interface ObservabilityContextValue {
508
+ services: Record<string, ObservabilityService>
509
+ config: ObservabilityConfig
510
+ consent: ConsentManager
511
+ scrubber: PiiScrubber
512
+ }
513
+
514
+ const ObservabilityContext = createContext<ObservabilityContextValue | null>(null)
515
+
516
+ export function useObservability() {
517
+ const ctx = useContext(ObservabilityContext)
518
+ if (!ctx) throw new Error('useObservability must be used within ObservabilityProvider')
519
+ return ctx
520
+ }
521
+
522
+ export function ObservabilityProvider({ children }: { children: React.ReactNode }) {
523
+ const config = useMemo(() => loadConfig(), [])
524
+ const consent = useMemo(() => new ConsentManager(config.consent.storageKey), [])
525
+ const scrubber = useMemo(() => new PiiScrubber(config.piiFields), [])
526
+ const [services, setServices] = useState<Record<string, ObservabilityService>>({})
527
+
528
+ useEffect(() => {
529
+ // Initialize services that have consent
530
+ const initServices: Record<string, ObservabilityService> = {}
531
+
532
+ // Import and init each enabled service
533
+ // Read the relevant reference file for each service's adapter implementation
534
+ // See: references/sentry.md, references/clarity.md, etc.
535
+
536
+ const handleConsentChange = () => {
537
+ // Re-evaluate which services should be active
538
+ for (const [name, svc] of Object.entries(initServices)) {
539
+ const category = SERVICE_CONSENT_MAP[name]
540
+ if (category && !consent.hasConsent(category)) {
541
+ svc.shutdown()
542
+ } else if (category && consent.hasConsent(category) && !svc.isEnabled()) {
543
+ svc.init()
544
+ }
545
+ }
546
+ }
547
+
548
+ consent.onChange(handleConsentChange)
549
+ setServices(initServices)
550
+
551
+ return () => {
552
+ Object.values(initServices).forEach(svc => svc.shutdown())
553
+ }
554
+ }, [])
555
+
556
+ return (
557
+ <ObservabilityContext.Provider value={{ services, config, consent, scrubber }}>
558
+ {children}
559
+ </ObservabilityContext.Provider>
560
+ )
561
+ }
562
+ ```
563
+
564
+ ### 1.8 Environment Variables Template
565
+
566
+ Generate `.env.example`:
567
+
568
+ ```bash
569
+ # ── Observability Configuration ──
570
+
571
+ # Healthcare mode — enables maximum PII scrubbing
572
+ VITE_HEALTHCARE_APP=true
573
+
574
+ # App version — used for release tracking
575
+ VITE_APP_VERSION=1.0.0
576
+
577
+ # ── Sentry ──
578
+ VITE_SENTRY_ENABLED=false
579
+ VITE_SENTRY_DSN=
580
+
581
+ # ── Microsoft Clarity ──
582
+ VITE_CLARITY_ENABLED=false
583
+ VITE_CLARITY_PROJECT_ID=
584
+
585
+ # ── Datadog ──
586
+ VITE_DATADOG_ENABLED=false
587
+ VITE_DATADOG_APP_ID=
588
+ VITE_DATADOG_CLIENT_TOKEN=
589
+ VITE_DATADOG_SITE=datadoghq.com
590
+ VITE_DATADOG_SERVICE=
591
+
592
+ # ── Google Analytics ──
593
+ VITE_GA_ENABLED=false
594
+ VITE_GA_MEASUREMENT_ID=
595
+ VITE_GTM_CONTAINER_ID=
596
+ VITE_GA_CONVERSIONS=false
597
+
598
+ # ── Syslog ──
599
+ VITE_SYSLOG_ENABLED=false
600
+ VITE_SYSLOG_ENDPOINT=
601
+ VITE_SYSLOG_LEVEL=info
602
+ ```
603
+
604
+ ---
605
+
606
+ ## Step 2: Integrate the Selected Service(s)
607
+
608
+ After building the foundation, read the appropriate reference file for each service the user selected:
609
+
610
+ | Service | Reference File | Consent Category |
611
+ |---------|---------------|-----------------|
612
+ | Sentry | `references/sentry.md` | necessary |
613
+ | Microsoft Clarity | `references/clarity.md` | analytics |
614
+ | Datadog | `references/datadog.md` | performance |
615
+ | Google Analytics / Pixels | `references/google-analytics.md` | analytics |
616
+ | Syslog → SIEM | `references/syslog.md` | necessary |
617
+
618
+ Read the selected reference file(s) and implement the service adapter following the patterns defined there.
619
+
620
+ Each reference file contains:
621
+ 1. NPM packages to install
622
+ 2. The service adapter class implementing `ObservabilityService` interface
623
+ 3. Initialization code (where and how to init)
624
+ 4. Event tracking patterns specific to that service
625
+ 5. PII scrubbing rules specific to that service
626
+ 6. Testing/verification steps
627
+ 7. Healthcare-specific configuration
628
+
629
+ ---
630
+
631
+ ## Step 3: Multi-Service Coexistence
632
+
633
+ When integrating multiple services simultaneously:
634
+
635
+ ### Load Order
636
+ Services must initialize in this order to avoid conflicts:
637
+ 1. **Sentry** first — catches errors from other service init
638
+ 2. **Syslog** second — operational logging is always needed
639
+ 3. **Datadog** third — performance monitoring before analytics
640
+ 4. **Google Analytics** fourth — analytics tracking
641
+ 5. **Clarity** last — session recording after everything else is stable
642
+
643
+ ### Performance Budget
644
+ Monitor combined script sizes:
645
+
646
+ ```typescript
647
+ // src/observability/performance-budget.ts
648
+
649
+ const SCRIPT_SIZES_KB = {
650
+ sentry: 72, // @sentry/react + @sentry/browser
651
+ clarity: 45, // Clarity script (loaded async)
652
+ datadog: 55, // @datadog/browser-rum
653
+ ga: 30, // gtag.js
654
+ syslog: 5, // Custom, minimal
655
+ }
656
+
657
+ export function checkPerformanceBudget(enabledServices: string[]): {
658
+ totalKb: number
659
+ overBudget: boolean
660
+ warning: string | null
661
+ } {
662
+ const totalKb = enabledServices.reduce((sum, svc) => sum + (SCRIPT_SIZES_KB[svc] || 0), 0)
663
+ const overBudget = totalKb > 100
664
+
665
+ return {
666
+ totalKb,
667
+ overBudget,
668
+ warning: overBudget
669
+ ? `Tracking scripts total ${totalKb}KB gzipped (budget: 100KB). Consider disabling non-essential services or lazy-loading them.`
670
+ : null,
671
+ }
672
+ }
673
+ ```
674
+
675
+ ### Deduplication
676
+ If both Sentry and Datadog are enabled, errors will be captured twice. The adapters should coordinate:
677
+ - Sentry handles detailed error tracking with stack traces
678
+ - Datadog tracks the error as a RUM event without the full stack trace
679
+ - Both get the same `correlationId` for cross-referencing
680
+
681
+ ---
682
+
683
+ ## Step 4: Wiring into the React App
684
+
685
+ ### App Entry Point
686
+
687
+ ```tsx
688
+ // src/main.tsx or src/App.tsx
689
+
690
+ import { ObservabilityProvider } from './observability/providers/ObservabilityProvider'
691
+ import { ConsentBanner } from './observability/providers/ConsentBanner'
692
+
693
+ function App() {
694
+ return (
695
+ <ObservabilityProvider>
696
+ <ConsentBanner />
697
+ <RouterProvider router={router} />
698
+ </ObservabilityProvider>
699
+ )
700
+ }
701
+ ```
702
+
703
+ ### In Route Components — Page View Tracking
704
+
705
+ ```tsx
706
+ // src/layouts/AppLayout.tsx
707
+
708
+ import { useTrackPageView } from '../observability/hooks/useTrackPageView'
709
+
710
+ function AppLayout() {
711
+ useTrackPageView() // Tracks every route change across ALL enabled services
712
+
713
+ return (
714
+ <div>
715
+ <Outlet />
716
+ </div>
717
+ )
718
+ }
719
+ ```
720
+
721
+ ### In Components — Event Tracking
722
+
723
+ ```tsx
724
+ // src/features/booking/BookingConfirmation.tsx
725
+
726
+ import { useTrackEvent } from '../../observability/hooks/useTrackEvent'
727
+
728
+ function BookingConfirmation() {
729
+ const trackEvent = useTrackEvent()
730
+
731
+ const handleConfirm = async () => {
732
+ await submitBooking()
733
+
734
+ // Tracked in ALL enabled services automatically
735
+ // PII is scrubbed automatically by the hook
736
+ trackEvent({
737
+ name: 'booking_confirmed',
738
+ category: 'conversion',
739
+ properties: {
740
+ appointmentType: booking.type,
741
+ providerId: booking.providerId,
742
+ // patientName would be auto-scrubbed if included
743
+ },
744
+ })
745
+ }
746
+ }
747
+ ```
748
+
749
+ ### Error Boundaries
750
+
751
+ ```tsx
752
+ // src/observability/hooks/useErrorBoundary.ts
753
+
754
+ import * as Sentry from '@sentry/react'
755
+
756
+ // Use Sentry's error boundary for automatic error tracking
757
+ export const ErrorBoundary = Sentry.ErrorBoundary
758
+
759
+ // Fallback component
760
+ export function ErrorFallback({ error, resetError }: { error: Error; resetError: () => void }) {
761
+ return (
762
+ <div className="flex flex-col items-center justify-center min-h-screen p-8">
763
+ <h1 className="text-2xl font-bold text-gray-900 mb-4">Something went wrong</h1>
764
+ <p className="text-gray-600 mb-6">We've been notified and are looking into it.</p>
765
+ <Button onClick={resetError}>Try Again</Button>
766
+ </div>
767
+ )
768
+ }
769
+ ```
770
+
771
+ ---
772
+
773
+ ## Step 5: Verification
774
+
775
+ After integrating, verify each service:
776
+
777
+ ### Per-Service Verification Checklist
778
+
779
+ - [ ] Service initializes only when enabled via env var
780
+ - [ ] Service does NOT initialize until consent is given (for analytics/marketing categories)
781
+ - [ ] Service tracks page views on route changes
782
+ - [ ] Service tracks custom events
783
+ - [ ] PII is scrubbed from all tracked data (test with a fake NHI number — it should appear as [REDACTED] in the service dashboard)
784
+ - [ ] Service respects kill switch (set env var to false, confirm no requests to service)
785
+ - [ ] Service does not error when disabled (graceful no-op)
786
+ - [ ] Source maps uploaded (Sentry/Datadog — for readable stack traces)
787
+ - [ ] No patient names, NHI numbers, or medical data appear in ANY service dashboard
788
+
789
+ ### Integration Test
790
+
791
+ ```typescript
792
+ describe('Observability', () => {
793
+ it('should scrub PII from tracked events', () => {
794
+ const scrubber = new PiiScrubber(config.piiFields)
795
+ const event = {
796
+ name: 'patient_viewed',
797
+ patientName: 'John Smith',
798
+ nhi: 'ABC1234',
799
+ email: 'john@example.com',
800
+ appointmentType: 'General Checkup',
801
+ }
802
+
803
+ const scrubbed = scrubber.scrubObject(event)
804
+
805
+ expect(scrubbed.patientName).toBe('[REDACTED]')
806
+ expect(scrubbed.nhi).toBe('[REDACTED]')
807
+ expect(scrubbed.email).toBe('[REDACTED]')
808
+ expect(scrubbed.appointmentType).toBe('General Checkup') // NOT PII, kept
809
+ })
810
+
811
+ it('should not initialize tracking without consent', () => {
812
+ const consent = new ConsentManager('test_consent')
813
+ expect(consent.hasConsent('analytics')).toBe(false)
814
+ // Clarity and GA should NOT be initialized
815
+ })
816
+ })
817
+ ```
818
+
819
+ ---
820
+
821
+ ## Quality Gate
822
+
823
+ Before delivering:
824
+
825
+ - [ ] Foundation built: config, consent, PII scrubber, provider, hooks
826
+ - [ ] Selected service(s) integrated via adapter pattern
827
+ - [ ] All PII fields from config are scrubbed in ALL services
828
+ - [ ] Consent management working — services only init after consent
829
+ - [ ] Kill switch working — env var disables each service independently
830
+ - [ ] SPA page views tracked on route changes
831
+ - [ ] Error boundary in place with fallback UI
832
+ - [ ] `.env.example` generated with all required variables
833
+ - [ ] No raw `window.gtag()` or `window.clarity()` calls scattered in components — all through the observability layer
834
+ - [ ] Performance budget checked — combined scripts under 100KB warning threshold
835
+ - [ ] Tests verify PII scrubbing and consent behavior