Full headless surface — a hand-rolled CopilotChat replacement that wires
every render hook on top of shadcn/ui primitives. Visual chrome matches
Headless: Simple so the two read as a paired sibling demo.
Architecture is progressive-disclosure: the entry file is a 30-line
HeadlessCompleteRoot that enumerates capabilities, each registered via
a focused hook module:
page.tsx
hooks/
use-tool-renderers.tsx — useRenderTool x3, useDefaultRenderTool
use-frontend-components.ts — useComponent (highlight_note)
use-headless-suggestions.ts — useConfigureSuggestions
chat/
chat.tsx, header.tsx, empty-state.tsx, composer.tsx,
suggestion-bar.tsx, message-list.tsx, message-user.tsx,
message-assistant.tsx, message-activity.tsx, typing-indicator.tsx
attachments/use-attachments-config.ts + attachment-preview.tsx
tools/weather-card.tsx, stock-card.tsx, chart-card.tsx,
generic-tool-card.tsx, highlight-note.tsx
Backend (src/agents/headless_complete.py): get_weather, get_stock_price,
get_revenue_chart tools; the chart tool replaces the previous Excalidraw
"Sketch a diagram" suggestion (MCP capability stays wired).
Bug fixes folded in:
- Tool-call cards stuck "running" forever — message-list.tsx indexes
role:"tool" messages by toolCallId and passes the matching ToolMessage
to renderToolCall so cards advance to "complete".
- Empty state was hugging the top of the viewport — Radix ScrollArea
wraps content in a `display: table` div that breaks h-full propagation.
Empty state now renders OUTSIDE the ScrollArea.
- SuggestionBar duplicated the empty-state prompts on first paint.
Hidden until the conversation starts.
Minimum-viable headless chat that wires only `useAgent` + `useCopilotKit`,
dressed in shadcn/ui primitives. Five small single-purpose files so a
reader can grok the surface in under a minute:
page.tsx — provider + <Chat />, ~10 lines
chat.tsx — useAgent + useCopilotKit + send loop
composer.tsx — Textarea + send button
empty-state.tsx — sparkles + sample prompts
message-bubble.tsx + typing-indicator.tsx — render pieces
Wires runtimeUrl="/api/copilotkit" + agent="headless-simple" against
the neutral assistant graph registered in route.ts.
Also adds the v2 catch-all route at copilotkit-mcp-apps/[[...slug]]/route.ts
(used by Headless: Complete in the next commit). v2 hooks POST to subpaths
like /v2/agent/run; the previous flat route 404'd, leaving headless demos
stuck on "Thinking…".
Foundational layer that the per-demo work in subsequent commits builds on.
- Manifest-driven landing page (src/app/page.tsx) — auto-generated grid
of demo cards from manifest.yaml, grouped by tag with explicit ordering
for chat-ui / interactivity / generative-ui / agent-state / multi-agent
/ headless / platform.
- Per-demo titles (src/middleware.ts + src/app/demos/layout.tsx).
- Diagnostic console gated to NODE_ENV=production in app/layout.tsx so
deployed showcases surface uncaught errors and iframe context.
- src/app/globals.css — Tailwind v4 @theme inline block that maps
showcase CSS variables into Tailwind theme tokens (without it, shadcn
utilities like bg-muted / text-foreground compile to nothing). body
intentionally NOT given width:100% so <CopilotSidebar /> can shrink
the document via marginInlineEnd.
- shadcn primitives under src/components/ui/ + src/lib/utils.ts.
- tsconfig.json — @/* path alias rooted at src/.
Removed:
- src/app/copilotkit-overrides.css (global override layer, superseded
by per-demo theming)
- src/app/api/copilotkit-mcp-apps/route.ts (replaced with
[[...slug]]/route.ts so v2 subpath POSTs like /v2/agent/run resolve)
Switch CLI command to `npx copilotkit@latest create --framework <id>` on all 8
framework quickstarts. Bypasses the unfiltered 18-framework picker and skips
the EIP prompt (which is mutually exclusive with --framework and today
scaffolds langgraph-python-threads regardless of which framework page the
user came from).
For LangGraph: keep an EIP callout since EIP=Yes does scaffold a LangGraph-
Python project today; note that threads support for other frameworks is
coming. For Microsoft Agent Framework and LangGraph (multi-variant): show
both --framework <variant> commands.
Apply the canonical V2 form:
- \`<CopilotKit>\` from \`@copilotkit/react-core\` (root, not /v2).
- styles from \`@copilotkit/react-core/styles.css\` (not react-ui/v2).
- drop \`@copilotkit/react-ui\` from npm install commands.
Add sample-audio-button region markers around the SampleAudioButton
component in the built-in-agent voice demo so the
<Snippet cell="voice" region="sample-audio-button" /> reference in
shell-docs/voice.mdx resolves to real code instead of a missing-snippet
warning.
Comment-only change; no runtime behavior change. Mirrors the canonical
in-place region pattern already used by langgraph-typescript, mastra,
ms-agent-python, claude-sdk-typescript, llamaindex, spring-ai,
pydantic-ai, claude-sdk-python, and crewai-crews per the Mastra Round
Pattern Decisions playbook.
## Summary
- The dynamic shields.io license badge in the top-of-README badge row
sometimes renders as **invalid** on github.com (I suspect some caching
or something but not gonna spend the time digging in).
- Downloaded the rendered SVG once and vendored it at
`assets/license-badge.svg`
## Summary
Three small pre-cutover cleanup fixes batched into one PR (consolidation
of the prior #4680, #4681, #4682 — same content, fewer review queues).
- `b3ec38720` — Add `<WhenFrameworkHas absent>` fallback to
`programmatic-control.mdx` for the 7 frameworks without
`interrupt_pattern` (ag2, agno, built-in-agent, crewai-crews,
google-adk, mastra, spring-ai), mirroring the canonical pattern from PR
#4496.
- `c080ca062` — Retarget `/migrate/1.10.X` redirect destination from
`/migrate` (which 404s) to `/migrate/v2`. `permanent: false` preserved.
- `e61e8c36d` — Add `mcp-server-setup.mdx` exclusion to docs sync
script. Shell-docs version is intentionally ahead of upstream (HTTP/SSE
Tabs + `mcp-remote` + Tadata callout); without exclusion the next sync
would clobber it.
Supersedes #4680, #4681, #4682.
## Test plan
- [ ] `nx run shell-docs:dev` and visit `/programmatic-control` while
switching the framework selector to non-native fws — verify fallback
callout renders and links to `/human-in-the-loop`.
- [ ] Visit `/migrate/1.10.X` — should redirect to `/migrate/v2` and
render the V2 migration page.
- [ ] Inspect `showcase/scripts/sync-docs-from-main.ts` PATH_EXCLUSIONS
for the `mcp-server-setup` regex.
## Summary
- Extract `CopilotKitContext` and `useCopilotKit` into standalone
`context.ts` in react-core, enabling cross-platform reuse without web
dependencies
- Add new `@copilotkit/react-native` package with lightweight provider,
polyfills, and streaming fetch
- All hooks (`useAgent`, `useFrontendTool`, `useHumanInTheLoop`, etc.)
are re-exported directly from react-core — no reimplementation
## Motivation
CopilotKit's React hooks are platform-agnostic, but the barrel import in
`@copilotkit/react-core` pulls in web-only dependencies (Radix UI, Lit,
A2UI renderer, react-dom, CSS). This makes the package unusable in React
Native without extensive Metro shimming.
By extracting the React context into a standalone entry point
(`@copilotkit/react-core/v2/context`), the new
`@copilotkit/react-native` package can provide its own lightweight
provider while reusing all existing hooks.
## What's in `@copilotkit/react-native`
| Export | Description |
|--------|-------------|
| `CopilotKitProvider` | Lightweight provider — no DOM, CSS, Radix, Lit,
or A2UI deps |
| `installStreamingFetch()` | XHR-based streaming fetch for
`response.body.getReader()` support |
| `@copilotkit/react-native/polyfills` | All polyfills at once
(ReadableStream, TextEncoder, crypto, DOMException, window.location) |
| `@copilotkit/react-native/polyfills/*` | Granular per-polyfill imports
(`/streams`, `/encoding`, `/crypto`, `/dom`, `/location`) for users who
need to avoid overriding their own shims |
| `useAgent`, `useFrontendTool`, etc. | Re-exported from react-core
(shared context) |
## Usage
```tsx
// index.js (entry point, before other imports)
import "@copilotkit/react-native/polyfills";
import { installStreamingFetch } from "@copilotkit/react-native";
installStreamingFetch();
// App.tsx
import { CopilotKitProvider, useAgent, useCopilotKit } from "@copilotkit/react-native";
function App() {
return (
<CopilotKitProvider runtimeUrl="https://your-server/api/copilotkit">
<ChatScreen />
</CopilotKitProvider>
);
}
```
## Test plan
- [x] `nx run react-core:build` passes
- [x] `nx run @copilotkit/react-native:build` passes
- [x] `nx run react-core:test` — all 1153 tests pass
- [x] Manual test in React Native app (tested during development with
bare RN 0.84 project)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Add to monorepo scope in release.config.json. Set version to 1.56.5,
correct ESM extensions in exports map, add check-types/publint/attw
scripts, align tsdown to ^0.20.3. Add react-native example glob to
pnpm-workspace.yaml. Regenerate lockfile preserving zod@3 and
langchain dependency versions.
## Summary
- Fix oxfmt formatting violation in
`deep-agents-finance-erp/use-request-approval.tsx` (failing `static /
quality`)
- Add missing `lucide-react` dependency to
`showcase/shell-docs/package.json` (failing `Showcase: Build & Push` for
shell-docs)
- Regenerate `langgraph-typescript` lockfile after
`@langchain/langgraph` bump to 1.3.0 (failing `Showcase: Build & Push`
for langgraph-typescript)
- Guard all Slack notification steps in `showcase_docs-sync`,
`test_smoke-starter`, and `showcase_qa-sync` workflows with
`env.SLACK_WEBHOOK != ''` so they skip gracefully when
`SLACK_WEBHOOK_OSS_ALERTS` is not set (failing `Showcase: Docs Sync` and
smoke tests)
## Test plan
- [x] `oxfmt --check` passes locally on the formatted file
- [x] `lucide-react` resolves via `npm ls` in shell-docs
- [x] `npm ci --dry-run` passes for langgraph-typescript with
regenerated lockfile
- [x] Pre-commit hooks (lint, test, publint, attw) pass locally
- [x] CI green on prior push (all 24 checks passed)
Extract CopilotKitContext, useCopilotKit, and LicenseContext into
src/v2/context.ts. Add src/v2/headless.ts barrel export for
platform-agnostic hooks. Add v2/context and v2/headless entry
points to tsdown config and package.json exports. Update all hook
imports to use the new context module. Always subscribe to onError
in web provider (matching RN pattern). Use batchedForceUpdate for
onMessagesChanged. Replace extraDeps spread with JSON.stringify in
useFrontendTool and useRenderTool dependency arrays.
Headless React Native wrapper for CopilotKit. Provides a lightweight
CopilotKitProvider and re-exports platform-agnostic hooks from
react-core without pulling web dependencies (no DOM, CSS, Radix,
Lit, A2UI). Includes XHR-based streaming fetch polyfill for Hermes
and granular polyfills for streams, encoding, crypto, DOM, and
location APIs.
Bump @langchain/langgraph-cli from 1.2.1 to 2.0.0 in the langgraph-js
starter and showcase langgraph-typescript integration. The transitive
dep @langchain/langgraph-api@1.2.1 imports STREAM_EVENTS_V3_MODES from
@langchain/langgraph/web, but that symbol was never published in any
release of @langchain/langgraph (including 1.3.0 currently pinned).
langgraph-api@2.0.0 drops the broken import entirely.
The shell-docs Docker build fails because link-to-copilot-cloud.tsx
imports from lucide-react but the package was never added to
shell-docs/package.json. Adding it at ^0.469.0 to match sibling
showcase packages.
## Summary
Ports the client-side telemetry stack from `docs/` into
`showcase/shell-docs/` and tracks every subsequent telemetry-related
upstream merge so the cutover from `docs.copilotkit.ai` to shell-docs
can ship without going dark on funnel attribution.
## What's included
### Telemetry stack port
- **Client-side PostHog** — `posthog-js` provider mounted in
`app/layout.tsx`, complements server-side middleware capture
- **GA4** — `react-ga4` pageview hook
- **HubSpot, Reo.dev** — Script tags in `<head>`
- **Scarf pixel** — page-load OSS attribution
- **RB2B** — canonical `<Script id="reb2b-script">` in `<head>` (matches
upstream after `#4646` LCP-trim refactor; see env var note below)
- **Framework selector instrumentation** — fires `framework_selected`
(net-new event, no upstream equivalent)
- **POSTHOG env rename** — `POSTHOG_PROJECT_KEY` → `POSTHOG_KEY` across
both shell and shell-docs middlewares, env-drives `POSTHOG_HOST` with
`eu.i.posthog.com` fallback
### Open Graph + routing
- **OG image route** — `app/og/[...slug]/route.tsx` for per-page
social-share images
- **vercel.json** — `app/og/**` `maxDuration: 60`
- **Internal-link sweep** — 30 `https://docs.copilotkit.ai/...`
self-references across 15 mdx files rewritten to relative paths so they
don't redirect-loop post-cutover
### Upstream parity tracking (each commit mirrors a merged docs/ PR)
- **Reverse-proxy PostHog through `/ingest`** (mirrors `#4554`) —
`next.config.ts` rewrites + provider hardcodes `/ingest` + middleware
excludes `/ingest` from matcher
- **Talk to Our Engineers nav button** (mirrors `#4590`) — fires
`talk_to_us_clicked` with `{ location: "docs_nav" }`
- **Cloud CTA repointed to dashboard.operations.copilotkit.ai** (mirrors
`#4592`) — UTM-tagged + fires `try_for_free_clicked` with `{ location
}`. Distinguishes `docs_navbar` (desktop) from `docs_navbar_mobile`.
`LinkToCopilotCloud` is preserved for MDX content references that still
point at `cloud.copilotkit.ai`
- **CLI command copy tracking** (mirrors `#4643` + follow-ups) —
`track-command-copy.ts` + `CopyTracker` provider monkey-patches
`navigator.clipboard.writeText`. Final shape: `cli_command_copied` with
`{ install_type, location? }`. `install_type` covers
`npx`/`pnpm`/`pip`/`uv`/`docker`/`curl`/`brew`/`helm`/`kubectl`/`make`/`bash`/`sh`
and friends
- **`capture_dead_clicks: false`** (mirrors `#4646` LCP trim) — drops
dead-clicks-autocapture.js
- **Drop `useRB2B` hook + use canonical `<Script>`** (mirrors `#4646`) —
single source of truth for RB2B injection
## Env vars required on the deployment target
Same project IDs as the current docs deployment for data continuity:
- `NEXT_PUBLIC_POSTHOG_KEY`, `POSTHOG_KEY`, `NEXT_PUBLIC_POSTHOG_HOST`
- **`NEXT_PUBLIC_REB2B_KEY`** — renamed from `NEXT_PUBLIC_RB2B_ID` to
match upstream's canonical Script. Same key value, different env var
name. **Deployment ops needs to set this.**
- `NEXT_PUBLIC_SCARF_PIXEL_ID`
- `NEXT_PUBLIC_REO_KEY`
- `NEXT_PUBLIC_GOOGLE_ANALYTICS_TRACKING_ID`
## Architecture notes
- Hook-based telemetry (GA4) and `CopyTracker` are consolidated into
`<AnalyticsClient />`, a single client boundary mounted from the
server-component layout. Mirrors upstream's `ProvidersWrapper` pattern.
- `<head>` carries the Reo.dev, HubSpot, and (gated on `REB2B_KEY`) RB2B
Script tags. PostHog provider wraps the body inside Suspense; Scarf
pixel sits at end of body.
## Test plan
- [ ] `nx run shell-docs:typecheck`, `lint`, `build` clean
- [ ] Cloud CTA click → confirm dashboard URL with UTMs lands and
`try_for_free_clicked` fires with the right `location`
- [ ] Talk to Our Engineers click → confirm `talk_to_us_clicked` fires
with `location: "docs_nav"` and routes to copilotkit.ai/contact-us
- [ ] PostHog dashboard: docs session continues to attribute via
`/ingest` reverse proxy
- [ ] Copy any code block → confirm `cli_command_copied` fires once with
the correct `install_type`
- [ ] RB2B, Reo.dev, HubSpot, GA4 scripts load (when their env vars are
set) and report events
- [ ] OG image route renders for 5 random pages
- [ ] Framework selector emits `framework_selected`
Two bugs in the Reo init <Script> in layout.tsx:
- If NEXT_PUBLIC_REO_KEY was unset, the script still ran with
e = "undefined" and fetched https://static.reo.dev/undefined/reo.js.
Sibling REB2B_KEY was correctly gated; Reo now matches.
- Bare interpolation into dangerouslySetInnerHTML — any future env
value containing " or </script> would break out of the inline
literal. Switches to JSON.stringify(REO_KEY) and reuses the
resulting binding inside Reo.init() so the key only appears once.
Pulls the dashboard.operations.copilotkit.ai href + UTM string back
into CLOUD_CTA so the desktop and mobile placements share a single
source of truth. UTM tweaks become a one-line change instead of two.
Two review fixes for framework-selector.tsx:
- Replaces the `posthog-js` singleton import with `usePostHog()` from
`posthog-js/react`, matching the rest of the codebase. Both work after
posthog.init() runs, but the singleton was an outlier.
- Renames the event from `framework_selected` to `docs.framework_selected`
to match the dotted-prefix convention used elsewhere in PostHog
Insights (oss.* / cloud.* / eip.* / pricing.cta_clicked). Cheap to do
now since the event is brand-new with no historical data.
The suppressor was carried over from the original telemetry-stack port
of upstream's posthog-provider.tsx. Its job was masking
ERR_BLOCKED_BY_CONTENT_BLOCKER noise from PostHog requests being
blocked by ad blockers targeting *.i.posthog.com directly.
This PR ships the /ingest reverse proxy (mirroring upstream #4554), so
PostHog calls now flow through the docs host and the underlying
hostname is invisible to ad blockers. With nothing to suppress, the
mask only hid real errors that mention "posthog". Three concrete
problems on top of the dead-code framing:
- Substring match on "posthog" swallows unrelated messages.
- isInitializedRef.current only guards the same component instance,
so HMR or StrictMode remounts re-capture the already-patched
console.error and layer wrappers on each remount.
- originalLog is captured but console.log is never patched.
Resolves PDX-112.
Mirrors upstream #4646 in two changes from the same PR:
- Drop the duplicate useRB2B hook in favor of the canonical
<Script id="reb2b-script"> in app/layout.tsx (gated on REB2B_KEY).
Env var renames from NEXT_PUBLIC_RB2B_ID (the hook's name) to
NEXT_PUBLIC_REB2B_KEY (upstream's canonical name) — deployment
ops will need updating.
- Set capture_dead_clicks: false on PostHog init so the
dead-clicks-autocapture.js bundle isn't pulled in on the critical
path.
Mirrors upstream #4643 + #4646 follow-ups. New trackCommandCopy helper
infers install_type from the leading token (covers npx/pnpm/pip/uv/
docker/curl/brew/helm/kubectl/make/bash/sh, falls back to "code"), and
the CopyTracker provider monkey-patches navigator.clipboard.writeText
once at app boot so every programmatic copy fires cli_command_copied
without per-component instrumentation. Preserves upstream's chained-
wrapper pattern so it coexists with Reo's writeText patch.
Final event shape is { install_type, location? } — the command body
and product props from the original draft were dropped upstream
(commit 0b7b3c77c) before merge.
Adds a Talk to Our Engineers CTA to the right side of the docs nav at
viewports >= 1400px and surfaces it in the mobile burger menu below
that. Fires posthog talk_to_us_clicked with { location: "docs_nav" }
on click, then navigates to copilotkit.ai/contact-us. Mirrors the
event the docs/ navbar fires so docs-originated clicks can be
segmented separately from website clicks (which use "nav").
Routes PostHog analytics through /ingest/* (rewrites to eu.i.posthog.com)
so requests bypass ad blockers and tracking-protection that target the
*.i.posthog.com hostname directly. Hardcodes POSTHOG_HOST = "/ingest"
in the provider, points ui_host at https://eu.posthog.com so PostHog UI
links still resolve, and excludes /ingest from the middleware matcher
so the proxy itself doesn't fire phantom pageviews. Mirrors the same
fix in docs/.
Sweeps 30 hardcoded https://docs.copilotkit.ai/* self-references across
15 mdx files in src/content/ to relative paths so they don't redirect-loop
once docs.copilotkit.ai cuts over to shell-docs.
Adds a Free Developer Access CTA to the brand nav (desktop + mobile
menu) that points at dashboard.operations.copilotkit.ai with UTM tags
and fires try_for_free_clicked with { location } on click.
Distinguishes docs_navbar (desktop) from docs_navbar_mobile so the
funnel can split the two layouts.
Also adds the LinkToCopilotCloud component for use by MDX content
references that deep-link into Copilot Cloud — those remain on
cloud.copilotkit.ai.
Fires framework_selected when a user picks a framework so the docs
funnel can attribute drop-off to specific frameworks. Net-new event
(no upstream equivalent in docs/).
Adds app/og/[...slug]/route.tsx for per-page social-share image
generation, and a vercel.json with maxDuration: 60 on app/og/** so
the Edge function has enough headroom to render OG cards.
Brings PostHog, GA4, HubSpot, Reo.dev, Scarf, and RB2B into shell-docs
with parity to docs/. Adds the client-side PostHog provider with
session-stitched bootstrap and pageview capture, the AnalyticsClient
wrapper that mounts RB2B + GA4 hooks behind a single client boundary,
the Scarf pixel for OSS attribution, and the HubSpot and Reo.dev
scripts.
Renames POSTHOG_PROJECT_KEY to POSTHOG_KEY across shell and shell-docs
middlewares so the env names match the upstream pattern, and env-drives
POSTHOG_HOST with eu.i.posthog.com as the fallback.
## Summary
Stacked on top of #4629. Two commits:
1. **MemoizedCustomMessage memo gating signals** (Friday's WIP, cleaned
in CR loop). Adds three new memo gating signals to
`MemoizedCustomMessage` so custom message renderers stay reactive across
the structural events that affect "is this slot still authoritative?"
decisions:
- `numberOfMessagesInRun` — invalidates when peers stream into the same
run
- `isInLatestRun` — invalidates when a newer run starts
- `isRunning` (gated on `isInLatestRun`) — invalidates exactly twice per
run on the latest run's slots, preserving the perf-test guarantee that
completed runs' messages skip re-renders during streaming
`CopilotChatMessageView` consolidates per-run metadata
(run-id-by-message, count-by-run, latest-run) into a single `useMemo`
that runs O(n) per render rather than O(n²) per message.
2. **`IntelligenceIndicator` — auto-mounted** "Using CopilotKit
Intelligence" pill, ported from the visuals of
[CopilotKit/Intelligence#155](https://github.com/CopilotKit/Intelligence/pull/155).
## How the indicator works
- `CopilotChatMessageView` mounts `<IntelligenceIndicator>` for every
message slot whenever `copilotkit.intelligence !== undefined`. No
factory, no provider, no `renderCustomMessages` registration — the
caller doesn't add anything.
- The indicator self-gates so only the canonical slot renders a pill:
last message of the latest in-flight run, with at least one tool call
whose `function.name` matches one of `DEFAULT_TOOL_PATTERNS` (currently
`[/^bash$/]`, the Intelligence MCP server's canonical tool).
- "Exactly one pill at any moment" is structural — only one message can
ever satisfy the gates, so each renderer invocation decides
independently and the result is one pill in the DOM.
### Phase machine (per-instance, all timers local)
- `spinner` while `agent.isRunning`
- → `check` after `agent.isRunning` falls (debounced 500 ms to absorb
step-boundary `RUN_FINISHED → RUN_STARTED` blips)
- → `fading` after 800 ms hold
- → `hidden` after 480 ms fade animation
A 200 ms `agent.isRunning` poll closes the AG-UI snapshot-subscriber gap
(subscribers added INSIDE a run never see that run's `onRunFinalized`).
## API
Nothing new for the user. Configure intelligence on your runtime, and
the pill appears.
```tsx
<CopilotKitProvider runtimeUrl="..."><CopilotChat /></CopilotKitProvider>
```
When `/info` returns `intelligence: { wsUrl: ... }`, the gate flips and
the pill auto-mounts on the canonical slot.
## Configurability
Tool-name matching is currently hardcoded to `[/^bash$/]`. If we need
per-instance customization later (a `CopilotKitProvider` prop, or a
`toolPatterns` field on `IntelligenceRuntimeInfo` so the runtime can
declare its own intelligence tools), the constant becomes the fallback.
## Test plan
- [x] `nx run @copilotkit/react-core:test` — **1158 passed**
- [x] `nx run @copilotkit/core:test` — **424 passed**
- [x] `nx run @copilotkit/web-inspector:test` — **7 passed**
- [x] Build: `react-core`, `core`, `web-inspector` clean. Pill CSS
bundled into `dist/v2/index.css` (10 rules + 3 keyframes).
### New test coverage
- 1 walkthrough — Run A → Run B with multiple bash calls; pill follows
the canonical slot through every phase. Setup passes **no
`renderCustomMessages` prop** to prove auto-mount.
- 4 gate tests: last-in-run / in-flight (with phase-machine fade-out) /
latest-run / tool-match.
- 1 intelligence gate — no pill when `copilotkit.intelligence` is
undefined.
- 1 explicit auto-registration assertion — pill renders solely because
intelligence is set, no user setup.
## Caveats
- Tool-name patterns are hardcoded; not yet configurable by the runtime
or the app caller. Easy follow-up if needed.
- The `CopilotChatConfigurationProvider` dependency that
`CopilotChatMessageView` uses for `agentId` / `threadId` (to query
state-manager) is pre-existing and not addressed here — flagged
separately.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
- Repairs broken links in 4 shared snippets that were the root cause of
most internal-referrer 404s on docs.copilotkit.ai
- Adds 35 redirects in \`next.config.mjs\` covering every URL with
\`broken_link_accessed\` events from the last 30 days of PostHog data
## Approach
PostHog tracks 404s via the \`broken_link_accessed\` event (see
\`docs/POSTHOG_BROKEN_LINKS.md\`). I queried the last 30 days, grouped
by \`broken_url\`, and split the results into two buckets:
1. **Source-side bugs** — broken links coming from our own MDX. The
biggest offenders were shared snippets (\`copilot-runtime.mdx\`,
\`generative-ui/a2ui.mdx\`, \`generative-ui-specs-overview.mdx\`,
\`backend/ag-ui.mdx\`) that get inlined into many integration pages,
multiplying each broken link across the site. Fixed at source.
2. **External / legacy traffic** — search results, marketing site links,
partner sites. Added redirects.
For URLs without a clean 1:1 destination, every broken path now
redirects to the closest matching concept page (no hard 404s left). Each
ambiguous mapping was validated by reading the candidate destination's
content.
## Categories of redirects added
- \`/generative-ui/specs/*\` → \`/learn/generative-ui/specs/*\` (section
moved)
- \`/premium/{threads,inspector}\` → root equivalents (those pages are
at root, not under \`/premium\`)
- \`/built-in-agent/guides/*\` → renamed pages or section index (the
\`guides/\` prefix was dropped in the 2026-02 restructure)
- \`/learn/direct-to-llm/*\` → \`/built-in-agent\` (namespace removed)
- \`/learn/langgraph/*\` → \`/langgraph/*\` (was never under \`/learn\`)
- \`/deepagents/{prebuilt-components,custom-look-and-feel/*}\` → root
equivalents (deepagents integration is missing these pages — see
follow-up note below)
-
\`/reference/v1/hooks/{useRenderTool,useComponent,useThreads,useInterrupt,useCapabilities}\`
→ \`/reference/v2/hooks/<same>\` (these hooks are new in v2)
- \`/reference/v2/hooks/useCopilotAction\` →
\`/reference/v2/hooks/useFrontendTool\` (renamed)
- \`/whats-new/*\` → \`/learn/whats-new/*\`
- Doubled-prefix paths (\`/agent-spec/agent-spec/wayflow\`, etc.) —
straightforward un-double
- \`/zh/langgraph/deep-agents\` → \`/deepagents\` (no i18n on this site)
## Follow-up worth tracking separately
The \`integrations/deepagents/\` directory is missing several pages that
other integrations have (\`prebuilt-components.mdx\`,
\`custom-look-and-feel/\`, \`premium/\`, etc.). The redirects in this PR
band-aid the symptom; the integration page itself is incomplete vs
siblings.
## Test plan
- [x] Verified each of the 35 former-404 paths returns 308 → final 200
on local \`next dev\`
- [x] Verified rendered HTML on pages that include the fixed snippets
now emits the new link targets
- [x] Confirmed the failing pre-commit test
(\`@copilotkit/sqlite-runner:test\`) is pre-existing on \`main\` and
unrelated to this docs-only change
Several shared snippets linked to paths that no longer exist:
- /generative-ui/specs/* → /learn/generative-ui/specs/* (specs moved)
- /premium/threads → /threads (page is at root, not under /premium)
- /premium/inspector → /inspector (page is at root, not under /premium)
Because these snippets are inlined into many integration pages,
each broken link multiplied across the site and accounted for the
majority of internal-referrer 404s on docs.copilotkit.ai.
## Summary
Follow-up to #4672. The aggregated multi-turn probe shipped in that PR
hit a CopilotKit v2 quirk on `/demos/beautiful-chat`: only the FIRST
`useComponent` tool call in a conversation paints its component.
Subsequent tool calls emit (the agent's followup content arrives) but
the component never mounts. Reproduced cleanly without any frontend tool
involvement — pie chart turn 1 paints 5 svg circles in seconds, bar
chart turn 2 emits "Bar chart rendered above…" but paints zero recharts
elements.
Splitting into per-pill scripts means each probe gets its own browser
launch — fresh page state, fresh conversation, no `useComponent`
ordering pollution.
`CATALOG_TO_D5_KEY["beautiful-chat"]` maps to all five literals;
`isD5Green` requires every key green for the cell to advance to D5, and
per-pill failure isolation surfaces in PB row names
(`d5:<slug>/beautiful-chat-pie-chart`, etc.) — a regression points
directly at the broken surface in the dashboard drilldown.
## What's covered (5 pills)
| Literal | Surface | Assertion |
|---|---|---|
| `beautiful-chat-toggle-theme` | frontend `toggleTheme` tool |
`html.dark` class flip |
| `beautiful-chat-pie-chart` | controlled gen-UI `pieChart` | `>= 3 svg
circle` |
| `beautiful-chat-bar-chart` | controlled gen-UI `barChart` | recharts
container + `>= 2 .recharts-bar-rectangle` |
| `beautiful-chat-search-flights` | A2UI fixed-schema FlightCards (#4668
path) | `United` / `Delta` / `$349` / `$289` literals |
| `beautiful-chat-schedule-meeting` | HITL `scheduleTime` | picker
mounts → click slot → "Meeting Scheduled" |
All 5 verified locally end-to-end (5/5 pass, fresh page session per
probe).
## What's NOT in this PR (all four documented in
`_beautiful-chat-shared.ts`)
- **Excalidraw Diagram** — depends on `mcp.excalidraw.com` reachability;
turning D5 reliability into a third-party uptime bet is the wrong
tradeoff.
- **Calculator App** — sandboxed iframe makes Playwright assertions
cross-frame fragile; `generateSandboxedUi` is already covered by
`d5-gen-ui-open` on a different demo route, so this would be duplicate
coverage.
- **Sales Dashboard** — `generate_a2ui` → secondary `render_a2ui` chain
renders Metric labels, but `Row`-bound PieChart / BarChart children
don't paint their recharts containers when driven by aimock fixtures.
Live pill click against the same fixture chain shows the inverse symptom
(containers paint, Metric labels go missing). The shape suggests
aimock's non-progressive arg streaming differs from a live LLM in a way
the A2UI binder is sensitive to. Needs a separate aimock or binder
investigation.
- **Task Manager** — backend `manage_todos` dispatches and the agent
emits closing content, but `StateStreamingMiddleware`'s `state.todos`
propagation doesn't populate the App pane TodoList through aimock — same
suspected root cause as Sales Dashboard.
These four are deliberate exclusions, not deferred work. If we want to
add them later, the path is clear (each gets its own
`beautiful-chat-<surface>` literal + probe file + fixture + mapping
entry).
## Architecture
Mirrors the `_hitl-shared.ts` pattern: shared module with DOM helpers +
per-pill assertions, thin individual probe files registering one
`D5FeatureType` each.
- `_beautiful-chat-shared.ts` — `BeautifulChatPage` extends the runner's
`ConversationPage` with `click()`, with the same runtime guard as
`d5-hitl-text-input.ts`. DOM helpers cast `globalThis` to a structural
shape per closure (the harness's tsconfig has `types: ["node"]`, no DOM
lib) — same pattern as `d5-chat-css.ts`'s `probeChatCss`.
- Each fixture uses unique `d5 beautiful-chat probe: ...` substrings;
the multi-stage Schedule Meeting flow uses `hasToolResult` `false →
true` for round disambiguation. No `toolCallId` leakage concern since
each probe runs in its own fresh page session.
## Test plan
- [x] `npx nx run @copilotkit/showcase-harness:typecheck` — clean
- [x] `npx nx run @copilotkit/showcase-shell-dashboard:typecheck` — no
new errors (5 pre-existing test-file errors unchanged)
- [x] `npx nx run @copilotkit/showcase-harness:build` — clean
- [x] `OPS_BASE_URL=stub npm run build` (shell-dashboard) — clean
- [x] `npm test -- --run d5-registry` — 12/12 pass with the 5 new
literals (and the old `beautiful-chat` literal removed from the
registry)
- [x] Local end-to-end: 5/5 probes pass against the local stack via
Playwright (each in its own fresh page session)
The aggregated multi-turn probe from #4672 hit a CopilotKit v2 quirk on
/demos/beautiful-chat: only the FIRST useComponent tool call in a
conversation paints its component. Subsequent tool calls emit (the
agent's followup content arrives) but the component never mounts.
Reproduced cleanly without any frontend tool involvement —
pie-chart turn 1 paints 5 svg circles in seconds, bar-chart turn 2
emits "Bar chart rendered above..." but paints zero recharts elements.
The runner can't sidestep this from inside one conversation without a
page.reload() between turns, which the structural Page type doesn't
expose. Splitting into per-pill scripts means each probe gets its own
browser launch — fresh page state, fresh conversation, no useComponent
ordering pollution. CATALOG_TO_D5_KEY maps `beautiful-chat` to all
listed literals; isD5Green requires every key green for the cell to
advance to D5, and per-pill failure isolation surfaces in PB row names.
Coverage in this PR (5 pills):
- beautiful-chat-toggle-theme (frontend tool, html.dark flip)
- beautiful-chat-pie-chart (controlled gen-UI useComponent)
- beautiful-chat-bar-chart (controlled gen-UI useComponent)
- beautiful-chat-search-flights (A2UI fixed-schema FlightCards)
- beautiful-chat-schedule-meeting (HITL with slot-click resolution)
All 5 verified locally end-to-end (5/5 pass against the local stack).
Out of scope, intentionally (track in follow-up):
- Excalidraw — depends on mcp.excalidraw.com reachability
- Calculator — sandboxed iframe; dup of d5-gen-ui-open
- Sales Dashboard — generate_a2ui → render_a2ui chain renders
Metric labels but Row-bound charts don't paint
recharts containers under aimock fixtures (live
pill against same fixture chain shows the
inverse symptom). Suggests aimock's
non-progressive arg streaming differs from a
live LLM in a way the A2UI binder is sensitive
to. Needs separate aimock/binder investigation.
- Task Manager — manage_todos dispatches and agent emits closing
content, but StateStreamingMiddleware's
state.todos propagation doesn't populate the
App pane TodoList through aimock — same suspected
root cause as Sales Dashboard.
Architecture details:
- _beautiful-chat-shared.ts factors DOM helpers + per-pill
assertions, mirroring _hitl-shared.ts's pattern for an extended
Page type with click() + a runtime guard
- Each fixture uses unique D5-prefixed userMessage substrings; the
multi-stage Schedule Meeting flow uses hasToolResult false→true
for round disambiguation (no toolCallId leakage since each probe
runs in its own fresh page session)
## Summary
Replace the abrupt "cut in" with a 300ms fade + slide-from-bottom
entrance on each chat card so they feel connected to the streaming
response instead of popping in.
Follow-up polish on
[#4587](https://github.com/CopilotKit/CopilotKit/pull/4587).
## What changed
Added `animate-in fade-in slide-in-from-bottom-2 duration-300 ease-out`
to the root element of every tool render surface in the chat:
- `ToolCard` — `SpecializedToolCard` and `DefaultToolCard` (covers
`do_research`, `do_projections`, `navigate_and_filter`, dashboard ops,
`render_chat_visual`, etc.)
- `InlineChart` (both placeholder and rendered chart)
- `CashPositionCard` (both placeholder and full card)
- `InventoryReorderCard` (both placeholder and full card)
- `InvoiceApprovalCard` (both placeholder and full card)
## Why this approach
- **`tw-animate-css` is already imported** in
`examples/showcases/deep-agents-finance-erp/src/app/globals.css` — no
new dependencies.
- **Mount-time CSS animation, not Framer Motion** — the cards are
short-lived per turn and don't need orchestration; CSS keyframes resolve
to `animation-name: enter` exactly once on first paint and don't re-fire
on prop transitions, so swapping between `InProgress` and the populated
state stays smooth.
- 300ms `ease-out` matches the rest of the demo's transition timing.
## Test plan
- [x] Chat → "Cash Position" → cards slide+fade in (verified via
`getComputedStyle().animationName === "enter"`, `animationDuration ===
"0.3s"`).
- [x] Same animation fires for `do_research` / `do_projections` tool
cards, inline charts, approval cards, and inventory reorder cards.
- [x] No layout shift or jank in successive turns.
- [ ] Reviewer: visual verification on a non-mac browser if possible
(animation runs on standard CSS, but worth confirming).
## Notes
- No JS/runtime changes. Pure CSS class additions via Tailwind
utilities.
- Decoupled from the HITL fix in
[#4630](https://github.com/CopilotKit/CopilotKit/pull/4630) — landing
this independently is fine.