feat/ph-replay
needs attentionviewing older commit069e020 · fullPR #361reviewed 2026-07-30 21:06 UTC3H · 6M · 7L · 1I- Purpose
- PostHog session replay was silently broken: disable_session_recording: true in posthog.init() is a local kill-switch that prevents the recorder from loading regardless of console settings — 0 $snapshot events vs 1,604 $pageview over 14 days.
- Goal
- Enable session replay for the authenticated platform app in production only, with a dual-gate that prevents unintended recording in previews, staging, and the marketing site.
- Sub-goals
- SG-1: Add sessionReplay prop to PostHogProvider (app-level gate)
- SG-2: Read NEXT_PUBLIC_POSTHOG_SESSION_REPLAY env var (env-level gate, production-only)
- SG-3: Apply maskAllInputs + maskTextSelector for CFE credential protection
- SG-4: Update docs with the two-gate invariant and the local kill-switch gotcha
- What
- PostHogProvider gains a sessionReplay prop and isSessionReplayEnabledForEnvironment helper; disable_session_recording is now conditional instead of always-true; apps/platform layout opts in with <PostHogProvider sessionReplay>.
- Why
- The PostHog console showed recording enabled (100% sample, no triggers, no URL blocklist, remote config confirmed 'enabled: true') but zero recordings were produced — the local kill-switch in posthog.init() was overriding the server config.
- Areas
- packages/analytics/src/providers/PostHogProvider.tsx+46−5docs/analytics/privacy.md+27−16packages/analytics/README.md+11−0apps/platform/src/app/[locale]/layout.tsx+4−1
- Blast
- 4 files, +88/-22. Touches only analytics package internals and one layout. Low footprint.
Findings · 17
correctness3
useEffect([sessionReplay]) dep implies reactivity that is impossible — __loaded guard returns early on every re-run
packages/analytics/src/providers/PostHogProvider.tsx
Once PostHog is initialized, posthog.__loaded is truthy and the effect exits before evaluating replayEnabled. Either revert to [] and document single-mount semantics, or handle the already-loaded case with posthog.startSessionRecording()/stopSessionRecording().
opt_out_capturing() in dev makes the dual-gate untestable locally
packages/analytics/src/providers/PostHogProvider.tsx
Setting NEXT_PUBLIC_POSTHOG_SESSION_REPLAY=true in .env.local won't produce recordings because opt_out fires immediately after init.
isSessionReplayEnabledForEnvironment reads build-time constant on every effect run
packages/analytics/src/providers/PostHogProvider.tsx
security5
maskTextSelector '.sensitive' configured but zero elements in the platform carry this class — selector is dead
packages/analytics/src/providers/PostHogProvider.tsx
grep finds no className='sensitive' or data-ph-no-capture. API keys displayed in <code> elements (SecretRevealCard) are not inputs so maskAllInputs doesn't cover them — they'll appear unmasked in recordings.
API key in <code> element in SecretRevealCard has no recording mask
apps/platform/src/app/[locale]/(dashboard)/credentials/api/_components/SecretRevealCard.tsx
After create/rotate, full API secret is in a <code> element. maskAllInputs doesn't apply. Apply className='sensitive' or data-ph-no-capture to the <code> container.
autocapture: true captures $el_text of clicks near sensitive data as structured event properties
packages/analytics/src/providers/PostHogProvider.tsx
PostHog records innerText of clicked elements. Button labels adjacent to credential/API-key pages will appear in PostHog events.
Bearer token in EnvPanel has no defense-in-depth beyond maskAllInputs
apps/platform/src/app/[locale]/(docs)/_components/EnvPanel.tsx
Add data-ph-no-capture as belt-and-suspenders.
Session recordings sent to US PostHog cloud — Mexico LFPDPPP requires disclosing international transfers
packages/analytics/src/providers/PostHogProvider.tsx
conventions4
Module-level helper for a one-liner env check
packages/analytics/src/providers/PostHogProvider.tsx
isSessionReplayEnabledForEnvironment() wraps a single expression. Inline or hoist as a const.
20-line JSDoc on a boolean prop whose name is self-explanatory
packages/analytics/src/providers/PostHogProvider.tsx
Mixed Spanish/English comments in the same file
packages/analytics/src/providers/PostHogProvider.tsx
Spanish comment block in layout.tsx above PostHogProvider
apps/platform/src/app/[locale]/layout.tsx
tests3
Dual-gate logic has no unit tests — all 4 gate combinations can regress silently
packages/analytics/src/providers/PostHogProvider.tsx
4 cases: (prop=true, env=true)→ON; (prop=true, env=false)→OFF; (prop=false, env=true)→OFF; (prop=false, env=false)→OFF. Any inversion is a silent PII-recording regression.
disable_session_recording: !replayEnabled wiring not asserted in tests
packages/analytics/src/providers/PostHogProvider.tsx
PostHog silently ignores unknown keys. A typo or accidental ! removal flips behavior in production without any test failing.
posthog.__loaded single-init guard not covered by tests
packages/analytics/src/providers/PostHogProvider.tsx
improvement2
useEffect dep [sessionReplay] includes a build-time constant — hoist env check to module scope
packages/analytics/src/providers/PostHogProvider.tsx
const SESSION_REPLAY_ENV = process.env.NEXT_PUBLIC_POSTHOG_SESSION_REPLAY === 'true' at module scope allows [] dep array and removes false impression of runtime re-evaluation.
JSDoc verbosity: ~30 lines for a boolean prop and a one-liner helper
packages/analytics/src/providers/PostHogProvider.tsx