From f709ac74b2106dbd715ed2a3d169ba0e1659419f Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Thu, 17 Sep 2026 13:19:29 +0200 Subject: [PATCH 1/2] feat: embed AI Basic Chat in the docs sandbox Register examples/react/basic-chat as a WebContainer example, rewrite workspace:* for @tanstack/ai packages, and mount that player from a docs comment. --- .../examples/ClientExampleDocEmbed.client.tsx | 93 ++++++++++++++ .../examples/ClientExampleDocEmbed.tsx | 106 ++++++++++++++++ src/components/markdown/MdComponents.tsx | 17 +++ src/utils/client-example-config.ts | 13 ++ src/utils/docs.functions.ts | 19 ++- src/utils/repository-example.ts | 120 ++++++++++++++++++ src/utils/sandbox.ts | 4 + tests/client-example-doc-embed.test.ts | 44 +++++++ tests/repo-path.test.ts | 6 + tests/repository-example.test.ts | 31 +++++ tests/rewrite-workspace-protocol.test.ts | 75 +++++++++++ 11 files changed, 524 insertions(+), 4 deletions(-) create mode 100644 src/components/examples/ClientExampleDocEmbed.client.tsx create mode 100644 src/components/examples/ClientExampleDocEmbed.tsx create mode 100644 tests/client-example-doc-embed.test.ts create mode 100644 tests/rewrite-workspace-protocol.test.ts diff --git a/src/components/examples/ClientExampleDocEmbed.client.tsx b/src/components/examples/ClientExampleDocEmbed.client.tsx new file mode 100644 index 000000000..c877a6c29 --- /dev/null +++ b/src/components/examples/ClientExampleDocEmbed.client.tsx @@ -0,0 +1,93 @@ +import * as React from 'react' +import { getLibrary } from '~/libraries' +import { getClientExampleConfig } from '~/utils/client-example-config' +import { fetchClientExampleFiles } from '~/utils/docs' +import type { ExampleDefinition } from '~/utils/example-workspace' +import { createRepositoryExampleDefinition } from '~/utils/repository-example' + +const LazyExampleWorkbench = React.lazy(() => + import('~/components/examples/ExampleWorkbench.client').then((module) => ({ + default: module.ExampleWorkbench, + })), +) + +export function ClientExampleDocEmbedClient({ + fallback, + framework, + library, + slug, + version, +}: { + fallback: React.ReactNode + framework: string + library: string + slug: string + version: string +}) { + const config = getClientExampleConfig({ + framework, + libraryId: library, + slug, + version, + }) + const [definition, setDefinition] = React.useState() + + React.useEffect(() => { + let cancelled = false + setDefinition(undefined) + + const currentConfig = getClientExampleConfig({ + framework, + libraryId: library, + slug, + version, + }) + if (!currentConfig) return + + void fetchClientExampleFiles({ + data: { + example: currentConfig.slug, + framework: currentConfig.framework, + libraryId: currentConfig.libraryId, + version, + }, + }) + .then((result) => { + if (cancelled || !result.success) return + + try { + const nextDefinition = createRepositoryExampleDefinition({ + binaryFiles: result.binaryFiles, + entry: currentConfig.entry, + files: result.files, + id: `${currentConfig.libraryId}-${currentConfig.framework}-${currentConfig.slug}`, + runtime: currentConfig.runtime, + title: currentConfig.slug, + }) + if (!cancelled) setDefinition(nextDefinition) + } catch { + if (!cancelled) setDefinition(undefined) + } + }) + .catch(() => { + if (!cancelled) setDefinition(undefined) + }) + + return () => { + cancelled = true + } + }, [framework, library, slug, version]) + + if (!config || !definition) return fallback + + return ( + + + + ) +} diff --git a/src/components/examples/ClientExampleDocEmbed.tsx b/src/components/examples/ClientExampleDocEmbed.tsx new file mode 100644 index 000000000..c74b07800 --- /dev/null +++ b/src/components/examples/ClientExampleDocEmbed.tsx @@ -0,0 +1,106 @@ +import { ClientOnly, useParams } from '@tanstack/react-router' +import * as React from 'react' +import { getClientExampleConfig } from '~/utils/client-example-config' + +const LazyClientExampleDocEmbed = React.lazy(() => + import('./ClientExampleDocEmbed.client').then((module) => ({ + default: module.ClientExampleDocEmbedClient, + })), +) + +const clientExampleAttributeKeys = ['framework', 'library', 'slug'] +const clientExampleAttributePattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/ + +export function parseClientExampleAttributes(value: unknown) { + if ( + !isRecord(value) || + !hasOnlyKeys(value, clientExampleAttributeKeys) || + typeof value.library !== 'string' || + typeof value.framework !== 'string' || + typeof value.slug !== 'string' || + !isClientExampleAttribute(value.library) || + !isClientExampleAttribute(value.framework) || + !isClientExampleAttribute(value.slug) + ) { + return null + } + + return { + library: value.library, + framework: value.framework, + slug: value.slug, + } +} + +export function ClientExampleDocEmbed({ + framework, + library, + slug, +}: { + framework: string + library: string + slug: string +}) { + const { version } = useParams({ strict: false }) + const resolvedVersion = version ?? 'latest' + const config = getClientExampleConfig({ + framework, + libraryId: library, + slug, + version: resolvedVersion, + }) + + if (!config) return null + + const fallback = + + return ( +
+ + + + + +
+ ) +} + +function ClientExampleDocEmbedFallback({ slug }: { slug: string }) { + return ( +
+
+ {slug} +
+