diff --git a/apps/website/src/lib/analytics/events.ts b/apps/website/src/lib/analytics/events.ts index 7fbbfd367..662898ed6 100644 --- a/apps/website/src/lib/analytics/events.ts +++ b/apps/website/src/lib/analytics/events.ts @@ -90,7 +90,14 @@ export type CtaId = // MediumSwitcher derives ids from section id + medium key at runtime | `medium_${string}`; -export type AnalyticsLibrary = 'langgraph' | 'render' | 'chat' | 'ag-ui' | 'unknown'; +export type AnalyticsLibrary = + | 'langgraph' + | 'render' + | 'chat' + | 'ag-ui' + | 'deep-agents' + | 'runtimes' + | 'unknown'; export type WhitepaperId = 'overview' | 'angular' | 'render' | 'chat'; diff --git a/apps/website/src/lib/cockpit-links.spec.ts b/apps/website/src/lib/cockpit-links.spec.ts index 70688ff1e..7b1ec599f 100644 --- a/apps/website/src/lib/cockpit-links.spec.ts +++ b/apps/website/src/lib/cockpit-links.spec.ts @@ -1,5 +1,6 @@ -import { cockpitManifest } from '@threadplane/cockpit-registry'; +import { COCKPIT_DOCS_LINKS, cockpitManifest } from '@threadplane/cockpit-registry'; import { describe, expect, it } from 'vitest'; +import { findDocsPage } from './docs-config'; import { buildCockpitHandoffProperties, buildCockpitModeHref, @@ -10,6 +11,54 @@ import { describe('Docs-to-Cockpit links', () => { it('contains the exhaustive first-iteration mapping set', () => { expect(docsCockpitMappings).toEqual({ + 'deep-agents/getting-started/introduction': { + product: 'deep-agents', section: 'getting-started', topic: 'overview', page: 'overview', language: 'python', + }, + 'deep-agents/capabilities/planning': { + product: 'deep-agents', section: 'core-capabilities', topic: 'planning', page: 'overview', language: 'python', + }, + 'deep-agents/capabilities/filesystem': { + product: 'deep-agents', section: 'core-capabilities', topic: 'filesystem', page: 'overview', language: 'python', + }, + 'deep-agents/capabilities/subagents': { + product: 'deep-agents', section: 'core-capabilities', topic: 'subagents', page: 'overview', language: 'python', + }, + 'deep-agents/capabilities/memory': { + product: 'deep-agents', section: 'core-capabilities', topic: 'memory', page: 'overview', language: 'python', + }, + 'deep-agents/capabilities/skills': { + product: 'deep-agents', section: 'core-capabilities', topic: 'skills', page: 'overview', language: 'python', + }, + 'runtimes/getting-started/introduction': { + product: 'runtimes', section: 'getting-started', topic: 'overview', page: 'overview', language: 'python', + }, + 'runtimes/aws-strands/overview': { + product: 'runtimes', section: 'core-capabilities', topic: 'aws-strands', page: 'overview', language: 'python', + }, + 'runtimes/aws-strands/quickstart': { + product: 'runtimes', section: 'core-capabilities', topic: 'aws-strands', page: 'overview', language: 'python', + }, + 'runtimes/aws-strands/how-it-connects': { + product: 'runtimes', section: 'core-capabilities', topic: 'aws-strands', page: 'overview', language: 'python', + }, + 'runtimes/microsoft-agent-framework/overview': { + product: 'runtimes', section: 'core-capabilities', topic: 'microsoft-agent-framework', page: 'overview', language: 'python', + }, + 'runtimes/microsoft-agent-framework/quickstart': { + product: 'runtimes', section: 'core-capabilities', topic: 'microsoft-agent-framework', page: 'overview', language: 'python', + }, + 'runtimes/microsoft-agent-framework/how-it-connects': { + product: 'runtimes', section: 'core-capabilities', topic: 'microsoft-agent-framework', page: 'overview', language: 'python', + }, + 'runtimes/mastra/overview': { + product: 'runtimes', section: 'core-capabilities', topic: 'mastra', page: 'overview', language: 'python', + }, + 'runtimes/mastra/quickstart': { + product: 'runtimes', section: 'core-capabilities', topic: 'mastra', page: 'overview', language: 'python', + }, + 'runtimes/mastra/how-it-connects': { + product: 'runtimes', section: 'core-capabilities', topic: 'mastra', page: 'overview', language: 'python', + }, 'langgraph/guides/streaming': { product: 'langgraph', section: 'core-capabilities', topic: 'streaming', page: 'overview', language: 'python', }, @@ -124,4 +173,85 @@ describe('Docs-to-Cockpit links', () => { )).toBe(true); } }); + + // A typo'd key is invisible at runtime: the page just never resolves and the + // reader silently keeps the Cockpit-home fallback. Pin the keys to the real + // content tree so a typo (or a docs rename) fails here instead. + it('keys every mapping to a docs page that exists', () => { + for (const key of Object.keys(docsCockpitMappings)) { + const [library, section, slug] = key.split('/'); + expect(findDocsPage(library, section, slug), key).toBeDefined(); + } + }); + + // The two directions are separate tables in separate packages. Where the + // Cockpit side already points at a docs page, that page must point back at + // the same capability, or the round trip lands somewhere else. + it('round-trips the deep-agents and runtimes links back to their capability', () => { + const forward = Object.entries(COCKPIT_DOCS_LINKS).filter(([key]) => + key.startsWith('deep-agents/') || key.startsWith('runtimes/'), + ); + expect(forward).toHaveLength(10); + + for (const [key, docsPath] of forward) { + const [product, section, topic] = key.split('/'); + const [library, docsSection, slug] = docsPath.replace('/docs/', '').split('/'); + + expect(resolveCockpitIdentity(library, docsSection, slug), key).toEqual( + expect.objectContaining({ product, section, topic }), + ); + } + }); + + it('reports deep-agents and runtimes as themselves, not unknown', () => { + expect( + buildCockpitHandoffProperties( + { library: 'deep-agents', section: 'capabilities', slug: 'planning' }, + 'Run', + ), + ).toEqual({ + library: 'deep-agents', + source_section: 'capabilities', + source_slug: 'planning', + destination_product: 'deep-agents', + destination_capability: 'planning', + requested_mode: 'run', + mapped: true, + }); + + expect( + buildCockpitHandoffProperties( + { library: 'runtimes', section: 'mastra', slug: 'quickstart' }, + 'Code', + ).library, + ).toBe('runtimes'); + }); + + // Every page of a runtime's docs section shares that runtime's one demo, so + // a reader on the quickstart gets the same handoff as one on the overview. + it('sends every page of a runtime section to that runtime demo', () => { + for (const slug of ['overview', 'quickstart', 'how-it-connects']) { + expect( + buildCockpitModeHref( + { library: 'runtimes', section: 'aws-strands', slug }, + 'Run', + 'https://cockpit.example', + ), + ).toBe( + 'https://cockpit.example/runtimes/core-capabilities/aws-strands/overview/python?mode=run', + ); + } + }); + + it('sends a deep-agents capability page to its capability demo', () => { + expect( + buildCockpitModeHref( + { library: 'deep-agents', section: 'capabilities', slug: 'skills' }, + 'Code', + 'https://cockpit.example', + ), + ).toBe( + 'https://cockpit.example/deep-agents/core-capabilities/skills/overview/python?mode=code', + ); + }); }); diff --git a/apps/website/src/lib/cockpit-links.ts b/apps/website/src/lib/cockpit-links.ts index 13d81d9cf..566e25b1b 100644 --- a/apps/website/src/lib/cockpit-links.ts +++ b/apps/website/src/lib/cockpit-links.ts @@ -11,7 +11,149 @@ export interface DocsIdentity { export const COCKPIT_ENVIRONMENT_LABEL = process.env.NEXT_PUBLIC_COCKPIT_ENVIRONMENT_LABEL ?? 'Shared development'; +/** + * Documentation page -> Cockpit capability (the reverse of + * `COCKPIT_DOCS_LINKS` in `@threadplane/cockpit-registry`). + * + * Keyed by `${library}/${section}/${slug}` — the three segments of the docs + * route, `/docs//
/`. Values are a full Cockpit + * identity, because the Cockpit route carries five segments; the extra two are + * fixed (`page: 'overview'`, `language: 'python'`) for every manifest entry + * today, so they are written out rather than derived. + * + * Rules for this table: + * + * - An unmapped page is not an error. `resolveCockpitIdentity` returns null and + * the control plane sends the reader to Cockpit home instead of guessing a + * URL — the five-segment formula 404s for pages that have no demo. + * - Several docs pages may share one Cockpit target. The runtimes library + * documents each runtime across three pages (overview, quickstart, + * how-it-connects) that one demo serves equally well. + * - An `introduction` maps to its product's `getting-started/overview` entry. + * Those entries are `entryKind: 'docs-only'` in the manifest, so Run mode + * lands on the product's Cockpit overview rather than a live demo — still a + * better destination than Cockpit home, and it keeps the Capability row + * truthful. + * - The spec pins this object exactly and cross-checks every target against + * `cockpitManifest`, so a Cockpit topic rename breaks the test rather than + * the link. + */ export const docsCockpitMappings = { + 'deep-agents/getting-started/introduction': { + product: 'deep-agents', + section: 'getting-started', + topic: 'overview', + page: 'overview', + language: 'python', + }, + 'deep-agents/capabilities/planning': { + product: 'deep-agents', + section: 'core-capabilities', + topic: 'planning', + page: 'overview', + language: 'python', + }, + 'deep-agents/capabilities/filesystem': { + product: 'deep-agents', + section: 'core-capabilities', + topic: 'filesystem', + page: 'overview', + language: 'python', + }, + 'deep-agents/capabilities/subagents': { + product: 'deep-agents', + section: 'core-capabilities', + topic: 'subagents', + page: 'overview', + language: 'python', + }, + 'deep-agents/capabilities/memory': { + product: 'deep-agents', + section: 'core-capabilities', + topic: 'memory', + page: 'overview', + language: 'python', + }, + 'deep-agents/capabilities/skills': { + product: 'deep-agents', + section: 'core-capabilities', + topic: 'skills', + page: 'overview', + language: 'python', + }, + 'runtimes/getting-started/introduction': { + product: 'runtimes', + section: 'getting-started', + topic: 'overview', + page: 'overview', + language: 'python', + }, + 'runtimes/aws-strands/overview': { + product: 'runtimes', + section: 'core-capabilities', + topic: 'aws-strands', + page: 'overview', + language: 'python', + }, + 'runtimes/aws-strands/quickstart': { + product: 'runtimes', + section: 'core-capabilities', + topic: 'aws-strands', + page: 'overview', + language: 'python', + }, + 'runtimes/aws-strands/how-it-connects': { + product: 'runtimes', + section: 'core-capabilities', + topic: 'aws-strands', + page: 'overview', + language: 'python', + }, + 'runtimes/microsoft-agent-framework/overview': { + product: 'runtimes', + section: 'core-capabilities', + topic: 'microsoft-agent-framework', + page: 'overview', + language: 'python', + }, + 'runtimes/microsoft-agent-framework/quickstart': { + product: 'runtimes', + section: 'core-capabilities', + topic: 'microsoft-agent-framework', + page: 'overview', + language: 'python', + }, + 'runtimes/microsoft-agent-framework/how-it-connects': { + product: 'runtimes', + section: 'core-capabilities', + topic: 'microsoft-agent-framework', + page: 'overview', + language: 'python', + }, + // The Mastra demo's backend is a Node AG-UI service, but the Cockpit route + // is `python` like every other manifest entry — the language segment names + // the Cockpit lane, not the runtime. + 'runtimes/mastra/overview': { + product: 'runtimes', + section: 'core-capabilities', + topic: 'mastra', + page: 'overview', + language: 'python', + }, + 'runtimes/mastra/quickstart': { + product: 'runtimes', + section: 'core-capabilities', + topic: 'mastra', + page: 'overview', + language: 'python', + }, + 'runtimes/mastra/how-it-connects': { + product: 'runtimes', + section: 'core-capabilities', + topic: 'mastra', + page: 'overview', + language: 'python', + }, 'langgraph/guides/streaming': { product: 'langgraph', section: 'core-capabilities', @@ -77,6 +219,8 @@ const toAnalyticsLibrary = (library: string): AnalyticsLibrary => { case 'render': case 'chat': case 'ag-ui': + case 'deep-agents': + case 'runtimes': return library; default: return 'unknown';