Use `CopilotKit/CopilotKit/skills -y` instead of the repo root: root
discovery sweeps in the internal `showcase-demo-debugging` skill
(metadata.internal, lives in .claude/.agents, not skills/), so users got
12 skills incl. one internal. The /skills subpath yields exactly the 11
published skills. Drop -g so install defaults to project scope, letting each
project pin the skills version matching its CopilotKit dependencies.
The build-with-agents guide recommended a bare `npx skills add` that drops
human users into a multi-step interactive flow (skill multiselect, agent
selection, scope, install method, confirm). Recommend `-g -y` so all skills
install globally in one shot, with a Callout pointing to the flag-less command
for users who want to choose interactively.
## Problem
Shell-docs had conflicting v2 guidance around the provider import path.
Some migration/reference/quickstart pages either recommended
`CopilotKitProvider` or kept `CopilotKit` examples on the root
`@copilotkit/react-core` package even though v2 docs should import the
`CopilotKit` component from `@copilotkit/react-core/v2`.
## Why
The correct recommendation is the `CopilotKit` component name, imported
from the v2 entrypoint. Leaving root-package imports in v2-facing docs
makes the migration and reference guidance contradict the v2 package
layout.
## Fix
- Recommend `CopilotKit` from `@copilotkit/react-core/v2`, not
`CopilotKitProvider`.
- Update v2 migration, reference, and quickstart examples to use the v2
provider/style entrypoints.
- Leave root `@copilotkit/react-core` imports only in v1 docs and
explicit migration “Before” examples.
- Add regression coverage for stale provider/style package paths.
- Fix the shell-docs SignupLink SSR test typing exposed by typecheck.
Closes#5153
The reference route rendered fenced code blocks as bare, unstyled
<pre> (no highlighting, no copy button) because its MDXRemote call
omitted the rehypeCode plugin and the pre: MdxCodeBlock override that
the main docs pipeline uses. Wire both in (verbatim from the framework
route) so reference code blocks match the rest of the docs. Fixes
rendering for all reference SDKs (React v2/v1 + Core).
Introduces a third SDK in the reference docs alongside React v2 and v1:
- reference-items.ts: add the 'core' version, generalize root-vs-nested
routing, add 'types'/'enums' subdirs + categories, recognize a core/
slug prefix (literal strip), and emit its static params
- reference-version-selector.tsx: relabel the picker as an SDK switch
(React v2 / React v1 / Core (TypeScript)), import ReferenceVersion from
reference-items, give listbox options role=option/aria-selected
- app/reference/page.tsx: rename 'API Reference' to 'Overview' and add a
'Choose your SDK' card chooser
Clarify the wrapper's role (it forces noStore:false because unstable_noStore is
unavailable in middleware/Edge). Pure rename across shell, shell-docs, and
shell-dashboard: definitions, middleware call sites, and tests. No behavior
change.
Addresses PR #5087 review comment from @tylerslaton:
> When we open the cookbook section, the sidebar should update to include
> only the recipes. Similar to how Reference works today.
Mirror the dedicated-route approach Reference uses (app/reference/page.tsx
+ app/reference/[...slug]/page.tsx), reusing the existing MDX flow via
DocsPageView's pre-built navTree prop:
- app/cookbook/page.tsx — landing route. Builds a navTree scoped to the
cookbook subdir (buildNavTree(CONTENT_DIR/cookbook, 'cookbook')) and
passes it to DocsPageView so the sidebar shows only cookbook entries.
- app/cookbook/[...slug]/page.tsx — catch-all for /cookbook/<recipe>,
using the same scoped navTree.
- Remove the '---Cookbook---' divider and '...cookbook' spread from
showcase/shell-docs/src/content/docs/meta.json so cookbook no longer
appears in the Documentation sidebar (only via the navbar tab).
Verified locally:
- /cookbook and /cookbook/daytona serve 200 with sidebar scoped to
Overview + Daytona only (active highlight tracks the current page).
- / has exactly one /cookbook anchor — the navbar tab — and no cookbook
entries in the Documentation sidebar.
- /built-in-agent sidebar has zero /cookbook entries.
OSS-222
Non-functional cleanup pass on the showcase deploy-pipeline integration
branch. All changes are scoped to comment rot, log severity for already-
demoted runtime-config fields, length-aware env-name coalescing (a
deliberately-empty primary no longer masks a populated alternate), and
test-quality tightening. No production behavior change beyond the
specific items below.
Changes by area:
- shell/shell-dashboard/shell-docs runtime-config.ts: factor the
`process.env[primary] ?? process.env[alt]` chain into a shared
length-aware `readEnvPair` helper. The prior `??` form treated
`PRIMARY=""` as set, masking a populated alternate; the helper now
treats empty-string as unset and falls through to the alternate.
- shell-docs runtime-config.ts: demote the two recoverable URL fields
(`intelligenceSignupUrl`, `posthogHost`) from console.info to
console.warn. The `FATAL-CONFIG:` Sentry-alert prefix is preserved
only on the true sentinels; the demoted fields now clear prod log-
aggregation thresholds without raising ops alerts.
- All three shells' runtime-config.ts: prefix log lines with the shell
name (e.g. `[shell-docs runtime-config]`) so the shared log stream
identifies which shell emitted the line.
- shell-docs runtime-config-serialize.ts: rewrite the U+2028 / U+2029
RegExp arguments using six-character ASCII backslash-u escape
sequences (was: literal codepoints in the string arg). The literal
codepoints are line terminators that a formatter or editor could
silently strip, breaking the security-critical XSS escape. The
ASCII form is robust to any such pass.
- shell-docs use-google-analytics.test.ts: de-tautologize the hook-
order test. It now asserts `usePathname(` and `useEffect(` both
exist in the source, so deleting all hooks would fail the test
rather than trivially satisfying the early-return path.
- shell-dashboard baseline-types.test.ts: update the partner-count
expectation from 25 to 26 -- the 26th entry (Cloudflare) is a
legitimate integration that landed independently; the test was
stale and had nothing to do with this branch.
- scripts/resolve-verify-matrix.ts: drop the `FIX 7 --` plan-
internal prefix from a comment; keep the explanation.
- shell-docs/.env.example: correct the `NEXT_PUBLIC_SHELL_URL`
fallback claim (sentinel, not canonical prod host) and document
the remaining 7 consumed env vars with their FATAL/warn/silent
semantics so the example matches runtime-config.ts.
Skipped:
- C-SENTINEL-DEDUP (`http://ops.invalid` shared constant across
shell-dashboard's next.config.ts and runtime-config.ts): both
files are at different module levels (root vs src/lib) and the
string appears once in each; extracting to a shared module would
widen the diff into a refactor for marginal benefit. Skipped per
the spec's "if it widens diff awkwardly, skip" guidance.
- C-SSRTEST: already exhaustively covered. Each of the three shells
has an SSR placeholder test that exercises every URL field via
`new URL()` parseability and (for shell-docs) the analytics-key
empty-string semantics. Treated as a no-op.
Validation: shell + shell-dashboard + shell-docs runtime-config /
serialize / GA tests green; bin/showcase Ruby suite green (87 runs);
showcase/scripts resolve-verify-matrix + aggregate-build-results +
lint-rule-no-public-env green (79 runs).
Six fixes addressing CR findings on the Option-B runtime URL-injection migration:
1. SSR_PLACEHOLDER must be parseable URL sentinels — `new URL("")` throws on
SSR causing 500s for any consumer that constructs URLs from runtime-config
fields. Use `.invalid`-TLD sentinels (RFC 2606) for URL fields; analytics
keys stay empty string. Add `suppressHydrationWarning` on consumers that
render the placeholder server-side and the real value post-hydration
(integration-grid, page-actions popover).
2. Hook-order: move `usePathname()`/`useEffect` ABOVE the early-return in
use-google-analytics. Gate the effect bodies on `GA_ID` instead so React
sees a stable hook order across renders.
3. `readUrl`/`readKey` accept either bare or `NEXT_PUBLIC_*`-prefixed env
names via a fallback chain — covers both server-only and inlined-public
variable conventions without forcing a rename across deploy targets.
4. Extract `serializeRuntimeConfig` to `lib/runtime-config-serialize.ts` so
the OWASP-escape behavior (XSS via </script>, U+2028/U+2029 line-terminator
injection) can be unit-tested without importing the layout into vitest.
5. Reclassify `intelligenceSignupUrl`/`posthogHost` from FATAL-CONFIG to
info-level in shell-docs — these are optional integrations, not hard
wiring failures, so absence should not poison the error stream.
6. Comment-rot cleanup: drop "Option B", B12, "the bug we are fixing", fix
"four substrings"→"three substrings" miscounts, and refresh shell-docs
.env.example to describe the runtime-injection contract instead of a
stale next.config throw claim.
V1: shell + shell-docs `next build` succeeds (no Edge-runtime crash on
`unstable_noStore`).
V2: `OPS_BASE_URL=` shell-dashboard `next build` no longer throws —
`next.config.ts` is now a phase-aware function that emits a sentinel
destination at build time and throws only at start (PHASE_PRODUCTION_BUILD
from next/constants).
Tests: shell-docs 72/72, shell 12/12, shell-dashboard runtime-config 16/16
(pre-existing baseline-partner-count failure unchanged).
getRuntimeConfig() in each shell's runtime-config.client.ts threw when
typeof window === 'undefined'. But Next.js App Router executes 'use
client' component bodies on the SERVER during initial SSR, so any client
component that called getRuntimeConfig() in its render body 500'd the
page. shell-dashboard already had the fix.
Mirror shell-dashboard's pattern: return a typed SSR_PLACEHOLDER (empty
strings for URL/key fields; {} for shell-dojo whose RuntimeConfig is
empty) when window is undefined. Keep the loud throw when window IS
present but window.__SHOWCASE_CONFIG__ is missing — that's a genuine
wiring bug and should not be masked.
Updated shell-docs and shell client tests: replace 'throws on server'
case with 'returns SSR sentinel placeholder' assertion matching each
shell's RuntimeConfig shape. shell-dojo has no client test so verified
via tsc only.
Two coupled changes that complete the Option B runtime-injection
switch for shell-docs:
- app/sitemap.ts + app/robots.ts: add `export const dynamic =
"force-dynamic"` so Next.js regenerates both routes per request.
Without this Next would statically prerender them at build time
and freeze whichever NEXT_PUBLIC_BASE_URL was set during
`next build` — the exact freeze that defeats the runtime-config
plumbing.
- next.config.ts: REMOVE the `next build` gates that threw when
NEXT_PUBLIC_BASE_URL or NEXT_PUBLIC_SHELL_URL were unset. Those
gates assumed build-time URL injection. Under Option B both URLs
are read per-request from process.env via getRuntimeConfig(), so
a single built artifact must be allowed to build with neither var
set — they're a deploy-time concern now. Missing-value surfacing
moves to `console.error` from runtime-config.ts at request time.
Migrate the remaining shell-docs consumers of NEXT_PUBLIC_* env vars
off of process.env reads and onto the runtime-config readers:
- lib/providers/posthog-provider.tsx (client): the PostHog key is
pulled inside PostHogProvider so the value reflects the current
deploy's NEXT_PUBLIC_POSTHOG_KEY; empty string disables analytics
via the existing truthiness gates around init/capture
- lib/providers/scarf-pixel.tsx (client): pixel id read at render
time; empty string returns null (existing no-op behavior preserved)
- lib/hooks/use-google-analytics.tsx (client): GA tracking id read
at hook invocation; empty string short-circuits via the existing
`if (!GA_ID) return` guard
- middleware.ts (Edge): POSTHOG_HOST is now resolved per-request via
getRuntimeConfigEdge() (the Edge wrapper skips unstable_noStore()
since next/cache is unavailable there and middleware always runs
per-request anyway). Server-side POSTHOG_KEY is unchanged — it's
not a NEXT_PUBLIC_* var.
Migrate every consumer of NEXT_PUBLIC_BASE_URL / NEXT_PUBLIC_SHELL_URL
/ NEXT_PUBLIC_INTELLIGENCE_SIGNUP_URL in the shell-docs tree off of
process.env reads and onto the runtime-config readers:
- lib/sitemap-helpers.ts (server): getBaseUrl() now delegates to
getRuntimeConfig().baseUrl
- components/search-modal.tsx (client): reads shellHost once per
render and threads it into normalizeHref() + the integration href
builder; normalizeHref is now parameterized rather than closing
over a module-scope SHELL_HOST
- components/integration-grid.tsx (client): reads shellHost after
the framework-scoped early return so we never touch the client
reader on no-op renders
- components/ai/page-actions.tsx (client): getClientBaseUrl()
delegates to getRuntimeConfig().baseUrl; the local inline copy is
retained so a "use client" file doesn't reach into the Node-only
sitemap-helpers module
- components/react/signup-link.tsx + ops-platform-cta.tsx (client):
buildHref() reads the signup URL lazily inside the function body
so the URL reflects the current deploy's env
Module-scope reads of process.env.NEXT_PUBLIC_* are eliminated from
all six files — every URL is now resolved at render time from the
runtime-config object the root layout injects.
Add inline <script> tag as the first child of <head> that populates
window.__SHOWCASE_CONFIG__ with values read from process.env at request
time via getRuntimeConfig(). This is the server side of the Option B
runtime-injection plumbing — every shell-docs client component will
read its URLs/analytics keys from this object instead of compiled-in
NEXT_PUBLIC_* values, so a single built artifact can serve staging
and prod by changing Railway env vars.
The serializer escapes < (XSS guard against </script> breakout) plus
U+2028 / U+2029 (legal in JSON, illegal in JS string literals when the
page is parsed as text/javascript) per OWASP guidance. The regex
sources are constructed via new RegExp(String.fromCharCode(...)) to
sidestep the TypeScript tokenizer treating the literal codepoints as
line terminators inside /regex/ literals.
Drop in NODE_ENV writes via Record<string,string> cast in the server
test — modern @types/node marks NODE_ENV read-only, and the runtime
reader only inspects the string value.
Adds the server (runtime-config.ts) and client
(runtime-config.client.ts) runtime config readers for shell-docs as
the foundation of Option B's per-request URL/key resolution. The
server module reads from process.env at request time (gated by
unstable_noStore so callers are not statically prerendered); a thin
getRuntimeConfigEdge wrapper skips the cache opt-out for middleware
(Edge runtime cannot import next/cache). The client module reads
window.__SHOWCASE_CONFIG__ which the root layout will inject in the
next commit.
Red-green: verified the test files fail without the modules
(module-not-found) and pass once the modules land — 10/10 green.
Part of plan-B (showcase per-env runtime URL injection).
Three coupled bugs surfaced from the BIA-as-default cutover, all hitting
the A2UI snippet rendered on /built-in-agent/generative-ui/a2ui:
1. Framework-scoped link rewriter (docs-page-view) blindly prefixed
every root-relative MDX href with the active framework slug, so
`/a2a/generative-ui/declarative-a2ui` rendered as
`/built-in-agent/a2a/generative-ui/declarative-a2ui` (404). Now skip
the rewrite when the first URL segment matches a known framework
slug (registry integrations + docs-only a2a/agent-spec/deepagents)
or a reserved top-level route (/docs, /ag-ui, /reference, /api).
2. Snippet `shared/generative-ui/a2ui.mdx` "Learn More" block linked at
legacy paths (`/generative-ui/specs`, `/ag-ui-protocol`,
`/generative-ui/specs/*`) that the IA retired. Updated to canonical
destinations (`/concepts/generative-ui-overview`,
`/agentic-protocols/ag-ui`, `/generative-ui/<spec>`) so the
framework-prefix rewriter produces valid framework-scoped URLs.
3. S13 redirect (concepts/* -> framework root) was generated for every
legacy framework slug including those whose canonical slug didn't
change (mastra, ag2, agno, ...). The legacy docs never had
/<canonical-slug>/concepts/* pages so the rule never had legitimate
work for them — but shell-docs serves agnostic /concepts/* under
every framework's scope now, and the unconditional rule was 301'ing
those valid URLs to the framework root. Restricted to renamed
frameworks only.
Verified locally: every link rendered on
/built-in-agent/generative-ui/a2ui
now resolves to 200; /<unchanged-slug-fw>/concepts/architecture still
serves; /langgraph/concepts/* still collapses to /langgraph-python.
Embed the live demo directly in the cookbook recipe, mirroring the in-doc
iframe approach used by integration landing pages (framework-overview.tsx
liveDemos[] / IframeSwitcher). The recipe now opens with a 'Try it live'
section above the prerequisites, iframing the showcase deployment so
readers can drive the runCode tool against a real Daytona sandbox without
leaving the page.
URL is a placeholder ('showcase-daytona-runcode-production.up.railway.app',
matching the showcase-{slug}-production.up.railway.app backend host
pattern from the registry generator). Will render Railway's not-found
page until a permanent demo deployment is provisioned at that name; the
inline MDX comment notes the placeholder.
OSS-222
Three coupled bugs surfaced from the BIA-as-default cutover, all hitting
the A2UI snippet rendered on /built-in-agent/generative-ui/a2ui:
1. Framework-scoped link rewriter (docs-page-view) blindly prefixed
every root-relative MDX href with the active framework slug, so
`/a2a/generative-ui/declarative-a2ui` rendered as
`/built-in-agent/a2a/generative-ui/declarative-a2ui` (404). Now skip
the rewrite when the first URL segment matches a known framework
slug (registry integrations + docs-only a2a/agent-spec/deepagents)
or a reserved top-level route (/docs, /ag-ui, /reference, /api).
2. Snippet `shared/generative-ui/a2ui.mdx` "Learn More" block linked at
legacy paths (`/generative-ui/specs`, `/ag-ui-protocol`,
`/generative-ui/specs/*`) that the IA retired. Updated to canonical
destinations (`/concepts/generative-ui-overview`,
`/agentic-protocols/ag-ui`, `/generative-ui/<spec>`) so the
framework-prefix rewriter produces valid framework-scoped URLs.
3. S13 redirect (concepts/* -> framework root) was generated for every
legacy framework slug including those whose canonical slug didn't
change (mastra, ag2, agno, ...). The legacy docs never had
/<canonical-slug>/concepts/* pages so the rule never had legitimate
work for them — but shell-docs serves agnostic /concepts/* under
every framework's scope now, and the unconditional rule was 301'ing
those valid URLs to the framework root. Restricted to renamed
frameworks only.
Verified locally: every link rendered on /built-in-agent/generative-ui/a2ui
now resolves to 200; /<unchanged-slug-fw>/concepts/architecture still
serves; /langgraph/concepts/* still collapses to /langgraph-python.
shell-docs renders MDX server-side via MDXRemote, not Fumadocs's compile-
time MDX pipeline, so inline 'import { Boxes } from "lucide-react"' in an
.mdx body doesn't resolve at render time — Boxes ends up undefined and the
page errors with 'Expected component Boxes to be defined' on /cookbook.
Drop the import and the icon prop from the landing's <Card>. Existing
shell-docs pages (e.g. tutorials/ai-powered-textarea/*) use <Card> with no
icon, so this matches the local idiom. The Daytona recipe page is
unaffected because its icon is in frontmatter (icon: 'lucide/Boxes' — a
string, resolved by the page layout, not by MDX body).
OSS-222
Addresses PR review feedback (@tylerslaton): the docs/ folder is deprecated
in favor of showcase/shell-docs and will be removed soon. Move the entire
Cookbook contribution to shell-docs:
- showcase/shell-docs/src/content/docs/cookbook/ — landing + Daytona recipe
copied verbatim (Fumadocs frontmatter/components/dividers all match).
- showcase/shell-docs/src/content/docs/meta.json — new top-level
'---Cookbook---' divider with a '...cookbook' spread, slotted between
Platforms and Other.
- showcase/shell-docs/src/components/brand-nav.tsx — Cookbook tab added to
LEFT_LINKS (lucide ChefHat icon) and active-route detection extended so
/cookbook/* highlights it (peer to the existing Reference handling).
Reverts all earlier edits to docs/ from this PR (navbar, root meta, learn
meta, and the docs/content/docs/cookbook/ tree) so the deprecated folder is
left at zero-diff vs main.
OSS-222
Fumadocs's Tab component applies escapeValue() internally to the value
prop. Our DocsTab wrapper was also calling escapeValue() before passing
to FumadocsTab, causing multi-word tab values to be escaped twice.
For "JSON Configuration File" (3 words, 2 spaces):
1st escape (our wrapper): "json-configuration file" (1 space left)
2nd escape (Fumadocs Tab): "json-configuration-file" (fully hyphenated)
The trigger value uses only ONE escapeValue call:
escapeValue("JSON Configuration File") = "json-configuration file"
Trigger "json-configuration file" != content "json-configuration-file"
so Radix sets data-state="inactive" on the content panel, which is
then hidden by data-[state=inactive]:hidden.
Fix: remove escapeValue() from our Tab wrapper. The Tabs defaultValue
still needs pre-escaping because FumadocsTabs accepts it as-is (no
internal escape); only the individual Tab has internal escaping.
Single-word and two-word tabs (HTTP, Application Settings) were
unaffected because one escapeValue pass already produces a hyphen-only
string that is idempotent under a second pass. Same bug also affected
"stdio Transport (Local)" in the Windsurf section.
Three classes of legacy URLs were 404'ing because shell-docs (with BIA
as the soft-default framework) doesn't serve them at the root surface
the old docs did:
1. BIA-canonical pages — /server-tools, /mcp-servers, /model-selection,
/advanced-configuration, /agent-app-context live only under
/built-in-agent/ now. Internal sidebar clicks already framework-scope
via SidebarLink; external traffic (marketing, blog posts, bookmarks)
was 404'ing.
2. Moved root pages — /mcp-apps (moved to /generative-ui/),
/copilot-runtime, /custom-agent (moved to /backend/), /deep-agents
(renamed to /deepagents), /multi-agent-flows (LangGraph-only),
/custom-look-and-feel folder index, /generative-ui/specs/* (specs
subgroup retired), plus assorted misc (/what-is-copilotkit,
/getting-started/quickstart-chatbot, /telemetry, /migration-guides/*,
/reference/hooks/useCoAgent).
3. Legacy /integrations/<fw>/* prefix — R15/R17 already handled the
built-in-agent variant; this extends the same pattern to every other
framework, mirroring the existing /docs/integrations/* coverage.
All redirect destinations verified to return 200 against a local dev
build; existing tests still pass.
The @ag-ui/aws-strands TypeScript adapter ships alongside the Python
ag_ui_strands package but the docs only showed Python snippets. Add a
TypeScript tab next to every Python snippet (Python default, `persist`
on the tab group so a reader's choice sticks across pages) covering:
- quickstart: project init, install, agent file, run command
- frontend-tools: @tool stub + createStrandsApp server
- shared-state (read + write): StrandsAgentConfig.stateContextBuilder
- generative-ui tool-rendering: backend tool definition
- generative-ui state-rendering: ToolBehavior.stateFromArgs
Also applied to the parallel showcase/shell-docs tree. Non-code pages
(deploy-agentcore, copilot-runtime, inspector, etc.) remain untouched
since they either re-export shared snippets or have no framework code.