Skip to content

feat(gtm): Spec 1C — cockpit instrumentation (analytics-foundation 1c)#351

Merged
blove merged 42 commits into
mainfrom
worktree-gtm+cockpit-instrumentation
May 16, 2026
Merged

feat(gtm): Spec 1C — cockpit instrumentation (analytics-foundation 1c)#351
blove merged 42 commits into
mainfrom
worktree-gtm+cockpit-instrumentation

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 16, 2026

Summary

Spec 1C populates the developer-funnel dashboard from Spec 1A with real cohort data by instrumenting cockpit's three surfaces (React shell + 31 Angular iframes + cross-frame correlation), while keeping example reference code pristine via build-time entry override.

  • Phase 0: Public lifecycle tokens (`CHAT_LIFECYCLE`, `AGENT_LIFECYCLE`, `RENDER_LIFECYCLE`) in 3 libs — wired into ChatComponent, agent.fn.ts, and RenderLifecycleService.
  • Phase 1: Private `@ngaf/cockpit-telemetry` lib — tokens, ActivationAggregator (5-signal 30-min window), CockpitTelemetryService, `provideCockpitTelemetry()` factory, `bootstrapWithCockpitHarness()` entry helper, permanent browser-silence contract test. Uses memory persistence + parent-provided distinctID.
  • Phase 2: React shell — analytics module, `instrumentation-client.ts`, instrumented sidebar/mode-switcher/code-mode/narrative-docs. RunMode appends `cockpit_did`/`cockpit_cap`/`cockpit_phk`/`cockpit_host` to iframe src for cross-frame correlation.
  • Phase 3-4: All 31 Angular examples wired via `main.cockpit.ts` build-time entry override (`@angular/build:application` cockpit configuration). Pristine `main.ts` unchanged.
  • Phase 5: Website lifecycle docs at `/docs/{chat,agent,render}/guides/lifecycle`.
  • Phase 6.1-6.3: Taxonomy aligned (drops `install_command_copied`, renames `six_signals_complete` → `activation_complete`, adds `mode_switched`/`code_copied`, renames `recipe_start` → `recipe_opened`). Insight renamed `six-signal-activation-funnel` → `activation-funnel` with 5 steps. Dashboard tile updated.

Architecture: Lifecycle tokens + external adapter (Architecture B). Cockpit-telemetry subscribes to optional lifecycle tokens and `AgentLifecycleRegistry` (see below) — no telemetry code lands in example reference code.

Spec & Plan

  • Spec: `docs/superpowers/specs/gtm/2026-05-15-analytics-foundation-1c-cockpit-instrumentation-design.md`
  • Plan: `docs/superpowers/plans/gtm/2026-05-15-analytics-foundation-1c-cockpit-instrumentation.md`

Notable design decisions

  • AgentLifecycleRegistry: `agent()` doesn't own a DI scope, so it can't directly provide `AGENT_LIFECYCLE`. Added an optional `AgentLifecycleRegistry` service in `@ngaf/langgraph`. `agent()` registers if a consumer provided the registry; `provideCockpitTelemetry()` provides it and the service reacts via signal effect. Example app code unchanged.
  • Memory-only persistence: `posthog.init({ persistence: 'memory', bootstrap: { distinctID } })` inside each iframe — no localStorage cookies bleed. Parent passes the session UUID via URL params.
  • No telemetry by default: Cockpit shell only inits posthog when `NEXT_PUBLIC_COCKPIT_POSTHOG_TOKEN` is set, DO_NOT_TRACK is not active, and either we're not on localhost or `NEXT_PUBLIC_COCKPIT_CAPTURE_LOCAL=true`.
  • `_internal` escape hatch on ChatLifecycle: Used to keep the public token surface readonly while letting ChatComponent populate signals. Flagged in review as a follow-up candidate (consider WeakMap-keyed writer) — non-blocking.

What's NOT in this PR (deferred to operator with credentials)

  • Task 3.3 — Local Chrome MCP smoke test (requires PostHog token + running dev servers)
  • Task 6.4 — `nx run posthog-tools:sync:apply` to actuate the insight rename in PostHog (requires `POSTHOG_PERSONAL_API_KEY`)

I'll run the smoke + PostHog sync after merge.

Test plan

  • `npx nx run-many -t test -p chat,langgraph,render,cockpit-telemetry,cockpit,posthog-tools` — all 6 projects green
  • `npx nx run cockpit-telemetry:build` — clean
  • `npx nx run cockpit-langgraph-streaming-angular:build:cockpit` — clean
  • Spot-check builds across 6 examples in 4 categories — all clean
  • Post-merge: Chrome MCP smoke (verify `cockpit:recipe_opened` → iframe loads → `cockpit:chat_first_message` + `cockpit:transport_connected` fire with matching `distinct_id`)
  • Post-merge: `nx run posthog-tools:sync:plan` + `:sync:apply` — verify activation-funnel tile shows 5 steps in the developer-funnel dashboard

🤖 Generated with Claude Code

