← all branches

feat/ph-replay

needs attentionviewing older commit
069e020 · fullPR #361reviewed 2026-07-30 21:06 UTC3H · 6M · 7L · 1I
The branch
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
The changes (whole branch)
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+465docs/analytics/privacy.md+2716packages/analytics/README.md+110apps/platform/src/app/[locale]/layout.tsx+41
Blast
4 files, +88/-22. Touches only analytics package internals and one layout. Low footprint.
pii-recording no-tests-for-dual-gate sensitive-class-unused deploy-action-required
typecheck (@batu/analytics)· pnpm --filter @batu/analytics typecheck ✅ (PR body)lint (@batu/analytics)· pnpm --filter @batu/analytics lint ✅ (PR body)typecheck (@batu/platform)· Only pre-existing workspace dep errors in non-touched files (PR body)ci· GraphQL statusCheckRollup inaccessible from runner tokencoderabbit· No .coderabbit.yaml in repo

Findings · 17

correctness3

high

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().

medium

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.

low

isSessionReplayEnabledForEnvironment reads build-time constant on every effect run

packages/analytics/src/providers/PostHogProvider.tsx

security5

high

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.

high

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.

medium

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.

low

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.

info

Session recordings sent to US PostHog cloud — Mexico LFPDPPP requires disclosing international transfers

packages/analytics/src/providers/PostHogProvider.tsx

conventions4

medium

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.

low

20-line JSDoc on a boolean prop whose name is self-explanatory

packages/analytics/src/providers/PostHogProvider.tsx

low

Mixed Spanish/English comments in the same file

packages/analytics/src/providers/PostHogProvider.tsx

low

Spanish comment block in layout.tsx above PostHogProvider

apps/platform/src/app/[locale]/layout.tsx

tests3

medium

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.

medium

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.

low

posthog.__loaded single-init guard not covered by tests

packages/analytics/src/providers/PostHogProvider.tsx

improvement2

medium

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.

low

JSDoc verbosity: ~30 lines for a boolean prop and a one-liner helper

packages/analytics/src/providers/PostHogProvider.tsx

History · 3 commits

  1. ab8cd04safeincremental0H · 0M · 1L2026-07-31 00:23
  2. 8d1b744needs attentionincremental0H · 5M · 10L2026-07-30 21:44
  3. 069e020needs attentionfull3H · 6M · 7L2026-07-30 21:06current