Skip to content

feat(gtm): Spec 1D — website reconciliation (analytics-foundation 1d)#365

Merged
blove merged 16 commits into
mainfrom
gtm-spec-1d-website-reconciliation
May 16, 2026
Merged

feat(gtm): Spec 1D — website reconciliation (analytics-foundation 1d)#365
blove merged 16 commits into
mainfrom
gtm-spec-1d-website-reconciliation

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 16, 2026

Summary

Spec 1D closes analytics-foundation. Two cohesive changes:

  • Per-app first-party proxies at `/ingest/` (Next.js rewrites) so ad-blockers can't drop `marketing:`, `docs:`, or `cockpit:` events. Posthog-js targets `api_host: '/ingest'` and Next.js forwards transparently to `us.i.posthog.com` (and `us-assets.i.posthog.com` for static assets). Cockpit's `/ingest/*` includes CORS headers so iframe Angular apps on `examples.cacheplane.ai` can POST cross-origin.
  • Consolidated capture-guard helpers into `@ngaf/telemetry`. `shouldCaptureAnalytics`, `isLocalAnalyticsHost`, and the shared cross-runtime helpers (`toSafeAnalyticsString`, `getEmailDomain`, `getSourcePage`, `normalizePostHogHost`) live in `@ngaf/telemetry/{browser,shared}`. Both apps consume one source of truth; the app-local duplicates are deleted.

