feat/ph-replay
needs attentionviewing older commit8d1b744 · incrementalPR #361reviewed 2026-07-30 21:44 UTC0H · 5M · 10L · 5I- Purpose
- Batu platform lacked session replay data despite PostHog being configured — disable_session_recording local kill-switch was always true, confirmed by 0 $snapshot events vs 1,604 $pageview events in 14 days.
- Goal
- Enable PostHog session replay on apps/platform (prod only) with a two-part gate, and harden privacy so secrets rendered outside <input> are excluded from capture.
- Sub-goals
- SG-1: Wire disable_session_recording: false when both gate halves pass
- SG-2: Keep apps/web, staging, and preview branches out of recording
- SG-3: Mask all input values unconditionally (CFE credentials)
- SG-4: Block non-input secrets via ph-no-capture class
- SG-5: Extract gate logic to pure testable session-replay.ts
- SG-6: Test all 4 gate combinations to guard silent PII recording regressions
- SG-7: Remove dead maskTextSelector: '.sensitive'
- What
- PostHogProvider delegates to session-replay.ts; ph-no-capture added to SecretRevealCard and EnvPanel; dead .sensitive maskTextSelector removed; 71-line test suite added; docs updated.
- Why
- disable_session_recording: true in posthog.init() overrode the PostHog console config. maskAllInputs only covers <input>; API keys in <code> blocks were recorded in clear.
- Areas
- apps/platform+11−3packages/analytics+203−19docs/analytics+48−15
- Blast
- 8 files +262/−37. Additive only, gate defaults OFF without env var, no schema/API/domain touched.
Findings · 20
correctness2
useEffect empty deps silently ignores post-mount sessionReplay prop changes
packages/analytics/src/providers/PostHogProvider.tsx:111
Intentional and documented — both gate halves are build-time constants. Risk is future confusion if sessionReplay is ever made dynamic.
SESSION_REPLAY_ENABLED_FOR_ENV frozen at module load — test ordering constraint
packages/analytics/src/providers/session-replay.ts:34
Tests that set process.env after import see stale value. Fine in practice (tests pass explicit booleans).
security4
ph-no-capture is the only defense for non-input secrets — no structural enforcement
packages/analytics/src/providers/PostHogProvider.tsx
mask_all_text: false + autocapture: true means any non-input secret without ph-no-capture ancestor will be captured. Convention-only; no lint rule or component wrapper guards future misses.
Removal of dead .sensitive selector — no regression
packages/analytics/src/providers/PostHogProvider.tsx
EnvPanel bearer token has double coverage — appropriate for most sensitive credential
apps/platform/src/app/[locale]/(docs)/_components/EnvPanel.tsx:125
ph-no-capture on parent div correctly blocks descendant secrets
apps/platform/src/app/[locale]/(dashboard)/credentials/api/_components/SecretRevealCard.tsx:39
conventions4
@file JSDoc block violates no-multi-line-comment rule
packages/analytics/src/providers/session-replay.ts:1
6-line @file/@description JSDoc block. CLAUDE.md prohibits multi-paragraph docstrings. Collapse to: // Pure gate: no imports so vitest/node can run this without posthog-js.
Test-file block comment is multi-paragraph
packages/analytics/src/providers/__tests__/session-replay.test.ts:1
Collapse to single-line comment. The silent-failure-mode rationale is valuable but the format violates CLAUDE.md.
sessionReplayInitOptions describe block has multi-line comment
packages/analytics/src/providers/__tests__/session-replay.test.ts
The negation-trap explanation should be a single comment on the assertion line, not a block above the test.
Extraction to session-replay.ts is architecturally sound FCIS move
packages/analytics/src/providers/session-replay.ts
tests5
No test pins the env var name read by SESSION_REPLAY_ENABLED_FOR_ENV
packages/analytics/src/providers/__tests__/session-replay.test.ts
A misspelled env var name silently disables replay in prod. Add test with vi.stubEnv + vi.resetModules() to pin NEXT_PUBLIC_POSTHOG_SESSION_REPLAY as the correct name.
No regression test asserting maskTextSelector is absent
packages/analytics/src/providers/__tests__/session-replay.test.ts
The commit removes dead maskTextSelector: '.sensitive'. Add: expect(sessionReplayInitOptions(true,true)).not.toHaveProperty('session_recording.maskTextSelector')
Redundant assertions: it.each + named test re-cover same 2 cases
packages/analytics/src/providers/__tests__/session-replay.test.ts:28
No component tests verify SecretRevealCard/EnvPanel apply ph-no-capture
packages/analytics/src/providers/__tests__/session-replay.test.ts
PH_NO_CAPTURE_CLASS test is circular — consumers use raw string
packages/analytics/src/providers/__tests__/session-replay.test.ts:62
improvement5
PH_NO_CAPTURE_CLASS not re-exported from package index — consumers use raw string
packages/analytics/src/index.ts
Both SecretRevealCard.tsx and EnvPanel.tsx hardcode 'ph-no-capture'. Export PH_NO_CAPTURE_CLASS from the package index so consumers can import the typed constant.
sessionReplayInitOptions double-evaluates the gate
packages/analytics/src/providers/PostHogProvider.tsx:82
PostHogProvider calls isSessionReplayEnabled() on line 82, then passes the same two args to sessionReplayInitOptions() on line 95 which internally re-runs the same gate. Prefer sessionReplayInitOptions(enabled: boolean).
ph-no-capture gap: CredentialsForm PasswordInput visibility-toggle path
apps/platform/src/app/[locale]/(dashboard)/credentials/cfe/_components/CredentialsForm.tsx
When visibility toggle switches to type='text', maskAllInputs stops masking. If no ph-no-capture wrapper, session replay captures the CFE password.
SESSION_REPLAY_ENABLED_FOR_ENV baked at module load — limits future env-branch testing
packages/analytics/src/providers/session-replay.ts:34
isSessionReplayEnabled is a trivial &&-alias — fine as named test target
packages/analytics/src/providers/session-replay.ts:43