blove and others added 30 commits May 15, 2026 12:32
Spec 1C of the GTM motion. Three-surface cockpit instrumentation:

- Outer (React shell): cockpit:recipe_opened, mode_switched, code_copied
  via posthog-js direct
- Inner (Angular iframes, per-example): cockpit:chat_first_message,
  transport_connected, thread_persisted, interrupt_handled,
  generative_component_rendered via new @ngaf/cockpit-telemetry
  private library that subscribes to lifecycle signals on
  @ngaf/chat, @ngaf/langgraph, @ngaf/render
- Cross-frame correlation via session UUID in URL params; memory-only
  persistence on both frames

Key decisions:
- Architecture B (lifecycle signals + external adapter) — libraries
  expose @ngaf/* tokens, adapter is private. Customer apps never emit
  cockpit:* events.
- cockpit-telemetry uses posthog-js directly, not @ngaf/telemetry/browser
  (cockpit is internal product, different posture from customer libs)
- main.cockpit.ts build-time entry override per example, so example
  reference code (main.ts, app.config.ts, components) stays pristine
- Telemetry on by default in production, off on localhost unless
  NEXT_PUBLIC_COCKPIT_CAPTURE_LOCAL=true; honors DO_NOT_TRACK
- Activation funnel = 5 signals (dropped cockpit:install_command_copied;
  ngaf:postinstall from PR #328 is uncorrelatable to cockpit sessions
  by design)
- Renamed cockpit:six_signals_complete → cockpit:activation_complete
- All 32 examples rolled out in batched per-category commits within
  this plan; canonical example: cockpit/langgraph/streaming/angular
- Website docs for the three public *_LIFECYCLE tokens land as Phase 5

Phases:
  0. Library lifecycle additions (~21 tests)
  1. @ngaf/cockpit-telemetry private library (~24 tests, incl.
     permanent browser silence test)
  2. React shell instrumentation (~17 tests)
  3. Canonical streaming example + Chrome MCP smoke
  4. 31 remaining examples in 4 category batches
  5. Website docs at /docs/<lib>/lifecycle
  6. Taxonomy + PostHog dashboard cleanup (drop install_command_copied,
     rename event + insight + dashboard, posthog:sync)

Total ~65 tests, 15 new spec files. Pre-PR-#328 design adjusted to
match the new ingest proxy + per-package install telemetry pattern.

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

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Public API addition for cockpit-telemetry (and other consumers) to
subscribe to per-instance chat lifecycle signals. componentReady,
firstMessageSent (sticky), messageCount and inputSubmittedAt (reset on
clearThread). Token only; wiring lands in next task.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Populates the four lifecycle signals from existing component code
paths: componentReady on the first agent-resolved effect,
firstMessageSent/messageCount/inputSubmittedAt in a new public
submitMessage() (also driven by the chat-input submitted output),
and reset (except sticky firstMessageSent) in a new public
clearThread(). The token is provided component-scoped via a factory
that hands ChatComponent a writable internal handle while consumers
see only the readonly Signal<T> surface. Adds 6 tests covering all
transitions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Addresses code-quality review feedback on Task 0.2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
8 lifecycle signals exposing transition timestamps. Wiring lands in
agent.fn.ts in the next task. Three signals (interruptResolvedAt,
threadCreatedAt, threadPersistedAt) require new hook points; five are
derived from existing BehaviorSubjects.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eight signal updates hooked into existing stream subscriptions and the
agent's submit/switchThread/load-history paths. Three new hooks
(interruptResolvedAt, threadCreatedAt, threadPersistedAt) — five
signals derive from existing stream state. All reset on switchThread.

Lifecycle surface exposed via a new `lifecycle: AgentLifecycle` field
on the returned LangGraphAgent (the factory has no DI scope of its own,
so this is the minimal-pollution path; consumers can re-provide the
AGENT_LIFECYCLE token via standard Angular providers if needed).

Mock agent updated to satisfy the new field. 10 tests cover all
transitions; 154/154 langgraph suite passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Service subscribes to the existing render-event stream and reduces to
five signals. firstMountAt is sticky; the rest update on each event.
Provided via provideRender() so all consumers automatically have access.

All RenderEvents flow through a single emitTapped() in RenderSpecComponent,
which fans out to the events output AND notifies the lifecycle service
(when present). Service is injected optionally so the components remain
usable without provideRender().

5 tests cover all signal transitions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ngaf/cockpit-telemetry — private (not in publishable group), Angular
library, consumed by the 32 Angular examples via main.cockpit.ts
build-time entry override. Mirrors @ngaf/cockpit-shell scaffold pattern.

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

6 tests cover the rollup math: pre-complete state, fire-once-when-complete,
idempotent signals, 30-min window reset, duration_ms property on emit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ers + posthog init

Initializes posthog-js with memory persistence + parent-provided
distinct_id, subscribes to CHAT/AGENT/RENDER lifecycle tokens
(each optional — graceful no-op if absent), fires cockpit:* events
and marks signals on the ActivationAggregator.

6 tests cover init idempotency, capture format, missing-lifecycle
gracefulness, capability property stamping.

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

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

Each cockpit example's main.cockpit.ts calls this with its
AppComponent + appConfig. When URL params present, telemetry providers
are added; otherwise bootstraps pristine.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When the cockpit harness is not present (no URL params), no eager
import of posthog-js. Mirrors @ngaf/telemetry/browser silence pattern.
Stays green permanently.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add @angular/platform-browser, @ngaf/chat, @ngaf/langgraph, @ngaf/render
  to peerDependencies (consumed by harness + service).
- TestComponent in harness.spec.ts uses lib- prefix per project eslint rules.
- Lockfile updated to record posthog-js install for the new private lib.

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

Mirrors apps/website/src/lib/analytics/ structure. Memory-only session
UUID, shouldCaptureAnalytics guard with localhost gate + DO_NOT_TRACK
honoring, typed track() helper. ~10 tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Memory persistence + parent-side session UUID. Off on localhost by
default (NEXT_PUBLIC_COCKPIT_CAPTURE_LOCAL=true to override). Honors
DO_NOT_TRACK. Three new env vars documented in .env.example.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Properties: capability, category, from_capability.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Properties: capability, surface=code_mode, file_path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two surfaces: docs_code_snippet (inline code blocks) and agentic_prompt
(prompt callouts).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The iframe URL now carries the session UUID + capability slug + posthog
key + host so the Angular harness can correlate to the parent session.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three-line harness uses bootstrapWithCockpitHarness from
@ngaf/cockpit-telemetry. Pristine main.ts unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
cockpit/<...>/project.json gains a cockpit build that uses main.cockpit.ts
as the entry. apps/cockpit:serve-streaming now invokes serve:cockpit on
the example. Production build unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add main.cockpit.ts + cockpit build/serve configurations to the
remaining LangGraph cockpit examples (memory, durable-execution,
subgraphs, deployment-runtime, interrupts, persistence, time-travel).
Update apps/cockpit serve-* targets to use the :serve:cockpit config
so the iframe loads the harness-enabled build.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add main.cockpit.ts + cockpit build/serve configurations to all
Deep Agents cockpit examples (sandboxes, subagents, memory, planning,
filesystem, skills). Update apps/cockpit serve-* targets to use the
:serve:cockpit config so the iframe loads the harness-enabled build.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add main.cockpit.ts + cockpit build/serve configurations to all chat
cockpit examples (tool-calls, messages, subagents, input, a2ui,
theming, threads, interrupts, timeline, generative-ui, debug). The
timeline harness preserves the installEmbeddedTheme() call before
bootstrap. Chat examples are launched via the serve-example.ts script;
that script is updated in the render batch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add main.cockpit.ts + cockpit build/serve configurations to all render
cockpit examples (computed-functions, element-rendering, repeat-loops,
state-management, spec-rendering, registry). Also update the shared
serve-example.ts script so both --capability and --all modes launch
the harness-enabled :serve:cockpit configuration; this covers the
chat + render capabilities that don't have per-capability serve-*
targets in apps/cockpit/project.json.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
blove and others added 11 commits May 15, 2026 18:58
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Activation funnel is 5 signals per Spec 1C. ngaf:postinstall is its
own top-of-funnel metric, uncorrelated to cockpit sessions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
5 steps (dropped install_command_copied), 30-minute window. posthog_id
nulled to force create on next sync (PostHog will assign a new id).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
jsdom doesn't implement CSS.escape; code-mode copy handler calls it. Tests
passed but vitest flagged an unhandled error. Polyfill restores green nx
test target.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Rename cockpit:recipe_start → cockpit:recipe_opened (sidebar click)
- Add cockpit:mode_switched (Run/Code/Docs tab change)
- Add cockpit:code_copied (code mode, doc snippet, agentic prompt)
- Update cockpit-recipe-completion insight to use renamed event
- Document that shell events are funnel context, not activation steps

Addresses code-review finding on Spec 1C.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bug: CockpitTelemetryService.subscribeAgent() injected AGENT_LIFECYCLE
which is never provided in DI (agent() exposes lifecycle on its return
object). Three activation signals (transport_connected, thread_persisted,
interrupt_handled) never fired; activation funnel was unreachable.

Fix: Add AgentLifecycleRegistry to @ngaf/langgraph as an optional
service. agent() registers itself if the registry is provided.
provideCockpitTelemetry now provides the registry, and the service
subscribes to lifecycles reactively via a signal effect.

Addresses code-review item on Spec 1C.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Service is already provided by provideRender(). The redundant
providedIn:'root' caused both paths to resolve to the same singleton —
removing it makes the scope follow the consumer's provideRender() call
(sub-tree-friendly).

Addresses code-review minor item on Spec 1C.

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 3:51am

Request Review

Picks up CHAT_LIFECYCLE, AGENT_LIFECYCLE, RENDER_LIFECYCLE tokens +
AgentLifecycleRegistry from Spec 1C. CI drift check would fail without
this regeneration.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@blove blove merged commit 0539b84 into main May 16, 2026
13 of 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