Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
9cc2b2f
docs(gtm): spec for analytics-foundation-1c (cockpit instrumentation)
blove May 15, 2026
0acf439
docs(gtm): implementation plan for analytics-foundation-1c (cockpit i…
blove May 15, 2026
80d3ab5
feat(chat): add CHAT_LIFECYCLE InjectionToken + interface
blove May 15, 2026
0c265da
feat(chat): wire CHAT_LIFECYCLE in ChatComponent
blove May 15, 2026
c8e31d6
test(chat): add onUserSubmitted coverage + clarify clearThread doc
blove May 15, 2026
4096d27
feat(langgraph): add AGENT_LIFECYCLE InjectionToken + interface
blove May 15, 2026
a0fd5d2
feat(langgraph): wire AGENT_LIFECYCLE in agent.fn.ts
blove May 15, 2026
1084d1b
feat(render): add RENDER_LIFECYCLE token + service + wiring
blove May 15, 2026
841c136
feat(cockpit-telemetry): scaffold private Nx library
blove May 15, 2026
7fcf835
feat(cockpit-telemetry): config token + typed event names
blove May 15, 2026
0f2c07e
feat(cockpit-telemetry): readCockpitConfigFromIframe — URL param reader
blove May 15, 2026
4b0ca31
feat(cockpit-telemetry): ActivationAggregator — 5-signal rollup with …
blove May 15, 2026
eb5d007
feat(cockpit-telemetry): CockpitTelemetryService — lifecycle subscrib…
blove May 15, 2026
ca6a2f1
feat(cockpit-telemetry): provideCockpitTelemetry() EnvironmentProvide…
blove May 15, 2026
782b01d
feat(cockpit-telemetry): bootstrapWithCockpitHarness — main.cockpit.t…
blove May 15, 2026
29512cf
test(cockpit-telemetry): permanent browser silence contract test
blove May 15, 2026
65467a4
fix(cockpit-telemetry): declare peer deps + use lib- selector in spec
blove May 16, 2026
2a5bd94
feat(cockpit): analytics module — distinct-id, properties, events, cl…
blove May 16, 2026
44fb781
feat(cockpit): posthog-js initialization via instrumentation-client.ts
blove May 16, 2026
96c3477
feat(cockpit): fire cockpit:recipe_opened on sidebar capability click
blove May 16, 2026
97cc49a
feat(cockpit): fire cockpit:mode_switched on mode tab change
blove May 16, 2026
5f5aba3
feat(cockpit): fire cockpit:code_copied on Code mode copy
blove May 16, 2026
1c1ec0a
feat(cockpit): fire cockpit:code_copied on narrative docs copy buttons
blove May 16, 2026
4f4a6b3
feat(cockpit): RunMode appends cockpit_did/cockpit_cap to iframe src
blove May 16, 2026
18a0540
feat(cockpit-streaming): main.cockpit.ts harness entry
blove May 16, 2026
fe73fba
feat(cockpit-streaming): add cockpit build configuration
blove May 16, 2026
42bef08
feat(cockpit-langgraph): wire 7 examples to cockpit-telemetry harness
blove May 16, 2026
7aed5a5
feat(cockpit-deep-agents): wire 6 examples to cockpit-telemetry harness
blove May 16, 2026
80db2cc
feat(cockpit-chat): wire 11 examples to cockpit-telemetry harness
blove May 16, 2026
a3e5c40
feat(cockpit-render): wire 6 examples to cockpit-telemetry harness
blove May 16, 2026
1528a87
docs(website): chat/lifecycle.md — CHAT_LIFECYCLE signal docs
blove May 16, 2026
9a05d69
docs(website): langgraph/lifecycle.md — AGENT_LIFECYCLE signal docs
blove May 16, 2026
d94ef8a
docs(website): render/lifecycle.md — RENDER_LIFECYCLE signal docs
blove May 16, 2026
6cf4cc7
docs(website): link lifecycle pages from each lib's landing
blove May 16, 2026
3bf54aa
chore(gtm): drop install_command_copied + rename activation event
blove May 16, 2026
62fb0ec
chore(posthog): rename six-signal-activation-funnel → activation-funnel
blove May 16, 2026
11046ab
chore(posthog): developer-funnel references activation-funnel insight
blove May 16, 2026
f1722a8
test(cockpit): polyfill CSS.escape in jsdom test setup
blove May 16, 2026
f24fefb
chore(gtm): align taxonomy with implemented cockpit shell events
blove May 16, 2026
16ce15b
fix(langgraph,cockpit-telemetry): wire AGENT_LIFECYCLE via registry
blove May 16, 2026
df130f7
refactor(render): drop providedIn:root on RenderLifecycleService
blove May 16, 2026
e8e9a66
docs(website): regenerate api-docs.json for lifecycle additions
blove May 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ POSTHOG_PROJECT_ID=
# NGAF_TELEMETRY_SAMPLE_RATE=1.0
# DO_NOT_TRACK=1 # cross-vendor opt-out
# NGAF_TELEMETRY_DISABLED=1 # package-specific opt-out

# Cockpit shell analytics (apps/cockpit)
NEXT_PUBLIC_COCKPIT_POSTHOG_TOKEN=
NEXT_PUBLIC_COCKPIT_POSTHOG_HOST=https://us.i.posthog.com
NEXT_PUBLIC_COCKPIT_CAPTURE_LOCAL=false
20 changes: 20 additions & 0 deletions apps/cockpit/instrumentation-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
import posthog from 'posthog-js';
import { getCockpitSessionId } from './src/lib/analytics/distinct-id';
import { shouldCaptureAnalytics } from './src/lib/analytics/properties';

const token = process.env.NEXT_PUBLIC_COCKPIT_POSTHOG_TOKEN;
const captureLocal = process.env.NEXT_PUBLIC_COCKPIT_CAPTURE_LOCAL === 'true';
const host = typeof window === 'undefined' ? undefined : window.location.host;
const doNotTrack = typeof navigator !== 'undefined' && navigator.doNotTrack === '1';

if (shouldCaptureAnalytics({ token, captureLocal, host, doNotTrack })) {
posthog.init(token!, {
api_host: process.env.NEXT_PUBLIC_COCKPIT_POSTHOG_HOST ?? 'https://us.i.posthog.com',
persistence: 'memory',
bootstrap: { distinctID: getCockpitSessionId() },
autocapture: false,
capture_pageview: false,
defaults: '2026-01-30',
});
}
1 change: 1 addition & 0 deletions apps/cockpit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"clsx": "^2.1.1",
"marked": "^15.0.0",
"next": "~16.1.6",
"posthog-js": "^1.372.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tailwind-merge": "^2.5.0"
Expand Down
28 changes: 14 additions & 14 deletions apps/cockpit/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"options": {
"commands": [
"npx nx serve cockpit --port 4201",
"npx nx serve cockpit-langgraph-streaming-angular --port 4300",
"npx nx serve cockpit-langgraph-streaming-angular:serve:cockpit --port 4300",
"cd cockpit/langgraph/streaming/python && source $HOME/.local/bin/env 2>/dev/null; uv sync && uv run langgraph dev --port 8123"
],
"parallel": true
Expand All @@ -68,7 +68,7 @@
"options": {
"commands": [
"npx nx serve cockpit --port 4201",
"npx nx serve cockpit-langgraph-persistence-angular --port 4301",
"npx nx serve cockpit-langgraph-persistence-angular:serve:cockpit --port 4301",
"cd cockpit/langgraph/persistence/python && source $HOME/.local/bin/env 2>/dev/null; uv sync && uv run langgraph dev --port 8123"
],
"parallel": true
Expand All @@ -79,7 +79,7 @@
"options": {
"commands": [
"npx nx serve cockpit --port 4201",
"npx nx serve cockpit-langgraph-interrupts-angular --port 4302",
"npx nx serve cockpit-langgraph-interrupts-angular:serve:cockpit --port 4302",
"cd cockpit/langgraph/interrupts/python && source $HOME/.local/bin/env 2>/dev/null; uv sync && uv run langgraph dev --port 8123"
],
"parallel": true
Expand All @@ -90,7 +90,7 @@
"options": {
"commands": [
"npx nx serve cockpit --port 4201",
"npx nx serve cockpit-langgraph-memory-angular --port 4303",
"npx nx serve cockpit-langgraph-memory-angular:serve:cockpit --port 4303",
"cd cockpit/langgraph/memory/python && source $HOME/.local/bin/env 2>/dev/null; uv sync && uv run langgraph dev --port 8123"
],
"parallel": true
Expand All @@ -101,7 +101,7 @@
"options": {
"commands": [
"npx nx serve cockpit --port 4201",
"npx nx serve cockpit-langgraph-durable-execution-angular --port 4304",
"npx nx serve cockpit-langgraph-durable-execution-angular:serve:cockpit --port 4304",
"cd cockpit/langgraph/durable-execution/python && source $HOME/.local/bin/env 2>/dev/null; uv sync && uv run langgraph dev --port 8123"
],
"parallel": true
Expand All @@ -112,7 +112,7 @@
"options": {
"commands": [
"npx nx serve cockpit --port 4201",
"npx nx serve cockpit-langgraph-subgraphs-angular --port 4305",
"npx nx serve cockpit-langgraph-subgraphs-angular:serve:cockpit --port 4305",
"cd cockpit/langgraph/subgraphs/python && source $HOME/.local/bin/env 2>/dev/null; uv sync && uv run langgraph dev --port 8123"
],
"parallel": true
Expand All @@ -123,7 +123,7 @@
"options": {
"commands": [
"npx nx serve cockpit --port 4201",
"npx nx serve cockpit-langgraph-time-travel-angular --port 4306",
"npx nx serve cockpit-langgraph-time-travel-angular:serve:cockpit --port 4306",
"cd cockpit/langgraph/time-travel/python && source $HOME/.local/bin/env 2>/dev/null; uv sync && uv run langgraph dev --port 8123"
],
"parallel": true
Expand All @@ -134,7 +134,7 @@
"options": {
"commands": [
"npx nx serve cockpit --port 4201",
"npx nx serve cockpit-langgraph-deployment-runtime-angular --port 4307",
"npx nx serve cockpit-langgraph-deployment-runtime-angular:serve:cockpit --port 4307",
"cd cockpit/langgraph/deployment-runtime/python && source $HOME/.local/bin/env 2>/dev/null; uv sync && uv run langgraph dev --port 8123"
],
"parallel": true
Expand All @@ -145,7 +145,7 @@
"options": {
"commands": [
"npx nx serve cockpit --port 4201",
"npx nx serve cockpit-deep-agents-planning-angular --port 4310",
"npx nx serve cockpit-deep-agents-planning-angular:serve:cockpit --port 4310",
"cd cockpit/deep-agents/planning/python && source $HOME/.local/bin/env 2>/dev/null; uv sync && uv run langgraph dev --port 8123"
],
"parallel": true
Expand All @@ -156,7 +156,7 @@
"options": {
"commands": [
"npx nx serve cockpit --port 4201",
"npx nx serve cockpit-deep-agents-filesystem-angular --port 4311",
"npx nx serve cockpit-deep-agents-filesystem-angular:serve:cockpit --port 4311",
"cd cockpit/deep-agents/filesystem/python && source $HOME/.local/bin/env 2>/dev/null; uv sync && uv run langgraph dev --port 8123"
],
"parallel": true
Expand All @@ -167,7 +167,7 @@
"options": {
"commands": [
"npx nx serve cockpit --port 4201",
"npx nx serve cockpit-deep-agents-subagents-angular --port 4312",
"npx nx serve cockpit-deep-agents-subagents-angular:serve:cockpit --port 4312",
"cd cockpit/deep-agents/subagents/python && source $HOME/.local/bin/env 2>/dev/null; uv sync && uv run langgraph dev --port 8123"
],
"parallel": true
Expand All @@ -178,7 +178,7 @@
"options": {
"commands": [
"npx nx serve cockpit --port 4201",
"npx nx serve cockpit-deep-agents-memory-angular --port 4313",
"npx nx serve cockpit-deep-agents-memory-angular:serve:cockpit --port 4313",
"cd cockpit/deep-agents/memory/python && source $HOME/.local/bin/env 2>/dev/null; uv sync && uv run langgraph dev --port 8123"
],
"parallel": true
Expand All @@ -189,7 +189,7 @@
"options": {
"commands": [
"npx nx serve cockpit --port 4201",
"npx nx serve cockpit-deep-agents-skills-angular --port 4314",
"npx nx serve cockpit-deep-agents-skills-angular:serve:cockpit --port 4314",
"cd cockpit/deep-agents/skills/python && source $HOME/.local/bin/env 2>/dev/null; uv sync && uv run langgraph dev --port 8123"
],
"parallel": true
Expand All @@ -200,7 +200,7 @@
"options": {
"commands": [
"npx nx serve cockpit --port 4201",
"npx nx serve cockpit-deep-agents-sandboxes-angular --port 4315",
"npx nx serve cockpit-deep-agents-sandboxes-angular:serve:cockpit --port 4315",
"cd cockpit/deep-agents/sandboxes/python && source $HOME/.local/bin/env 2>/dev/null; uv sync && uv run langgraph dev --port 8123"
],
"parallel": true
Expand Down
4 changes: 2 additions & 2 deletions apps/cockpit/scripts/serve-example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ process.on('SIGTERM', cleanup);
run('cockpit', 'npx nx serve cockpit --port 4201', '36');

if (allMode) {
capabilities.forEach((c) => run(c.id, `npx nx serve ${c.angularProject} --port ${c.port}`, '33'));
capabilities.forEach((c) => run(c.id, `npx nx serve ${c.angularProject}:serve:cockpit --port ${c.port}`, '33'));
console.log('\n🚀 Starting cockpit + all 14 examples\n');
} else {
const cap = findCapability(capabilityArg!);
if (!cap) { console.error(`Unknown: ${capabilityArg}`); process.exit(1); }
run(cap.id, `npx nx serve ${cap.angularProject} --port ${cap.port}`, '33');
run(cap.id, `npx nx serve ${cap.angularProject}:serve:cockpit --port ${cap.port}`, '33');
run(`${cap.id}-py`, `cd ${cap.pythonDir} && source $HOME/.local/bin/env 2>/dev/null; uv sync && uv run langgraph dev --port 8123`, '35');
console.log(`\n🚀 ${cap.id}: cockpit=4201 angular=${cap.port} langgraph=8123\n`);
}
5 changes: 4 additions & 1 deletion apps/cockpit/src/components/cockpit-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export function CockpitShell({
modes={PRIMARY_MODES}
activeMode={activeMode}
onChange={setActiveMode}
capability={entry.topic}
/>
</div>
</header>
Expand All @@ -112,6 +113,7 @@ export function CockpitShell({
<RunMode
entryTitle={entryTitle}
runtimeUrl={contentBundle.runtimeUrl}
capabilitySlug={entry.topic}
/>
</div>
{activeMode === 'Code' ? (
Expand All @@ -121,10 +123,11 @@ export function CockpitShell({
backendAssetPaths={backendAssetPaths}
codeFiles={contentBundle.codeFiles}
promptFiles={contentBundle.promptFiles}
capability={entry.topic}
/>
) : null}
{activeMode === 'Docs' ? (
<NarrativeDocs narrativeDocs={contentBundle.narrativeDocs} />
<NarrativeDocs narrativeDocs={contentBundle.narrativeDocs} capability={entry.topic} />
) : null}
{activeMode === 'API' ? (
<ApiMode docSections={contentBundle.docSections} />
Expand Down
45 changes: 44 additions & 1 deletion apps/cockpit/src/components/code-mode/code-mode.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
import React from 'react';
import { act } from 'react';
import { createRoot } from 'react-dom/client';
import { afterEach, describe, expect, it } from 'vitest';
import { afterEach, describe, expect, it, vi } from 'vitest';

vi.mock('../../lib/analytics/client', () => ({ track: vi.fn() }));

import { track } from '../../lib/analytics/client';
import { CodeMode } from './code-mode';

describe('CodeMode', () => {
Expand All @@ -14,6 +18,7 @@ describe('CodeMode', () => {
root?.unmount();
});
container?.remove();
vi.clearAllMocks();
});

it('renders Shiki-highlighted HTML for the active file', () => {
Expand Down Expand Up @@ -111,4 +116,42 @@ describe('CodeMode', () => {

expect(container.textContent).toContain('You are a helpful assistant.');
});

it('fires cockpit:code_copied when the Copy button is clicked', () => {
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);

Object.assign(navigator, {
clipboard: { writeText: vi.fn(() => Promise.resolve()) },
});

act(() => {
root!.render(
<CodeMode
entryTitle="Test"
codeAssetPaths={['src/app.tsx']}
backendAssetPaths={[]}
codeFiles={{ 'src/app.tsx': '<pre class="shiki"><code>const x = 1;</code></pre>' }}
promptFiles={{}}
capability="streaming"
/>,
);
});

const copyBtn = container.querySelector(
'button[aria-label^="Copy"]',
) as HTMLButtonElement | null;
expect(copyBtn).not.toBeNull();

act(() => {
copyBtn!.click();
});

expect(track).toHaveBeenCalledWith('cockpit:code_copied', {
capability: 'streaming',
surface: 'code_mode',
file_path: 'src/app.tsx',
});
});
});
21 changes: 18 additions & 3 deletions apps/cockpit/src/components/code-mode/code-mode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,28 @@

import React from 'react';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { track } from '../../lib/analytics/client';

interface CodeModeProps {
entryTitle: string;
codeAssetPaths: readonly string[];
backendAssetPaths: readonly string[];
codeFiles: Record<string, string>;
promptFiles: Record<string, string>;
capability?: string;
}

const getTabLabel = (path: string): string => path.split('/').pop() ?? path;

function CodeFileContent({ path, content }: { path: string; content: string | undefined }) {
function CodeFileContent({
path,
content,
capability,
}: {
path: string;
content: string | undefined;
capability?: string;
}) {
if (!content) {
return <p className="text-sm text-[var(--ds-text-muted)]">No source available for {getTabLabel(path)}</p>;
}
Expand All @@ -38,6 +48,11 @@ function CodeFileContent({ path, content }: { path: string; content: string | un
<button
aria-label={`Copy ${getTabLabel(path)}`}
onClick={() => {
track('cockpit:code_copied', {
capability,
surface: 'code_mode',
file_path: path,
});
const el = document.querySelector(`[data-code-path="${CSS.escape(path)}"] pre code`);
if (el) navigator.clipboard.writeText(el.textContent ?? '');
}}
Expand All @@ -57,7 +72,7 @@ function CodeFileContent({ path, content }: { path: string; content: string | un
);
}

export function CodeMode({ entryTitle, codeAssetPaths, backendAssetPaths, codeFiles, promptFiles }: CodeModeProps) {
export function CodeMode({ entryTitle, codeAssetPaths, backendAssetPaths, codeFiles, promptFiles, capability }: CodeModeProps) {
const promptPaths = Object.keys(promptFiles);
const allPaths = [...codeAssetPaths, ...backendAssetPaths, ...promptPaths];

Expand Down Expand Up @@ -94,7 +109,7 @@ export function CodeMode({ entryTitle, codeAssetPaths, backendAssetPaths, codeFi

{[...codeAssetPaths, ...backendAssetPaths].map((path) => (
<TabsContent key={path} value={path} className="flex-1 overflow-auto mt-4">
<CodeFileContent path={path} content={codeFiles[path]} />
<CodeFileContent path={path} content={codeFiles[path]} capability={capability} />
</TabsContent>
))}

Expand Down
43 changes: 42 additions & 1 deletion apps/cockpit/src/components/modes/mode-switcher.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
import React, { useState } from 'react';
import { createRoot } from 'react-dom/client';
import { act } from 'react';
import { afterEach, describe, expect, it } from 'vitest';
import { afterEach, describe, expect, it, vi } from 'vitest';

vi.mock('../../lib/analytics/client', () => ({ track: vi.fn() }));

import { track } from '../../lib/analytics/client';
import { ModeSwitcher } from './mode-switcher';

const MODES = ['Run', 'Code'] as const;
Expand Down Expand Up @@ -31,6 +35,7 @@ describe('ModeSwitcher', () => {
root?.unmount();
});
container?.remove();
vi.clearAllMocks();
});

it('shows mode buttons with Run active by default', () => {
Expand Down Expand Up @@ -70,4 +75,40 @@ describe('ModeSwitcher', () => {
expect(container.textContent).toContain('Code content');
expect(container.textContent).not.toContain('Run content');
});

it('fires cockpit:mode_switched when capability prop is set and mode changes', () => {
container = document.createElement('div');
document.body.append(container);
root = createRoot(container);

function Harness() {
const [active, setActive] = useState<(typeof MODES)[number]>('Run');
return (
<ModeSwitcher
modes={MODES}
activeMode={active}
onChange={setActive}
capability="streaming"
/>
);
}

act(() => {
root!.render(<Harness />);
});

const codeButton = Array.from(container.querySelectorAll('[data-mode-btn]')).find(
(b) => b.textContent === 'Code',
) as HTMLElement;

act(() => {
codeButton.click();
});

expect(track).toHaveBeenCalledWith('cockpit:mode_switched', {
capability: 'streaming',
from_mode: 'run',
to_mode: 'code',
});
});
});
Loading
Loading