Convert the top nav from an edge-to-edge sticky bar into a centered floating rounded pill (sticky top-2, glass background, max-w-1400) matching docs.copilotkit.ai. Move section navigation into a center cluster (Documentation / API Reference / Integrations) with a violet accent underline on the active tab, drop the AG-UI brand-tab split from the wordmark side, and restyle the search trigger as a glass pill with a visible kbd ⌘K affordance.
Folds in four low-coupling visual slots that all sit on the canonical
color tokens shipped earlier on this branch:
- Typography: page H1 to 40px / 500 / line-height 48px (was 32px / 700);
H2 to 600 weight with normal letter-spacing (was 700 / -0.015em); drop
the Spline Sans Mono Google Font and fall back to system mono, matching
what canonical docs.copilotkit.ai now ships.
- Callouts: rewrite the boxed admonition card to the canonical pattern —
white surface (`--bg-surface`) with shadow-md, full 1px border, a thin
inner left-strip in the type color at 50% opacity, and a lucide SVG
glyph instead of the round-badge letter. Brand violet for "info"
matches the synced accent.
- Tables: bump font to 14px (was 13px), switch from per-cell borders to
row-bottom-only borders, and pad to 10px / 12px per the canonical
data-table pattern.
- Content max-width: relax `max-w-3xl` (768px) to `max-w-[860px]` so long
prose lines aren't visibly cramped.
Legacy `--violet` and `--violet-light` tokens were left at the pre-sync
shell values (#5a3cd1, #ebe7fa) when the canonical accent moved to
#6d45f9 / #ede5ff. They're consumed by AG-UI section pages and
OpsPlatformCTA, both of which should track the brand violet.
Re-alias to the canonical accent values so the AG-UI pages and the CTA
match the rest of the docs visually.
Per visual styling audit slot 1: align shell-docs CSS custom properties
with the canonical docs.copilotkit.ai palette. Updates --accent to the
brand violet (#6d45f9) with derived --accent-light and --accent-dim,
shifts --bg to the canonical cool-gray (#dedee9), and introduces a new
--glass-background token (the previous --bg value) reserved for header
and sidebar glass-panel surfaces consumed by later slots.
## 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
- 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.
## Summary
The Approve / Reject card in the finance-erp showcase never worked
end-to-end — the card flashed in then vanished and the agent run
finished with the LangGraph checkpoint left in a half-paused state. This
was a pre-existing bug, not introduced by
[#4587](https://github.com/CopilotKit/CopilotKit/pull/4587).
## Why
The agent's `request_approval` tool calls
`copilotkit_interrupt(action="request_approval", args={...})` which
raises a `GraphInterrupt`. LangGraph pauses the run, stores the
interrupt on the checkpoint, and `ag_ui_langgraph` emits a `CUSTOM`
event with `name: "on_interrupt"` carrying the approval payload. Then
the run finalizes (`RUN_FINISHED`).
The frontend was using `useHumanInTheLoop`, which is the V2 hook for
**frontend-executed** tools (LLM calls a tool, frontend resolves a
Promise — no agent-side execution). It does not subscribe to
`on_interrupt` events at all, so:
- The render registered for `request_approval` saw only the streamed
tool call from `MESSAGES_SNAPSHOT` and rendered with `status:
InProgress` (no `executingToolCallIds` entry was ever set).
- The agent's checkpoint had a pending interrupt that no UI was offering
to resolve.
V2 ships a separate `useInterrupt` hook specifically for LangGraph-style
interrupts. Switching to it fixes the round trip.
## What changed
- `src/hooks/use-request-approval.tsx` — replaced `useHumanInTheLoop`
with `useInterrupt` and bridged the result to the existing
`InvoiceApprovalCard` / `InventoryReorderCard` components (no card
markup changes). The `respond` callback the cards expect is now backed
by `resolve` from `useInterrupt`, which sends
`forwardedProps.command.resume` back to the agent.
- Added a no-op `useRenderTool({ name: "request_approval", render: () =>
null })` so the existing wildcard `useRenderTool({ name: "*" })` in
`shell.tsx` doesn't render a stale generic ToolCard alongside the
interrupt UI.
- JSON-parse `event.value` — the runtime delivers the interrupt payload
as a JSON-encoded **string** (the structured object lives under
`rawEvent.value`). Accessing properties directly silently returns
`undefined` and filters the interrupt out, which is what masked an
earlier iteration of this fix.
## Test plan
- [x] Click "Approve Payments" → agent runs do_research, then surfaces
the InvoiceApprovalCard with the overdue invoices and Approve / Reject
buttons
- [x] Click "Approve Payment" → agent log: `[HITL] request_approval
RESUMED answer='{"approved": true, "message": "Payment approved for 3
invoice(s) totaling $153,500.00"}'`. Run continues with the user
response.
- [x] Click "Reject" → resume command carries `{approved: false}`; agent
acknowledges and stops processing.
- [x] Click "Reorder Inventory" → same flow surfaces
`InventoryReorderCard` with Approve PO / Skip Reorder buttons (the hook
handles both `type` values).
- [ ] Reviewer: please verify against your own LangGraph showcase setup
that no other HITL flow regressed.
## Notes
- No package changes. `@copilotkit/react-core` already exports
`useInterrupt` from V2.
- The fix is contained to a single file (`use-request-approval.tsx`);
the cards themselves are untouched.
## Summary
- **Dashboard mapping fix.** `CATALOG_TO_D5_KEY` in
`showcase/shell-dashboard/src/lib/live-status.ts` was missing `voice →
["voice"]`, so `computeMaxPossible` capped the langgraph-python voice
cell at D4 even when the d5-voice probe row was green. The harness
`REGISTRY_TO_D5` already had the entry; only the dashboard mirror was
out of sync.
- **Sample-button decoupled from `/transcribe`.** The "Play sample"
button used to fetch `sample.wav` and POST it to the runtime's
transcription endpoint, which made the sample button and the mic
indistinguishable under aimock (both returned the canned transcription).
Reworked it into a synchronous static-text injector — sample button is
now a deterministic test/demo affordance, and the mic is the only path
that exercises real Whisper transcription. Synced across all 18
voice-enabled integrations. Phrase stays `"What is the weather in
Tokyo?"` so aimock's `weather in Tokyo` substring fixture still matches.
- **Probe-test parity.** Added the missing `d5-voice.test.ts` companion
(every other `d5-*.ts` script has one) — 9 tests covering registration,
`buildTurns`, `preFill` (sample-button click + textarea-poll path), and
the weather/Tokyo assertion.
- **QA + e2e cleanup** for langgraph-python: dropped the
no-longer-applicable "Transcribing…" mid-flight assertion and the `block
/demo-audio/sample.wav` error-state subsection. Other 16 integrations'
qa/e2e files follow in a parity sync PR.
## Test plan
- [x] `nx test @copilotkit/showcase-harness -- --run d5-voice` → 9/9
pass
- [x] `npm test` in `showcase/shell-dashboard` → 509/510 pass (1
skipped, 0 failed)
- [x] `nx build @copilotkit/showcase-harness` → clean
- [x] Local boot: `langgraph-cli dev` (port 8123) + `next dev` (port
3000) + dashboard (port 3002) — voice page at `/demos/voice` renders,
"Play sample" injects the canned phrase instantly, send → agent returns
weather, mic → real Whisper transcription with `OPENAI_API_KEY` set
- [ ] Reviewer: confirm the langgraph-python voice cell on the live
dashboard advances to D5 once the next d5-voice probe tick lands a green
row