The existing `/api/ingest` route (Spec 1B's `libs/telemetry/browser` custom envelope path) is untouched — it serves a different contract for consumer apps' opt-in browser telemetry.

Spec & Plan

  • Spec: `docs/superpowers/specs/gtm/2026-05-16-analytics-foundation-1d-website-reconciliation-design.md`
  • Plan: `docs/superpowers/plans/gtm/2026-05-16-analytics-foundation-1d-website-reconciliation.md`

Notable design notes

  • Why rewrites instead of expanding /api/ingest: posthog-js sends PostHog's native batched/gzipped wire format to `/e/`, `/flags/`, `/static/array.js`, etc. The existing custom envelope route can't accept those payloads without re-parsing PostHog's format. Rewrites forward transparently, are platform-native (edge-friendly, zero lambda invocation), and match PostHog's officially documented Next.js proxy pattern.
  • CORS on cockpit /ingest: `Access-Control-Allow-Origin` is env-driven (`NEXT_PUBLIC_COCKPIT_IFRAME_ORIGIN`), wildcard `*` for local dev. No `Access-Control-Allow-Credentials` header — no cookies cross the boundary, no CSRF surface.
  • `isLocalAnalyticsHost` IPv6 fix: the original website implementation returned false for bare `::1` because `'::1'.split(':')[0] === ''`. The consolidated implementation handles `::1`, `[::1]:port`, `localhost`, and `127.0.0.1` correctly. Tests cover all four forms.
  • No taxonomy or dashboard changes. Events land in PostHog identically — only the network path changes.

Test plan

  • `nx run-many -t test -p telemetry,cockpit` — green
  • `nx run-many -t build -p telemetry,website,cockpit` — green
  • No stale `analytics/properties` imports anywhere in `apps/` or `libs/` (excluding .next cache)
  • No app-code direct refs to `us.i.posthog.com` (only in rewrite destinations + library fallbacks)
  • Post-merge manual smoke: DevTools network panel on cacheplane.ai + cockpit.cacheplane.ai shows only `.cacheplane.ai/ingest/` requests, no `*.posthog.com` traffic
  • Post-merge: PostHog Live Events confirms events still arrive with matching distinct_ids

🤖 Generated with Claude Code

blove and others added 16 commits May 16, 2026 07:05
Routes every first-party event through per-app /api/ingest proxies and
consolidates the analytics capture-guard helpers into @ngaf/telemetry so
ad-blockers stop dropping marketing/docs/cockpit traffic and there's one
source of truth across apps/website + apps/cockpit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
While drafting the implementation plan, discovered the original spec
conflated two distinct contracts:
- /api/ingest serves libs/telemetry/browser's custom-envelope path
  (Spec 1B), accepting only ngaf:* events posted by consumer apps.
- apps/website + apps/cockpit use posthog-js directly, which sends
  PostHog's batched/gzipped format to /e/, /flags/, /static/array.js,
  etc. — not the custom envelope.

Revised architecture uses Next.js rewrites (PostHog's officially
documented Next.js proxy pattern) at /ingest/* to forward transparently
to us.i.posthog.com. The existing /api/ingest route is unchanged; the
two paths serve different contracts side-by-side.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…econciliation)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…, getEmailDomain, getSourcePage, normalizePostHogHost)

Moved from apps/website/src/lib/analytics/properties.ts so both shells +
server-side code can consume one source of truth via @ngaf/telemetry/shared.
Tests follow the code.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…sHost

Moved from apps/website/src/lib/analytics/properties.ts so apps/website
and apps/cockpit consume the same capture guard via
@ngaf/telemetry/browser. apps/cockpit's variant (which lacked the
toSafeAnalyticsString hardening) is dropped in favor of this stricter
implementation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ry/shared

Switches server.ts + the four API routes (leads, ingest, whitepaper-signup,
newsletter) that use toSafeAnalyticsString, getEmailDomain, getSourcePage,
normalizePostHogHost to consume them from the published lib instead of the
app-local copy. Adds the @ngaf/telemetry/{shared,browser} path mappings to
the website tsconfig so Next.js bundler-mode resolution picks them up.
Local properties.ts stays until Task 0.5 deletes it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…/browser

instrumentation-client.ts now sources the capture guard from the shared
lib. Local properties.ts retains its remaining consumers (the test file
itself) until Task 0.5.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
All consumers now import from @ngaf/telemetry/{shared,browser}. Tests
moved to libs/telemetry in Tasks 0.1 and 0.2.

Also migrates apps/website/src/lib/analytics/client.ts (browser-side
posthog-js capture helpers) to @ngaf/telemetry/shared — it was missed by
the Task 0.3 grep because it uses `from './properties'` rather than
`from '../analytics/properties'`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…y/browser

Drops the cockpit-local properties.ts duplicate in favor of the
website's stricter implementation now living in @ngaf/telemetry/browser.
Both shells share one capture guard.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Export nextConfig as named export for testability
- Add async rewrites() for /ingest/static and /ingest API paths
- Set skipTrailingSlashRedirect to prevent ad-blocker bypass
- Add next.config.spec.ts with rewrite rule validation
- Set api_host to '/ingest' for local proxy rewriting
- Add ui_host pointing to us.posthog.com dashboard
- Remove normalizePostHogHost import (no longer used)
Adds Next.js rewrites forwarding /ingest/static/* to us-assets.i.posthog.com
and /ingest/* to us.i.posthog.com, plus CORS headers on /ingest/* so the
embedded runtime iframe can POST analytics through the cockpit origin.
Origin reads NEXT_PUBLIC_COCKPIT_IFRAME_ORIGIN (defaults to *).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switches the cockpit's posthog-js api_host to '/ingest' (served by the
proxy rewrites from §2.1) and sets ui_host to https://us.posthog.com so
toolbar/replay links still resolve. Drops the now-unused
NEXT_PUBLIC_COCKPIT_POSTHOG_HOST read from both client init paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
run-mode now defaults cockpit_host to {origin}/ingest so the embedded
Angular harness initializes posthog-js against the cockpit-served proxy.
NEXT_PUBLIC_COCKPIT_INGEST_HOST overrides for staging/preview origins.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…KPIT_IFRAME_ORIGIN

Two new env vars introduced by Spec 1D:
- NEXT_PUBLIC_COCKPIT_INGEST_HOST — absolute URL of the cockpit shell's
  /ingest proxy, written into the iframe URL's cockpit_host param.
- NEXT_PUBLIC_COCKPIT_IFRAME_ORIGIN — CORS-allowed iframe origin for
  the cockpit's /ingest rewrite.

NEXT_PUBLIC_COCKPIT_POSTHOG_HOST is removed — posthog-js now uses the
same-origin /ingest rewrite (no host env needed).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…Host

Implementation already handles this form; test makes the coverage explicit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cacheplane Ready Ready Preview, Comment May 16, 2026 5:05pm

Request Review

@blove blove merged commit 10acc90 into main May 16, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant