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).
Implements plan-B B11. URL and analytics NEXT_PUBLIC_* values now reach
each shell at runtime via Option B (env-driven runtime-config), so the
GHA showcase_build.yml workflow no longer threads them through as Docker
build-args and the shell-dashboard/shell-docs Dockerfiles no longer
declare the matching ARG/ENV pairs.
- showcase_build.yml: shell-dashboard and shell-docs matrix entries lose
build_args_pb_url / build_args_shell_url / build_args_ops_url /
build_args_base_url / build_args_analytics; the 'Prepare build args'
step drops the corresponding env: keys and if-branches plus the five
analytics NEXT_PUBLIC_* secrets. COMMIT_SHA and BRANCH stay — they
identify the artifact.
- showcase/shell-dashboard/Dockerfile: remove ARG/ENV for
NEXT_PUBLIC_SHELL_URL, NEXT_PUBLIC_POCKETBASE_URL, OPS_BASE_URL plus
the explanatory comments. Update the runner-stage comment to point at
runtime-config.ts as the new source of truth.
- showcase/shell-docs/Dockerfile: remove ARG/ENV for
NEXT_PUBLIC_BASE_URL, NEXT_PUBLIC_SHELL_URL, NEXT_PUBLIC_POSTHOG_KEY,
NEXT_PUBLIC_REB2B_KEY, NEXT_PUBLIC_SCARF_PIXEL_ID, NEXT_PUBLIC_REO_KEY,
NEXT_PUBLIC_GOOGLE_ANALYTICS_TRACKING_ID. COMMIT_SHA / BRANCH retained.
shell/Dockerfile and shell-dojo/Dockerfile already only declare commit-sha
and branch ARGs — no changes needed there (per plan-B B11.4).
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.
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.
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.
## Summary
- Bundle Inter Medium/Bold locally for shell-docs OG image rendering and
pass them to ImageResponse.
- Localize the OG background and CopilotKit logo as data URIs so the
route avoids render-time remote image fetches.
- Add route tests for valid image construction, unknown slug 404
propagation, render failure 500 behavior, and framework-scoped slug
resolution.
## Verification
- pnpm test in showcase/shell-docs (36 passed)
- pnpm lint in showcase/shell-docs (exits 0; existing warnings only)
- Local dev-server curl: /og/quickstart/og.png returned 200 image/png,
valid 1200 x 630 PNG
- Local dev-server curl: /og/does-not-exist/og.png returned 404
- Commit hook ran test-and-check-packages successfully
## Notes
- showcase/shell-docs is not present in the Nx project graph, so there
was no direct shell-docs Nx target to run.
- pnpm typecheck / pnpm build for standalone shell-docs currently fail
on pre-existing src/lib/rehype-code-meta.ts missing shiki types.
## Summary
- Unify authored and generated shell-docs navigation so the sidebar
keeps the same structure across framework modes.
- Restore setup-content bundling from integration-owned docs and wire
shell-docs to consume the generated bundle at runtime.
- Audit and fix the LangGraph TypeScript and Google ADK code regions so
the generated snippets are more useful and accurate.
- Tighten docs/build routing and workflow triggers so shell-docs
rebuilds when the relevant integration docs inputs change.
## Testing
- Shell-docs unit tests passed.
- Shell-docs typecheck passed.
- Shell-docs lint passed with existing repository warnings only.
- Setup-content bundle generation passed.
- Python integration files compiled successfully.
- Workflow YAML parsed successfully.
## Summary
- Restore the missing NewLookAndFeelPreview component for shell-docs
troubleshooting migration pages.
- Wire the MDX registry to render the real preview instead of an empty
shim.
## Verification
- npm --prefix showcase/shell-docs run typecheck
- npm --prefix showcase/shell-docs run lint (warnings only,
pre-existing)
- Browser verified
http://localhost:3003/built-in-agent/troubleshooting/migrate-to-1.8.2:
preview launcher renders and opens populated panel
- git commit pre-commit hooks passed: check-binaries, lint-fix,
test-and-check-packages
## Notes
- @copilotkit/showcase-scripts:verify-shell-docs:fast runs but fails on
existing broad shell-docs dead-link/import/content backlog unrelated to
PDX-203.
- Production next build hung locally after content generation with no
diagnostics; verified the affected route via dev server instead.
## Summary
- Disabled Fumadocs search in `showcase/shell-docs` so Cmd/Ctrl+K no
longer opens the built-in dialog.
- Centralized the custom search modal behind a single app-level
provider/event bridge so desktop and mobile triggers share one instance.
- Kept the custom search button and hotkey behavior intact, including
Escape to close.
## Testing
- `npm run typecheck` in `showcase/shell-docs` passed.
- `npm run lint` in `showcase/shell-docs` passed with pre-existing
warnings only.
- Verified locally in the in-app browser that Cmd+K opens one custom
search modal, Escape closes it, and no Fumadocs search dialog appears.
Root page was duplicating the snippet content inline. Switch it to
<BuildWithAgents /> so snippets/shared/guides/build-with-agents.mdx
is the one source — root page and built-in-agent both render from it.
built-in-agent uses docs_mode: 'authored' so it loads the per-framework
integrations/built-in-agent/build-with-agents.mdx directly, bypassing
the root page entirely. Other frameworks (generated mode) fall through
to the root page and already show the skills section.
Fix: extract the full page body into a shared snippet
snippets/shared/guides/build-with-agents.mdx
register it as <BuildWithAgents /> in SNIPPET_MAP + mdx-registry, and
switch built-in-agent's page to <BuildWithAgents />. Root page keeps
its inline content (for crawlability). The snippet is the source used
by authored-mode frameworks; root is the source for generated-mode and
crawlers — same pattern as MCPSetup / coding-agents.
The table was incomplete (6 skills listed, 12 actually install) and has
no CI check to keep it in sync. Replace with a one-line prose summary
and a link to the canonical skills/ directory on GitHub, which is always
accurate.
- Rewrites the root build-with-agents page to document Skills + MCP Docs Server
- Skills section: intro, npx skills add command, skills table, starter prompt
- Removes hideTOC so the new TOC (Skills / MCP Docs Server) renders
- Updates icon to BrainCircuit and description to keyword-rich copy
- Renames mcp-server-setup snippet heading: ## Overview → ## MCP Docs Server
so both sections compose cleanly on the root page without a double Overview
Closes out the last AC on OSS-133: npx skills add flow documented with
table of skills, starter prompt, and page structure matching Mastra reference.
## Summary
Ports the changes from PR #4927 to the live shell-docs app. PR #4927
renamed \`coding-agents\` → \`build-with-agents\` in the legacy
\`docs/\` tree, but \`showcase/shell-docs\` has its own independent
content directory that was never updated.
## Changes
- **11 file renames:** \`coding-agents.mdx\` → \`build-with-agents.mdx\`
(root + 10 integrations: ag2, agno, aws-strands, built-in-agent,
crewai-flows, langgraph, llamaindex, mastra, microsoft-agent-framework,
pydantic-ai)
- **Frontmatter titles:** \`"Coding Agents"\` → \`"Build with agents"\`
in all 11 files
- **Root \`build-with-agents.mdx\`:** replaced 291 lines of hardcoded
content with \`<MCPSetup />\` shared snippet (content had drifted from
the canonical snippet)
- **9 integration \`meta.json\` files:** \`"coding-agents"\` →
\`"build-with-agents"\` in sidebar nav
- **Root \`meta.json\`:** rename entry + add React Native under new
\`---Platforms---\` section (mirrors PR #4927's second commit)
- **\`seo-redirects.ts\`:**
- Update S4 (\`vibe-coding-mcp\`) and S15 (\`mcp\`) subpath destinations
to \`build-with-agents\`
- Add S16: \`coding-agents\` → \`build-with-agents\` subpath rename for
all 13 legacy framework slugs
- Add \`CODING_AGENTS_RENAMES\`: root \`/coding-agents\` + all canonical
framework \`/*/coding-agents\` → \`/*/build-with-agents\` (exact 301s)
- Fix stale destinations in F20, R12, R18, R19
- **\`docs-render.tsx\`:** update \`SUBPATH_TO_COMPONENT\` key
\`"coding-agents"\` → \`"build-with-agents"\` so pages at the new slug
still resolve the shared MCP snippet
## Test plan
- [x] \`/coding-agents\` 301-redirects to \`/build-with-agents\`
- [x] \`/langgraph-python/coding-agents\` 301-redirects to
\`/langgraph-python/build-with-agents\`
- [x] \`/mcp\` and \`/vibe-coding-mcp\` redirect to
\`/build-with-agents\`
- [x] Sidebar shows "Build with agents" entry in each integration
- [x] React Native appears under Platforms section in root sidebar
- [x] All 11 \`build-with-agents\` pages render content correctly (MCP
setup snippet loads)
- [x] Root \`/build-with-agents\` shows correct MCP setup content