Picks up _extract_forwarded_headers_from_config from PR #4984, now
shipped as copilotkit 0.1.91 on PyPI. Three Python integrations move
forward together: langgraph-python, strands, langgraph-fastapi.
Updates validate-pins ratchet hash (count stays 106, FAIL set shifted
because showcase pins now diverge from Dojo on 0.1.91 vs 0.1.87).
Showcase auto-redeploys to Railway on merge via showcase_build.yml
(path filter showcase/**).
## Summary
Wires per-request x-* headers through the CopilotKit Python middleware
so LangGraph-based agents receive the original request's forwarded
headers (D6 "everything works" prerequisite). Four logical pieces:
1. **sdk-python forwarded-header extraction** —
`_extract_forwarded_headers_from_config()` reads x-* headers from
LangGraph's runtime config (both wrapper-dict
`copilotkit_forwarded_headers` and raw x-* keys), applies documented
precedence (context > configurable, wrapper > raw), lowercases keys at
insertion to make precedence deterministic across mixed-case headers,
and always clears the ContextVar on early-exit paths so stale headers
from a prior request cannot leak.
2. **sdk-python tests** — 47 new/modified test cases covering
wrapper-dict and raw extraction, context > configurable precedence,
mixed-case normalization, RuntimeError early-return clearing,
exception-path clearing, None/empty-config fallbacks, sync/async parity.
3. **Showcase Python pins** — pins `copilotkit==0.1.90` across
langgraph-python, strands, and langgraph-fastapi so the version that
runs in showcase matches the version that contains this fix. Bumps
`ag-ui-langgraph[fastapi]>=0.0.35` in langgraph-fastapi because
copilotkit 0.1.90 requires it transitively (the previous `==0.0.34` pin
would cause `pip install` to hard-fail).
4. **Showcase docker-compose** — adds
`LANGGRAPH_HTTP={"configurable_headers":{"include":["x-*"]}}` to the
shared `x-integration-defaults` anchor so langgraph-api includes x-*
headers in the runtime config; without this, langgraph-api 0.7+ strips
x-* headers before the agent ever sees them.
## Companion PR
Depends on the matching ag-ui PR that adds per-request header forwarding
across 5 integration adapters (langgraph, mastra, vercel-ai-sdk,
langchain, claude-agent-sdk). After ag-ui releases, bump the
`@ag-ui/langgraph` pin in `packages/sdk-js/package.json` in a follow-up.
## CR loop
- Round 1: surfaced 12 findings (4 ag-ui clusters + 6 CopilotKit
clusters) across 14 reviewers.
- Round 2 fix: docker-compose LANGGRAPH_HTTP YAML merge bug, sdk-python
wrapper-dict precedence, exception-path ContextVar leak,
RuntimeError-path leak, langgraph-fastapi version conflict.
- Round 2 confirmation: 14 reviewers, surfaced 4 new Bucket (a) findings
on CopilotKit side.
- Round 3 fix: lowercase-at-insertion + always-clear ContextVar on both
early-exit paths + ag-ui-langgraph[fastapi] bump.
- Round 3 confirmation: 7 reviewers, all NO_BUCKET_A_FINDINGS.
- Pre-push-quality: green (ruff, 47+87 pytests, build, docker-compose
config).
## Test plan
- [ ] CI green
- [ ] Showcase D6 LangGraph integration receives x-aimock-context header
end-to-end with x-AIMock-Strict propagation
- [ ] No regressions in other Python integrations (strands,
langgraph-fastapi)
- [ ] Docker compose still boots all integrations with the new shared
LANGGRAPH_HTTP env
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
Three changes to `src/lib/docs-render.tsx` that together eliminate the
`[docs-render] snippet missing for component X` log noise on prod AND
fix a hard SSR error on `/quickstart` caused by a missing
`ComponentExamples` registration.
## Changes
**1. Suppress warnings for components the MDX imports** (existing scope
of this PR).
`inlineSnippets()` runs after `stripLeadingImports()` removes the MDX's
`import` lines, so the regex can't tell a `<PascalCase />` reference
apart from a real React component imported into the file. Every imported
component triggered a false-positive `snippet missing` warning. The page
still rendered correctly via `docsComponents`; only the log was wrong.
Capture imports before stripping, then short-circuit the warning when
the regex hits one of those names.
**2. Add `ComponentExamples` to `SNIPPET_MAP`** (new).
`copilot-ui.mdx` imports `ComponentExamples` from
`@/snippets/component-examples.mdx` and renders it. When
`copilot-ui.mdx` is recursively inlined into a parent MDX (e.g. via
`<CopilotUI />`), the import line is stripped and the inliner finds no
`SNIPPET_MAP` entry for `ComponentExamples`. The bare JSX survives the
inliner and `next-mdx-remote` throws `Expected component
ComponentExamples to be defined` at SSR. The error is caught in a
partial-render error boundary so the page returns 200, but a chunk of
content is missing from the rendered output.
Add `ComponentExamples: "component-examples.mdx"` to `SNIPPET_MAP` so
the recursive inliner resolves it the same way it resolves `CopilotUI`.
One line, fixes both the SSR error and the cosmetic warning.
(Note: the `gatherImportedComponentNames` shortcut from change 1 doesn't
help here because it captures imports from the OUTER MDX, not from
recursively-inlined snippet bodies. That deeper structural limitation
can be addressed later; the `SNIPPET_MAP` entry resolves the immediate
failure.)
**3. Skip icon-prefix bare references** (new).
Lucide `Square*` icons (`SquareTerminal`, `SquareChartGantt`) and
`react-icons` `Fa*` / `Si*` / `Pi*` families are bare-referenced in many
MDX files — no explicit import, resolved at render time via the
registry's emoji stubs in `docsComponents`. The existing
`endsWith("Icon")` filter doesn't catch these PascalCase +
library-prefix shapes. Add a regex check next to it:
`/^(Fa|Si|Pi|Square)[A-Z]/`.
## Components covered after changes 1-3
The import-aware filter catches every `import { X } from "..."`
reference (current set: AgentCoreCommandTabs, CodePanel, CodeShowcase,
Frame, IframeSwitcher, ImageAndCode, LinkToCopilotCloud,
NewLookAndFeelPreview, StartProviders).
The icon-prefix filter catches `FaArrowUp`, `FaWrench`,
`SquareTerminal`, `SquareChartGantt`, and future icon-library additions
matching the prefix shape.
`ComponentExamples` is registered in `SNIPPET_MAP` so the inliner
resolves it instead of warning.
Remaining bare references that don't match any of the above (e.g.
`CloudCopilotKit`) still warn — these are runtime React components
registered in `docsComponents` but not imported in the MDX. The right
cleanup for those is to add explicit `import { CloudCopilotKit } from
"..."` lines in the MDX, which the import-aware filter then catches
automatically. Out of scope for this PR.
## Adjacent observation (not fixed)
While diagnosing change 2, found that the underlying
`new-look-and-feel.tsx` component file referenced by
`troubleshooting/migrate-to-1.8.2.mdx` doesn't exist on disk. The MDX
registry stubs the name with `<div>{children}</div>`, so the preview
area in that page renders empty. This PR doesn't address the
empty-preview behavior; the snippet-missing log noise is purely
cosmetic.
## Test plan
- [x] Diff is additive only; runtime render path unchanged.
- [ ] Local: build shell-docs, hit `/built-in-agent/quickstart` +
`/crewai-crews/quickstart` + `/built-in-agent/agentic-protocols/mcp` +
`/built-in-agent/shared-state/predictive-state-updates` +
`/built-in-agent/troubleshooting/migrate-to-1.8.2`. Confirm zero
`[docs-render] snippet missing` and zero `Expected component
ComponentExamples to be defined` warnings in the dev server log.
- [ ] Local: confirm the ComponentExamples Tabs block actually renders
on `/crewai-crews/quickstart` (was silently missing pre-fix).
- [ ] Post-deploy: re-check Railway logs for the warning + error class
over a sample of page loads.
## Summary
Tutorial entries appeared in the sidebar nav on every Built-in Agent
docs page (e.g. `/built-in-agent/quickstart`) despite the section being
deprecated. Clicks worked because the redirect catalog 301s
`/{fw}/tutorials/:path*` to `/{fw}/quickstart`, but the entries should
not be visible at all.
## Root cause
BIA runs in `docs_mode: "authored"`, which routes through
`buildFrameworkOnlyNav` → `buildNavTree`. That code path keeps section
headers and recurses through `"...folder"` spreads (unlike the
generated-mode path used by langgraph/mastra/google-adk, which strips
section headers via `buildFrameworkOverridesNav`).
BIA's `integrations/built-in-agent/meta.json` declared
`"---Tutorials---"` + `"...tutorials"`, so the spread descended into the
tutorials folder and enumerated its subfolders into the sidebar.
## Fix
Remove the two lines from BIA's parent `meta.json`:
```diff
"---Premium Features---",
"...premium",
-"---Tutorials---",
-"...tutorials",
"---Troubleshooting---",
"...troubleshooting"
```
Source MDX is preserved.
## Test plan
- [x] JSON validity confirmed via `node -e 'JSON.parse(...)'`.
- [ ] Post-deploy: confirm no `Tutorial:` entries in
`/built-in-agent/quickstart` sidebar HTML.
- [ ] Post-deploy: confirm
`/built-in-agent/tutorials/ai-todo-app/step-2-setup-copilotkit` still
301s to `/built-in-agent/quickstart`.
Add EXTRA_FEATURED_BY_INTEGRATION map so hitl-in-chat, hitl-in-app, and
gen-ui-interrupt only appear in Featured for langgraph-python,
langgraph-typescript, langgraph-fastapi, and google-adk. All other
integrations see the original 9-item Featured list unchanged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Surface hitl-in-chat, hitl-in-app, and gen-ui-interrupt in the dojo's
hand-curated Featured list so they are visible at the top of the sidebar
for every integration that supports them (LGP, ADK, etc.). Unsupported
demos are silently skipped per the existing filter logic.
Resolves OSS-138
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two follow-on fixes to the same docs-render snippet pipeline.
1. ComponentExamples SSR error. The shared copilot-ui.mdx snippet
imports ComponentExamples from @/snippets/component-examples.mdx
and renders it. When copilot-ui.mdx is recursively inlined into a
parent MDX (e.g. crewai-flows/quickstart.mdx via <CopilotUI />),
stripLeadingImports removes the import line, then the regex hits
<ComponentExamples /> with no matching SNIPPET_MAP entry. The
inliner leaves the bare JSX in the output and at MDX render time
next-mdx-remote throws "Expected component ComponentExamples to be
defined". Add the snippet to SNIPPET_MAP so the recursive inliner
resolves it the same way it resolves CopilotUI.
The gatherImportedComponentNames shortcut from the same PR only
captures imports in the OUTER MDX, not in recursively inlined
snippet bodies — a deeper structural limitation worth fixing later
but not load-bearing here. The SNIPPET_MAP entry resolves both the
SSR error AND the false-positive warning in one line.
2. Icon-library bare references logging spurious warnings. Lucide
Square-prefixed icons (SquareTerminal, SquareChartGantt) and
react-icons fa / si / pi families appear bare in many MDX files —
no import, resolved at render time via the registry's emoji stubs.
The existing endsWith("Icon") filter doesn't catch these. Add a
PascalCase-prefix check next to it.
Strictly additive; runtime render path unchanged.
The Built-in Agent sidebar still rendered the deprecated tutorials section
("Tutorial: AI Todo App", "Tutorial: AI Textarea" with step pages) even
after PR #4987 added 301 redirects for /{fw}/tutorials/:path* paths.
The redirects fired on click, but the entries should never have appeared.
Root cause: integrations/built-in-agent/meta.json declared a
"---Tutorials---" section header followed by a "...tutorials" spread.
BIA runs in docs_mode "authored" which routes through buildFrameworkOnlyNav;
that path preserves section headers and recurses through spreads. The
spread descended into integrations/built-in-agent/tutorials/ and
enumerated the AI Todo App and AI Textarea subfolders into the sidebar.
Other frameworks were already safe: buildFrameworkOverridesNav (used by
generated-mode frameworks like langgraph, mastra, google-adk) explicitly
strips section nodes from per-framework override nav.
Fix: remove the "---Tutorials---" section header and "...tutorials"
spread from BIA's parent meta.json. Nothing now references the
tutorials folder from BIA's nav, so the spread handler never recurses
into it and the entries disappear. Two-line minimal change.
Tutorial source MDX under content/docs/integrations/built-in-agent/tutorials/
stays in place (PDX-100 owns the rewrite). The redirect catalog in
lib/seo-redirects.ts is untouched.
Refs PDX-205.
inlineSnippets() runs after stripLeadingImports() removes the MDX's
import lines, so its regex scan can't tell whether a `<Component />`
reference is a snippet lookup or a real React component rendered via
the docsComponents registry. Every imported component triggered a
false-positive "[docs-render] snippet missing for component X"
warning.
Capture the set of imported component names BEFORE stripping, then
short-circuit the warning when the regex hits one of those names.
Catches NewLookAndFeelPreview, AgentCoreCommandTabs, CodePanel,
CodeShowcase, Frame, IframeSwitcher, ImageAndCode, LinkToCopilotCloud,
and StartProviders (the full current set imported into snippet MDX
files under src/content/snippets/), plus any future additions
automatically.
The runtime render path is unchanged: the docsComponents registry
in mdx-registry.tsx still resolves these names at render time.
## Summary
Three independent regressions surfaced during Phase 6 post-cutover
validation against the live docs.copilotkit.ai. Bundled together since
they all live under `showcase/shell-docs/` and were verified together
end-to-end.
### 1. Soft-404 returning HTTP 200 with not-found UI body
Unknown URLs returned `HTTP 200` with the Next.js "404: This page could
not be found" body. Internal Next 404 markers
(`NEXT_HTTP_ERROR_FALLBACK`, `<meta name="robots" content="noindex">`)
were present in the body but the wire status stayed 200. Google treats
this as low-quality content and demotes the entire site.
Root cause: `<Suspense fallback={null}>` in `app/layout.tsx` committed
the response stream at status 200 before page-level `notFound()` could
flip it.
Fix:
- Removed the layout Suspense wrapper (both PostHogProvider and
FrameworkProvider are `"use client"` with no suspending APIs, so the
boundary was incidental from the original telemetry port).
- Added an explicit `src/app/not-found.tsx` rendering a branded 404
page.
- Marked the three catch-all routes `dynamic = "force-dynamic"` so
unknown slugs always re-evaluate at request time. Reference route stays
SSG (its slugs come from `referenceStaticParams`).
Verified: `/this-clearly-does-not-exist` returns `HTTP 404`. Real pages
return 200.
### 2. Per-page metadata + OG / Twitter cards
All four `generateMetadata` functions returned only
`alternates.canonical`. Every page inherited the layout's generic `<meta
name="description">` and emitted zero `og:*` / `twitter:*` tags. Every
social share unfurled bare.
Fix: routes now build full `Metadata` via a shared
`src/lib/seo-metadata.ts` helper that reads MDX frontmatter for title
and description and emits openGraph + Twitter card with absolute URLs.
Bonus fix in `app/og/[...slug]/route.tsx`: the OG image route fetched
Inter TTFs from `fonts.gstatic.com` on every request. Any failure
(Railway egress, font URL drift) tripped the catch block, which 307'd to
a 25-byte broken CDN fallback. Dropped the runtime font fetch (Satori's
default sans-serif renders cleanly), broadened slug resolution to also
try `integrations/<folder>/<slug>` paths, and replaced the
broken-fallback redirect with a real 500 + log.
Verified locally: full og/twitter meta set on every page;
`/og/built-in-agent/quickstart/og.png` and
`/og/langgraph-python/quickstart/og.png` both return 1200x630 PNGs with
branded backgrounds.
### 3. Pydantic-ai shared-state pages 500
`/pydantic-ai/shared-state/in-app-agent-read` and
`/pydantic-ai/shared-state/in-app-agent-write` returned deterministic
HTTP 500. Same paths on all other frameworks returned 200. A full
sitemap crawl (2451 URLs) found these as the only 5xx on the entire
site.
Root cause: both MDX files at
`src/content/docs/integrations/pydantic-ai/shared-state/in-app-agent-{read,write}.mdx`
were truncated/malformed during the v1→v2 content port — `read.mdx`
ended mid-Python-fence with unclosed `<Step>` / `<Steps>`; `write.mdx`
had a Python code fence containing JS/TSX. Pure MDX parse failure during
SSR.
Fix: restored both files from the canonical legacy source under
`docs/content/docs/integrations/pydantic-ai/shared-state/`, stripped
leading `import` blocks per the convention used by other pydantic-ai
pages (components resolve via `docsComponents` in
`src/lib/mdx-registry.tsx`).
Verified locally: both URLs go 500 → 200.
### 4. `/reference/v1/:path*` redirect dropped its suffix
Catalog rule P10 redirected `/reference/v1/hooks/useCopilotChat` to a
generic `/reference/v2` index instead of
`/reference/v2/hooks/useCopilotChat`. Users following v1 docs links from
product code messages landed on the wrong page.
Fix: one-line change in `seo-redirects.ts`: destination `/reference/v2`
→ `/reference/v2/:path*`. Audited all other catalog rules with `:path*`
source and bare destination — remaining cases (`concepts/*` collapse,
tutorials deprecation wildcards) are documented intentional
wildcard-to-single-page rules, not drift.
## Test plan
- [x] `npm run typecheck`, `npm run lint`, `npm test` (32 tests pass),
`npm run build` all green in `showcase/shell-docs/`.
- [x] Local prod-mode walkthrough on all four fix surfaces:
- `/built-in-agent/quickstart` → 200 + full og/twitter meta + per-page
description + branded OG PNG.
- `/langgraph-python/voice` → 200, live demo iframe renders.
- `/built-in-agent/garbage-page-xyz` → 404 (real status, branded 404
page).
- `/pydantic-ai/shared-state/in-app-agent-read` → 200.
- `/reference/v1/hooks/useCopilotChat` → 301 →
`/reference/v2/hooks/useCopilotChat` → 200.
- [ ] Post-deploy: re-curl a sample of soft-404 URLs against prod and
confirm wire status is `404`, not `200`.
- [ ] Post-deploy: validate a docs URL share in Slack / X to confirm OG
card renders with title + description + image.
Five post-cutover follow-ups bundled together because all surfaced in
the same spot-check pass on `/integration/<page>` routes.
## 1. Tag `page-send-message` region (`4680eb9c1`)
`/langgraph-python/programmatic-control` and
`/google-adk/programmatic-control` rendered a yellow "Missing snippet"
callout because `<Snippet region="page-send-message" />` had no matching
`// @region[page-send-message]` / `// @endregion[page-send-message]`
pair in the resolved `headless-complete` cell. Peer integrations
(mastra, ag2, strands, pydantic-ai, llamaindex, langgraph-fastapi,
crewai-crews, …) already had the tags; only north-star and its ADK
mirror were missing them. The region wraps the connect / send / stop
block in `chat/chat.tsx`.
## 2. Suppress HubSpot-rewritten href hydration mismatch on nav-bar
(`2c0791930`)
HubSpot's analytics tag (loaded from `js-na2.hs-analytics.net`) rewrites
the Intelligence CTA's outbound `href` client-side to append `__hstc` /
`__hssc` / `__hsfp` cross-domain tracking params. Server-rendered HTML
keeps the bare URL, post-hydration DOM has the rewritten URL, React's
hydration diff fires.
Add `suppressHydrationWarning` to the two anchor elements that point at
`INTELLIGENCE_CTA_HREF` (desktop BrandNav `LEFT_LINKS` entry,
MobileTopNav Lightbulb icon).
## 3. Register `UseAgentSnippet` (`f809b9b8b`, expanded by `773631cbd`)
`inlineSnippets()` in `docs-render.tsx` maintains its own `SNIPPET_MAP`
separate from `mdx-registry.tsx`'s `STUB_PARTIAL_MAP`. The two
registries drifted. `UseAgentSnippet` was the most-hit miss, but Railway
logs surfaced 14 more: `InstallSDKSnippet`, `InstallPythonSDK`,
`RunAndConnect` (+ `Snippet` alias), `CopilotUI`, `LandingCodeShowcase`,
the four `CopilotCloudConfigure*` / `SelfHostingCopilotRuntime*` keys,
plus `MigrateTo` / `MigrateToV` / `ToolRenderer` aliases. All added.
## 4. Make `inlineSnippets()` code-fence-aware + add Icon-suffix
heuristic (`773631cbd`)
After the registry fix, the remaining `[docs-render] snippet missing`
log entries split into two false-positive classes:
- **Code-fence false positives.** The regex matched `<Component />`
references inside ` ```tsx ``` ` example blocks — e.g. `<CopilotChat />`
/ `<CopilotSidebar />` shown as runtime usage, `<WeatherCard />` /
`<YourApp />` as placeholders. A new `isInsideCodeFence(content,
offset)` helper tracks fenced blocks (matching any indentation — MDX
inside `<Step>` is routinely 8-space-indented) and inline-code spans.
Replaces the ad-hoc `CopilotChat`-only allowlist from commit 3.
- **JSX-prop runtime components.** `icon={<PaintbrushIcon />}` etc. are
real React components from `mdx-registry.tsx::docsComponents`, not
snippets. Add an `Icon`-suffix heuristic: lucide icons used as JSX props
are silenced.
## 5. Suppress HubSpot hydration mismatch on `<OpsPlatformCTA>` +
`<SignupLink>` (`10b4960a3`)
Same HubSpot rewrite hits every dashboard.operations.copilotkit.ai
outbound link. Add `suppressHydrationWarning` to all four `<a>` tags in
`OpsPlatformCTA` (`info` / `inline` / `tile` / `card` variants) and the
single `<a>` in `SignupLink`. Observed live as a hydration error on
`/<framework>/prebuilt-components`, `/<framework>/headless`, and any
page that embeds an Intelligence-platform CTA.
## Verification
- `grep -n "@region\[page-send-message\]"
showcase/integrations/{langgraph-python,google-adk}/src/app/demos/headless-complete/chat/chat.tsx`:
both files have start (line 38) + end (line 114) markers; `diff` between
them is empty post-change.
- `npx tsx showcase/scripts/bundle-demo-content.ts`: regenerated
`demo-content.json` exposes `regions["page-send-message"]` for both
`langgraph-python::headless-complete` and
`google-adk::headless-complete` (1878 bytes, `chat/chat.tsx` lines
38-112).
- Playwright sweep across `/programmatic-control`,
`/runtime-server-adapter`, `/frontend-tools`,
`/generative-ui/tool-rendering`, `/prebuilt-components`,
`/deploy/agentcore`, `/auth` on `google-adk` and `mastra`: 0 console
errors, 0 warnings, 0 "Missing snippet" callouts in rendered DOM, both
desktop (1440px) and mobile (390px) viewports.
## Test plan
- [ ] Pull, build shell-docs, smoke
`/langgraph-python/programmatic-control` and
`/google-adk/programmatic-control`: yellow "Missing snippet" callout is
gone.
- [ ] Same pages on a mobile viewport: no hydration warning in the
console.
- [ ] `/<framework>/prebuilt-components` and any page with an inline
`<OpsPlatformCTA>`: no hydration warning.
- [ ] Peer integration pages (e.g. `/mastra/programmatic-control`,
`/<framework>/deploy/agentcore`, `/<framework>/frontend-tools`):
snippets still render, no `[docs-render] snippet missing` warnings.
- [ ] Redeploy shell-docs.
## Out of scope
- Underlying prose-vs-code parity gap on the headless-complete cell
(north-star uses `agent.abortRun()` and skips `connectAgent`) is tracked
separately.
- Unifying `docs-render.tsx::SNIPPET_MAP` and
`mdx-registry.tsx::STUB_PARTIAL_MAP` into a single source of truth (so
future entries can't drift) is the right architectural follow-up. Filed
separately.
- Environmental jsdom × vitest interaction blocking
`packages/web-inspector/src/lib/__tests__/telemetry.test.ts` (which
forced `--no-verify` on these commits) is tracked separately.
Three post-cutover SEO/social gaps surfaced once docs.copilotkit.ai
pointed at shell-docs:
- `generateMetadata` in the four catch-all routes returned only the
canonical link, so every page inherited the layout's generic title
and description and zero og/twitter tags shipped. Every share unfurled
bare. Routes now build full Metadata via a shared helper
(`src/lib/seo-metadata.ts`) that reads MDX frontmatter for title and
description and emits openGraph + twitter card with absolute URLs.
- The `/og/<slug>` route fetched Inter TTFs from fonts.gstatic.com on
every request. Any failure tripped the catch block, which 307'd to a
broken CDN fallback. Removed the runtime font fetch and let Satori
fall back to its built-in sans-serif; also broadened the slug
resolution to try the framework-scoped `integrations/<folder>/<slug>`
path so OG images render for framework variants too. Catch block now
surfaces real failures as 500 instead of redirecting to a broken PNG.
- Unknown URLs returned HTTP 200 with a not-found UI body (soft-404).
Root cause was a `<Suspense fallback={null}>` wrapper in the root
layout that committed the response stream before page-level
`notFound()` could set the 404 status. Removed the Suspense, added an
explicit `src/app/not-found.tsx`, and marked the catch-all routes
`force-dynamic` so unknown slugs always re-evaluate at request time.
Verified locally: `/this-clearly-does-not-exist` returns HTTP 404,
`/built-in-agent/quickstart` returns HTTP 200 with full meta tags.
The cutover to `docs_mode: authored` for pydantic-ai exposed two MDX
files that had been ported in a truncated state during the v1->v2
content migration:
integrations/pydantic-ai/shared-state/in-app-agent-read.mdx
integrations/pydantic-ai/shared-state/in-app-agent-write.mdx
`in-app-agent-read.mdx` ended mid-python-fence at
`if __name__ == "__main__":` with no closing ```, no closing `</Step>`,
no closing `</Steps>`. `in-app-agent-write.mdx` had a python code block
that switched to TSX content mid-fence (Python `if __name__` followed
by JS `// ...` and a TSX function inside a `python` block), which the
MDX/Shiki pipeline then tried to parse as Python.
Both produced SSR 500s in production (Railway edge: text/plain
"Internal Server Error") at:
/pydantic-ai/shared-state/in-app-agent-read
/pydantic-ai/shared-state/in-app-agent-write
These were the only two 5xx URLs in the full 2451-URL sitemap crawl.
Every other framework variant of the same paths (langgraph-python,
mastra, built-in-agent, google-adk, etc.) returned 200, confirming the
crash was content-specific to pydantic-ai.
Restore the full content from the canonical legacy source at
`docs/content/docs/integrations/pydantic-ai/shared-state/` (which was
intact, 178+188 lines), with the leading `import` block stripped to
match the convention used by the other ported pydantic-ai pages
(`predictive-state-updates.mdx` etc.) where `RunAndConnect`,
`IframeSwitcher`, and friends are resolved via `docsComponents` in
`src/lib/mdx-registry.tsx` rather than per-file imports.
Verified locally with `next dev`:
/pydantic-ai/shared-state/in-app-agent-read 500 -> 200
/pydantic-ai/shared-state/in-app-agent-write 500 -> 200
The P10 catalog rule was stripping the suffix when redirecting legacy
v1 reference URLs, sending /reference/v1/hooks/useCopilotChat to the
generic /reference/v2 index instead of /reference/v2/hooks/useCopilotChat.
Affects user-facing console messages in packages/react-core that ship
/reference/v1/hooks/useCopilotChatHeadless_c links and bounced users to
the index page instead of the specific hook reference.
Middleware substitutes :path* in destinations (verified against the
coagents rule), so adding :path* to the destination is enough to fix it.
OpsPlatformCTA and SignupLink both link out to
dashboard.operations.copilotkit.ai, which HubSpot's analytics tag
rewrites client-side to attach `__hstc` / `__hssc` / `__hsfp`
cross-domain tracking params. Same root cause as the prior nav-bar
Intelligence CTA fix — server-rendered href has the bare URL,
post-hydration DOM has the rewritten URL, React flags a hydration
mismatch on every page that surfaces the inline / tile / card CTA
(observed live on /<framework>/prebuilt-components and
/<framework>/headless among others).
Add `suppressHydrationWarning` to all four <a> tags in
OpsPlatformCTA (card / inline / tile / link-inside-card) and the
single <a> in SignupLink. Scoped to those anchors so genuine
mismatches elsewhere still surface.
Railway logs surfaced 15+ distinct `[docs-render] snippet missing for
component …` warnings post-cutover. Root causes split three ways:
1. Registry drift. `docs-render.tsx::SNIPPET_MAP` had drifted from
`mdx-registry.tsx::STUB_PARTIAL_MAP` — InstallSDKSnippet,
InstallPythonSDK, RunAndConnect (+ Snippet alias), CopilotUI,
LandingCodeShowcase, the four CopilotCloudConfigure* /
SelfHostingCopilotRuntime* keys, plus MigrateTo / MigrateToV /
ToolRenderer aliases were all missing. Add them.
2. Code-fence false positives. The inliner regex matched
`<Component />` references inside ```tsx``` example blocks (e.g.
`<CopilotChat />`, `<CopilotSidebar />` shown as runtime usage,
`<WeatherCard />` / `<YourApp />` placeholders). Make the regex
fence-aware via a new `isInsideCodeFence(content, offset)` helper
that tracks both fenced blocks (any indentation — MDX inside
`<Step>` is routinely 8-space-indented) and inline-code spans.
3. JSX-prop runtime components. `icon={<PaintbrushIcon />}` etc. are
registered in `mdx-registry.tsx::docsComponents` as real React
components, not snippets. Add an `Icon`-suffix heuristic: lucide
icons used as JSX props are silenced. CopilotChat / CopilotSidebar
in prose backticks are now silenced by (2) instead of the prior
ad-hoc allowlist, which is removed.
Verified clean across the previously-warning pages — /programmatic-control,
/runtime-server-adapter, /frontend-tools, /generative-ui/tool-rendering,
/prebuilt-components, /deploy/agentcore, /auth — all 0 docs-render
warnings post-change. Unified-registry refactor (single source of
truth) is the right next step but out of scope for this cutover-blocker
pass.
inlineSnippets() in docs-render.tsx maintains its own SNIPPET_MAP
separate from mdx-registry.tsx. The two registries drifted: mdx-registry
gained `UseAgentSnippet: "use-agent.mdx"` but docs-render didn't. As a
result every page that imports `<UseAgentSnippet />` (the shared
programmatic-control snippet, used by all integration overrides) logged
"[docs-render] snippet missing for component UseAgentSnippet" at SSR
time, and the snippet content was missing from non-MDX surfaces
(search-index, llms.txt, OG metadata).
Add the missing entry so the inliner can resolve it.
Separately, the inliner regex isn't code-fence-aware, so `<CopilotChat />`
usages inside example code blocks (slots.mdx, threads.mdx, etc.) were
also flagged as missing snippets even though CopilotChat is a runtime
React component, not a snippet. Add a small allowlist of known React
components so the regex short-circuits on those without warning. A
fence-aware regex is the right architectural fix but out of scope here.
Confirmed via Playwright: /mastra/programmatic-control console drops
from 3 [docs-render] warnings to 0 after the change.
HubSpot's analytics tag (loaded from js-na2.hs-analytics.net) rewrites
the Intelligence CTA's outbound href client-side to append `__hstc` /
`__hssc` / `__hsfp` cross-domain tracking params. Server-rendered HTML
keeps the bare URL, so the post-hydration DOM differs and React's
hydration diff fires a "tree hydrated but some attributes... didn't
match the client properties" warning.
Add `suppressHydrationWarning` to the two anchor elements that point at
`INTELLIGENCE_CTA_HREF` (the desktop BrandNav LEFT_LINKS entry and the
MobileTopNav Lightbulb icon). Suppress is scoped to just those anchors
— other nav items still flag genuine mismatches.
Confirmed via Playwright: page console drops from 1 hydration error to
0 on /mastra/programmatic-control after the change.
## Summary
- Add wildcard 301 redirects for the deprecated `/tutorials/*` URL space
in the shell-docs redirect catalog.
- Framework-scoped tutorial URLs redirect to that framework's
`/quickstart`; unscoped variants redirect to the docs root.
- Covers all 21 canonical framework slugs (built-in-agent,
langgraph-{python,typescript,fastapi}, google-adk, a2a, agent-spec,
deepagents, mastra, crewai-crews, pydantic-ai, agno, ag2, llamaindex,
strands, ms-agent-{python,dotnet}, claude-sdk-{python,typescript},
langroid, spring-ai).
## Why
The step-2 tutorial MDX
(`tutorials/ai-todo-app/step-2-setup-copilotkit.mdx` and
`tutorials/ai-powered-textarea/step-2-setup-copilotkit.mdx`) crashes
during SSR for every active framework slug, returning a 21-byte
`text/plain` 500 from `railway-edge`. Sibling steps (`overview`,
`step-1`, `step-3`, `next-steps`) render fine. The sitemap lists ~38 of
these URLs. Two of them are in the legacy sitemap as 200s.
The tutorials section is being retired, so the right operational
response is a 301 to a working destination rather than a renderer fix.
## Implementation
- New `CANONICAL_FRAMEWORKS` constant alongside the existing
`FRAMEWORKS` legacy-slug array.
- Generated wildcard entries `/${fw}/tutorials/:path*` →
`/${fw}/quickstart` per framework.
- Two explicit entries for unscoped paths: `/tutorials/:path*` → `/` and
`/tutorials` → `/`.
- Slotted in `WILDCARD_REDIRECTS` before the per-framework `P1×`/`P2×`
catch-alls so the more specific tutorial rule wins.
## Test plan
- [ ] Local: `npm run dev` in `showcase/shell-docs/` and curl a sample
of step-2 URLs, confirm 301 to `/{fw}/quickstart`.
- [ ] Local: `npm run typecheck` in `showcase/shell-docs/` (catalog is
pure data; TypeScript catches shape drift).
- [ ] Post-deploy: re-curl the 38 step-2 URLs from the production
sitemap and confirm 301 chains land on a 200.
Wave-2 follow-up to #4986. With the next/image optimizer disabled
(images.unoptimized: true), gen-ui-specs-light.png and gen-ui-specs-dark.png
were shipping uncompressed instead of being served as WebP via /_next/image.
Pre-baking WebP variants restores the bandwidth savings: light 408,807 -> 126,982
bytes (3.2x smaller) and dark 496,127 -> 167,118 bytes (3.0x smaller). Source
PNGs are retained alongside as fallbacks.
The tutorials section is retired post-cutover. Step-2 MDX (both ai-todo-app
and ai-powered-textarea) currently 500s in the SSR layer, surfacing across
all active framework slugs and in ~38 sitemap entries. Add wildcard 301s
in the redirect catalog so framework-scoped tutorial URLs land on that
framework's quickstart, and unscoped variants land on the docs root.
Covers all 21 canonical framework slugs (generated, authored, hidden) plus
the unscoped /tutorials and /tutorials/* paths. Slotted before the P1×/P2×
per-framework catch-alls so the more specific tutorial rule wins.
Post-cutover, every image on docs.copilotkit.ai broke because Next.js's
/_next/image optimizer needs the sharp module at runtime and sharp is
missing from the Railway runtime image. Setting images.unoptimized=true
makes <Image> render as a plain <img> pointing at the source URL,
eliminating the sharp dependency entirely.
This is visually identical for users: our CDN (cdn.copilotkit.ai,
CloudFront/S3) ignores ?fm=webp and serves the cached PNG regardless,
so the optimizer was already producing no format-conversion gains for
CDN-hosted images. A wave-2 follow-up will pre-bake WebP variants of
the two 4K gen-ui-specs-*.png files for bandwidth.
The programmatic-control docs page renders a yellow "Missing snippet"
box on the langgraph-python and google-adk variants because their
headless-complete cells were never tagged with the page-send-message
region the MDX requests. Add matching @region / @endregion markers
around the useAgent / useCopilotKit / send / reset block in
chat/chat.tsx so the Snippet component resolves on both integrations.
Re-bump on top of main's new 102 baseline (PR #4985 absorbed the
pre-existing 9-FAIL drift). Our PR adds 4 intentional version-pin
divergences from Dojo (copilotkit 0.1.87 -> 0.1.90 in three Python
integrations, ag-ui-langgraph from ==0.0.34 to >=0.0.35 in
langgraph-fastapi), bringing the total to 106.
The ag-ui-langgraph and copilotkit catch-up will reduce this back
down once both release pipelines complete and Dojo reference
examples pick up the new versions.
Add LANGGRAPH_HTTP={"configurable_headers":{"include":["x-*"]}} to the
shared x-integration-defaults anchor so every LangGraph-based showcase
service tells langgraph-api to include x-* headers in the runtime
config. Without this env var, langgraph-api 0.7+ strips x-* headers
before the agent sees them, breaking D6 context routing end-to-end even
when the SDK side is correct.
Pin copilotkit==0.1.90 across the three CopilotKit-aware Python
integrations (langgraph-python, strands, langgraph-fastapi) so the
forwarded-header extraction from this PR is the version that runs in
showcase. Bump ag-ui-langgraph to >=0.0.35 with the [fastapi] extra in
langgraph-fastapi because copilotkit 0.1.90 requires it transitively;
the previous ==0.0.34 pin would cause pip install to hard-fail.
The "next" dist-tag was a workaround for Docker builds that can't resolve
workspace:* — but "next" has gone stale (1.55.2-next.1) while "latest" is
at 1.56.5. Renovate doesn't cover showcase/, so these never auto-bumped.
Switch all 19 showcase package.json files to "latest".
## Summary
- The MS Agent Python integration's `reasoning-default` and
`reasoning-custom` demos were already fully ported from the
langgraph-python north-star — code, agent
(`src/agents/reasoning_agent.py` using the OpenAI Responses API for
`REASONING_MESSAGE_*` event streaming), pages, suggestion pills, e2e
specs (`tests/e2e/reasoning-default.spec.ts`,
`tests/e2e/reasoning-custom.spec.ts`), aimock fixtures
(`showcase/aimock/d5-all.json`,
`showcase/harness/fixtures/d5/reasoning-display.json`) and D5 probe
mapping all exist and are byte-identical to LGP.
- The only missing piece was the `manifest.yaml` registration. Without
it the cells never appeared in the showcase shell, weren't counted as
features, and were skipped by D5 routing.
- This PR adds:
- `reasoning-custom` + `reasoning-default` to the `features:` list
(between `headless-complete` and `frontend-tools`, matching LGP order).
- `demos:` entries for both, mirroring the LGP manifest verbatim.
## Verification
- `tsx showcase/scripts/generate-registry.ts` → catalog now lists both
cells with `status: wired`, `max_depth: 4`, identical to LGP.
- `tsx showcase/scripts/validate-parity.ts` → `ms-agent-python [PASS] 38
37 10 35 warn` (was 36/35; the 2 new e2e specs were already present).
New warnings are the standard `no qa/...md` pattern that LGP also has
for these two demos.
- `tsx showcase/scripts/validate-pins.ts` → ratchet count stays at 93
(unchanged).
## Test plan
- [x] generate-registry succeeds; catalog wired for both cells with
max_depth 4
- [x] validate-parity passes
- [x] validate-pins ratchet unchanged
- [ ] Showcase shell renders
`/integrations/ms-agent-python/demos/reasoning-default` and
`reasoning-custom` after deploy
- [ ] D5 `reasoning-display` probe passes for ms-agent-python in CI
- [ ] e2e: `npm --prefix showcase/integrations/ms-agent-python run
test:e2e -- tests/e2e/reasoning-default.spec.ts
tests/e2e/reasoning-custom.spec.ts --project=chromium` (will run on next
CI pipeline)
Surfaces the reasoning-default and reasoning-custom demos for the MS
Agent Python integration. The code, agent, UI, suggestions, e2e specs,
D5 probe mapping and aimock fixtures were already ported from the
langgraph-python north-star — only the manifest entries were missing,
which meant the cells never appeared in the showcase shell, weren't
counted as features, and weren't picked up by D5 routing.
Adds:
- `reasoning-custom` + `reasoning-default` to the features list
(between headless-complete and frontend-tools, matching LGP order).
- `demos:` entries for both, mirroring the LGP manifest verbatim.
After regeneration the shell catalog now reports the two cells with
`status: wired` and `max_depth: 4`, identical to LGP. validate-parity
goes 38 demos / 37 specs (the e2e specs were already present); the
ratchet validate-pins count stays at 93. The remaining `no qa/...`
warnings match the existing LGP/MAF pattern (LGP also has no
qa/reasoning-*.md), so no new QA docs are introduced here.
## Summary
- The `mcp-apps` and `voice-demo` HttpAgent URLs had a trailing slash
(`${AGENT_URL}/mcp-apps/`, `${AGENT_URL}/voice/`), but the FastAPI
backend in `agent_server.py` mounts those agents at `/mcp-apps` and
`/voice` exactly.
- Posting to the trailing-slash URL triggers FastAPI's default
`redirect_slashes` 307, which drops the SSE streaming body and surfaces
in the Next.js runtime as `RUN_ERROR: fetch failed (INCOMPLETE_STREAM)`
for every pill on the deployed showcase.
- Removing the trailing slash from both `HttpAgent({ url })`
constructors mirrors every other ms-agent-python subpath URL
(`/hitl-in-app`, `/headless-complete`, `/multimodal`, `/agent-config`,
…), all of which already work.
## Reproduction (live, against `showcase-ms-agent-python-production`)
```
$ curl -X POST -H 'Content-Type: application/json' -H 'Accept: text/event-stream' -d '{"method":"agent/run","params":{"agentId":"mcp-apps"},"body":{...}}' https://showcase-ms-agent-python-production.up.railway.app/api/copilotkit-mcp-apps
data: {"type":"RUN_ERROR","message":"fetch failed","code":"INCOMPLETE_STREAM"}
$ curl -X POST ... https://showcase-ms-agent-python-production.up.railway.app/api/copilotkit-voice/agent/voice-demo/run
data: {"type":"RUN_ERROR","message":"fetch failed","code":"INCOMPLETE_STREAM"}
```
The same `/api/copilotkit-mcp-apps` runtime called with `agentId:
"headless-complete"` (URL `/headless-complete`, no trailing slash)
returns a clean `RUN_FINISHED` stream — proving the trailing slash is
the only difference.
## Test plan
- [x] Reproduced live against deployed
`showcase-ms-agent-python-production`
- [x] `headless-complete` (no trailing slash, same runtime as mcp-apps)
confirmed working
- [ ] After Railway redeploy: click "Draw a flowchart" and "Sketch a
system diagram" pills on `/integrations/ms-agent-python/demos/mcp-apps`
and the iframe renders
- [ ] After Railway redeploy: voice demo sample-audio "What is the
weather in Tokyo?" returns an assistant reply
The mcp-apps and voice-demo HttpAgent URLs had a trailing slash
(`${AGENT_URL}/mcp-apps/`, `${AGENT_URL}/voice/`), but the FastAPI
backend in agent_server.py mounts those agents at `/mcp-apps` and
`/voice` exactly. Posting to the trailing-slash URL triggers FastAPI's
default `redirect_slashes` 307, which drops the SSE streaming body and
surfaces in the runtime as
`RUN_ERROR: fetch failed (INCOMPLETE_STREAM)` for every pill click on
the deployed ms-agent-python showcase.
Reproduced live against showcase-ms-agent-python-production. Every
other ms-agent-python HttpAgent URL (`/hitl-in-app`,
`/headless-complete`, `/multimodal`, `/agent-config`, etc.) already
uses no trailing slash and works fine, confirming the trailing slash
is the only delta.
PR #4956 bumped @copilotkit/aimock in showcase/scripts/package.json
from "latest" to "1.26.1" but did not regenerate package-lock.json
(still pinned to 1.16.4). This broke the Showcase Docker builds for
showcase-harness and shell-dashboard, since both run `npm ci` inside
showcase/scripts/ and `npm ci` fails on out-of-sync lockfiles.
The original analytics commit (4d67fe269) was reverted because it
also contained a botched BrandNav height change. Re-apply just the
PostHog instrumentation cleanly:
- `MarkdownCopyButton` fires `markdown_copied`
`{ path: pathname, markdown_url: markdownUrl }` after a successful
clipboard write. Coexists with the global `cli_command_copied`
(Benjamin's monkey-patch in `lib/track-command-copy.ts` that
intercepts every clipboard write at the navigator level); the
dedicated event lets the dashboard distinguish page-content copies
from CLI copies (which classify under the existing tracker as
`code` — not meaningful for the new docs-as-context surface).
- Each `ViewOptionsPopover` item gains a `target` discriminator
(`github`, `view-as-markdown`, `windsurf`, `claude-code`, `codex`,
`chatgpt`, `claude`, `cursor`) and an `onClick` that captures
`open_in_llm_clicked` `{ target, path }`. PostHog buffers locally
so the new tab opens without waiting on the network.
Closes Sam's P0 analytics ask from PR #4946 — verified by the QA
audit that every existing CTA + capture (`try_for_free_clicked`,
`talk_to_us_clicked`, etc.) survives intact across the branch.
Fumadocs's docs grid sets `--fd-docs-row-1: var(--fd-banner-height, 0px)`
in `node_modules/fumadocs-ui/dist/layouts/docs/slots/container.js:25`,
and the sidebar wrapper uses that value as both its sticky-top offset
AND its in-grid top offset (`top-(--fd-docs-row-1)` plus
`h-[calc(var(--fd-docs-height)-var(--fd-docs-row-1))]`). That design
assumes the banner is sticky / fixed at the viewport top — so the
sidebar starts BELOW the banner.
shell-docs renders the banner in NORMAL body flow above BrandNav.
Banner pushing things down via flow is sufficient; the extra
`--fd-docs-row-1` offset double-counts the banner height. Result:
when the banner appears, BrandNav drops 54px (correct, flow) AND
the sidebar drops an ADDITIONAL 54px below BrandNav (wrong) —
visible as a yawning gap between BrandNav's bottom edge and the
sidebar's framework picker that didn't exist when the banner was
dismissed.
Pin `--fd-docs-row-1: 0px` on `#nd-docs-layout` for md+ so the
sidebar tracks BrandNav's bottom edge consistently, banner or no
banner. The mobile branch (where MobileTopNav is `position: fixed`)
keeps its existing `padding-top: var(--fd-nav-height)` so the docs
grid clears the fixed mobile nav — independent concern, untouched.
Verified at 1440×900 with banner present: BrandNav bottom = 142,
sidebar top = 166, gap = 24px (matches `main`'s `md:mt-6`). Without
banner: BrandNav bottom = 88, sidebar top = 112, gap = 24px. Same
gap in both cases.