Two pre-existing type errors from commit 9b05ed41 surfaced on main's
Docker build:
1. `unscoped-docs-page.tsx` imports `findFrameworksWithCell` from
`@/lib/docs-render`, but the helper was only declared locally in the
two page.tsx routes with a 1-arg signature (and referenced an
undeclared `demos` in one case — dead code). Export a 3-arg shared
version from docs-render.tsx that accepts the integration slug list
and demo map as parameters (keeps the lib free of registry imports),
drop the dead local in `[[...slug]]/page.tsx`, and rewire the live
caller in `[framework]/[[...slug]]/page.tsx` through the shared
export.
2. The Step-2 section cards on the overview call `<SidebarLink>`
without the required `scope` prop. The prop was already ignored
internally (destructured as `_scope`) so relaxing it to optional is
the minimal fix and keeps the call-site intent documented.
`npm run build` in showcase/shell-docs now compiles cleanly.
Drop error-boundary-card in favour of Next.js error.tsx at each route,
refresh docs components (brand-nav, docs-callout, docs-page-view,
docs-steps, docs-tabs, framework-provider/selector/tabs,
property-reference, router-pivot, sidebar-link, snippet), update
docs-render + mdx-registry + reference-items for the new QA shape.
The scrub and #4084 touched the same surface: #4084 re-added an `open:`
generative_ui profile listing `open-gen-ui`/`open-gen-ui-advanced`, and
re-added both features to `constrained-explicit.allowed`. Extending the
branch's scrub to both re-additions keeps the semantic consistent with
the schema (which already dropped `open` from the approaches enum).
- `showcase/shared/constraints.yaml`: drop `open-gen-ui` +
`open-gen-ui-advanced` from `constrained-explicit.allowed`; drop main's
re-added `open:` profile entirely.
- `showcase/packages/langgraph-python/manifest.yaml`: drop the now-orphan
`open-gen-ui` + `open-gen-ui-advanced` feature and demo entries
(validator confirmed they had no allowed approach left).
- Regenerated `showcase/shell/src/data/registry.json` + sibling
`shell-docs`/`shell-dojo` registries and `constraints.json` via
`pnpm --dir showcase/scripts generate-registry`. All 17 integrations
validate.
`feature-registry.json` intentionally still defines both features — the
original scrub commits (2b996c54d, 27f886e59) left it untouched, so the
demo source files on disk also stay. Follow-up deletion if desired is
out of scope for this merge.
There is no app/integrations/ route, so the reservation was causing
/integrations/... URLs to 404. Without it, these paths fall through to
UnscopedDocsPage via the non-integration fallthrough in [framework].
Strip snippet_framework: langgraph-python from frontmatter and
integration="langgraph-python" from all InlineDemo tags in the
main docs tree (27 files). On framework-scoped pages frameworkOverride
drives Snippet and InlineDemo; on unscoped pages FrameworkGuardedContent
already hides the body until a framework is selected, so no default
is needed.
buildNavTree now checks meta.root before including a subdirectory,
so unselected/ (and any other root:true directory) is excluded from
the main nav tree. This eliminates the flicker where SidebarLink
generated /langgraph-python/unselected/coding-agents and the
[framework] route had to server-redirect to the correct scoped URL.
On framework-scoped pages, MDX links like /quickstart rendered as plain
<a href="/quickstart"> causing RouterPivot redirect flicker. Override the
MDX `a` component when frameworkOverride is set to prepend the framework
prefix to root-relative internal links.
Next.js routes /quickstart to [framework]/[[...slug]] (dynamic segment
beats optional catch-all), where "quickstart" is not a registered
integration, causing notFound(). Fix by:
- Extracting the unscoped doc rendering logic into UnscopedDocsPage
- Falling through to it in [framework] when the slug is not a framework
- Simplifying [[...slug]]/page.tsx to handle only the root overview
SidebarLink now uses storedFramework as fallback so links on the
overview page go directly to /<framework>/<slug> without a RouterPivot
redirect. OverviewNavItem and section cards now use SidebarLink for the
same reason. FrameworkSelector "Clear selection" was navigating to
/docs/<slug> (broken); fixed to /<slug>. hrefFor now preserves the
current slug when switching frameworks from an unscoped page.
SidebarLink fallback, framework-scoped backLink, "framework-agnostic
version" banner link, FrameworkLandingPage sidebar, and brand-nav
all still pointed at /docs/* which routes to 404 via the [framework]
catch-all. Switch all to / or /<slug>.
Override InlineDemo in DocsPageView's MDX component map to substitute
defaultFramework for the hardcoded integration prop when a framework
is selected. MDX files don't need to change — the override happens at
the render layer, matching how Snippet already handles this.
DOCS_SECTIONS, OverviewNavItem, CopilotKit Docs link, backLink, and
slugHrefPrefix all generated /docs/<slug> paths that 404 — the
[framework] catch-all intercepted "docs" as a framework slug and
returned notFound(). Strip the /docs prefix so all root-route links
resolve correctly.
Page-by-page audit of showcase/shell-docs MDX content. All changes are
in src/content/docs/.
## Broken snippet region fixed
- generative-ui/a2ui/dynamic-schema.mdx: Step 5 referenced
`runtime-inject-tool` which does not exist in the declarative-gen-ui
cell. Replaced with a hardcoded code block showing `injectA2UITool: true`
(same content already shown on the parent a2ui.mdx page).
## MDX syntax fix
- frontend-actions.mdx: stray closing ``` at end of file (would cause
a parse/render failure).
## Internal link fixes — /docs/ prefix removed (×14 files)
The docs app routing does not use a /docs/ prefix — pages live at
/<slug>. Links using /docs/<slug> hit the reserved-slug guard in the
[framework] route and 404. Fixed across:
generative-ui/index.mdx (7 links)
generative-ui/tool-based.mdx (3 links)
learn/index.mdx (6 links)
multi-agent/subagents.mdx (2 links)
shared-state.mdx (2 links)
shared-state/streaming.mdx (2 links)
shared-state/agent-readonly.mdx (4 links)
troubleshooting/debug-mode.mdx
troubleshooting/error-debugging.mdx
troubleshooting/migrate-to-1.10.X.mdx
troubleshooting/migrate-to-1.8.2.mdx (2 links)
troubleshooting/observability-connectors.mdx
## Internal link fixes — /unselected/ removed (×2 files)
backend/copilot-runtime.mdx
backend/custom-agent.mdx
## Summary
- **shell-dashboard:** dashboard.showcase.copilotkit.ai rendered every
"demo" and "code" link as `http://localhost:3000/...`. Root cause:
`NEXT_PUBLIC_SHELL_URL` was never provided at build time and the source
fell back to `localhost:3000`. Next.js inlines `NEXT_PUBLIC_*` at `next
build`, so a runtime Railway env var could not rescue a bad build. Fix
plumbs the value through as a Docker build arg from
`showcase_deploy.yml` and fails loudly at build if it's unset so this
can't regress silently.
- **shell-dojo:** dojo.showcase.copilotkit.ai was missing items in the
langgraph column (langgraph-python showed 9 demos vs 20+ in the
manifests). Root cause: `shell-dojo/src/data/registry.json` was stale —
the generator only wrote to `shell/`, the dojo Dockerfile never ran the
generator at build, and the CI path filter didn't rebuild the dojo when
manifests changed. Fix dual-emits from `generate-registry.ts` to
`shell/`, `shell-dojo/`, and `shell-docs/`, runs the generator in the
dojo Dockerfile, expands the workflow's path filter to include
`packages/**` and `shared/**`, and refreshes the committed JSON so it
matches what the generator produces today. Langgraph-python demo count 9
→ 32.
## Test plan
- [x] `shell-dashboard` Docker build succeeds with
`NEXT_PUBLIC_SHELL_URL` build arg (Depot `36wlvzkgp1`).
- [x] `shell-dashboard` Docker build fails loudly when the build arg is
omitted (Depot `mbh41c3qtk`).
- [x] `shell-dojo` Docker build succeeds; generator+bundler run at
build; 159 demos bundled (Depot `h7bbq8f8jt`).
- [x] `showcase_deploy.yml` passes YAML validation.
- [ ] After merge + deploy: verify `dashboard.showcase.copilotkit.ai`
links point to `https://showcase.copilotkit.ai/...`.
- [ ] After merge + deploy: verify `dojo.showcase.copilotkit.ai` shows
the full langgraph column (langgraph-python ≥ 20 items).
Drops the inline TypeScript typecast from the Mastra agent-app-context
example and uses optional chaining + direct access instead, so the doc
snippet is easier to read and copy. Keeps optional chaining on `.find`
so the example stays safe when the AG-UI context is absent. Also fixes
the `[!code highlight:N]` count after the comment line was removed.
Ports @Abubakar-01's changes from #4125 so they can ship together with
the `requestContext` rename, targeting the new `showcase/shell-docs/`
path after the shell restructure on main.
Co-authored-by: Muhammad Abubakar <abubakaran102025@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The dojo app was missing items under the langgraph column because
shell-dojo shipped a stale committed registry.json. The generator
only wrote to shell/, the dojo Dockerfile didn't run the generator
at build, and the CI path filter didn't rebuild the dojo when
manifest files changed.
Fix: emit from generate-registry.ts to shell, shell-dojo, and
shell-docs; add the generator step to shell-dojo's Dockerfile;
expand the deploy workflow's path filter to include packages/**
and shared/**; and refresh the committed registry/demo-content
JSON so files on disk match what the generator produces today.
The upstream docs sync added 24 new integration pages (`threads.mdx`,
`premium/self-hosting.mdx`) plus learn/reference content, but didn't
land the runtime wiring they depend on — so the pages would ship blank.
It also accepted upstream changes in tool-rendering.mdx over local
modifications that needed to be restored.
- Register `Threads` / `SelfHosting` in SNIPPET_MAP so the shared
`<Threads />` and `<SelfHosting />` tags inline on integration pages
- Register `ThreadsEarlyAccess` wrapper + `MessageSquareMore` /
`Network` / `Newspaper` icons in mdx-registry for the new threads
content and the rebuilt `learn/index` card grid
- Revert tool-rendering.mdx upstream-wins regressions: framework-neutral
`/unselected/server-tools` link and `components={props.components}`
forwarding for the `<SharedContent>` override pathway
- Add `threads` + `...premium` to every integration's root meta.json
plus 11 new `premium/meta.json` files so the new pages appear in the
sidebar; update `docs/meta.json`, `docs/learn/meta.json`, and
`docs/premium/meta.json` the same way
- Fix `convertMarkdownTableToHtml` to emit JSX-valid tags. It was
generating `style="..."` string attributes, which next-mdx-remote
rejects; this path was dormant until the new threads page put a
markdown table inside a `<Step>`
Directory-plus-index.mdx layout produced two reference entries for
the same logical page: one at `foo/` (the index) and one at `foo`
(a flat sibling if it existed). Collapse index.mdx into the parent
slug so the generated list has a single canonical entry, and bail
out with a clear error when a flat-file collision would shadow the
index. Prevents silently dropping one of the two pages at build
time.
The log effect depended on the error object identity, which changes
on every render even when the underlying error is the same — so the
effect fired repeatedly and spammed the log. Depend on
error.message and error.digest (primitive, stable across renders
of the same error) instead. React's exhaustive-deps check is still
satisfied because those are the fields the effect actually reads.
A cluster of UX correctness fixes across the page handlers and
the shared brand nav:
- Filter out undeployed frameworks before rendering so the route
doesn't produce blank pages that users can reach via stale links.
- Strip the leading body H1 with a CRLF-safe regex that only matches
when the body H1 equals the frontmatter title — mirrors ag-ui
route behavior so two stacked titles never render.
- Reference routes now titleCase the slug and resolve via the
index.mdx fallback, matching the directory-plus-index layout the
docs source uses.
- ag-ui title resolver gains a fallback so deep slugs without a
matching registry entry still produce a reasonable title instead
of crashing.
- brand-nav builds the mobile link href dynamically so it points at
the current framework rather than a hard-coded placeholder.
The selector dropdown previously rendered every framework entry as
a clickable option, including ones that had been marked as not
deployed. Selecting an undeployed entry routed to a blank page.
Disable the button for those entries so the dropdown matches the
surrounding tab behavior, which already hides them.
localStorage can hold a value from a previous deploy that no longer
exists in the current frameworks list (package renames, undeploy,
etc.), which pins the provider to a bogus framework identity until
the user manually picks another. Validate the stored value against
the current list both at mount and on cross-tab `storage` events,
and fall back to the default when it doesn't match.
Framework-tabs assumed the items array always matched the frameworks
prop one-to-one. A count mismatch wedged the active index at a stale
value that could fall outside the new frameworks array, rendering a
blank tab. Add a length-mismatch guard that re-syncs state when the
frameworks prop changes between renders, and remove props that were
threaded through but never read so the surface matches what the
component actually uses.
When the tab item array contains duplicate values or has a length
that doesn't match the items prop, looking up the active index by
value conflates tabs. Find the active tab by position instead so
each rendered tab has a stable identity regardless of duplicated
or drifted values.
The img and video overrides spread props, then replaced style wholesale
with the layout defaults — silently discarding any author-provided
style keys from MDX. Merge author style first and layout defaults
last so we preserve author intent (e.g. custom max-width, filters)
while still enforcing our rounded-corner + marginBottom guards.
Per MDN, combining `allow-scripts` with `allow-same-origin` lets the
framed page remove its own sandbox attribute at runtime — that is a
sandbox escape regardless of how much we trust the origin. Drop
`allow-same-origin` from all three iframe sandboxes (InlineDemo,
author-supplied IFrame, YouTube embed). None of them need same-origin
semantics with the parent docs host to function.
Also validate InlineDemo's computed demo URL at the sink instead of
trusting the registry blindly — a malformed backend_url should render
a visible error placeholder, not a silently broken iframe.
MDX-authored hrefs flow into <a> and <Link> without scheme filtering.
`rel="noopener noreferrer"` does not neutralize script-URL schemes
like `javascript:`, `data:text/html`, or `vbscript:`, so a malicious
snippet could land in the rendered page as an XSS vector.
Add sanitizeHref() with an allowlist of http/https/mailto/tel plus
relative / protocol-relative / fragment / query forms. Anything else
returns null and the A/Link overrides render a <span> with the same
visible content instead of an anchor. Also add a protocol-relative
(`//`) early-return in isExternalHref so those URLs correctly route
through a plain <a> rather than next/link.
The title resolution path had two correctness bugs:
- readTitle and loadDoc both ran the H1 fallback regex against the
raw file, so a YAML comment like `# ...` inside frontmatter could
be picked up as the page's H1. Run the regex against the parsed
body (frontmatter stripped) instead. Also log a console.error when
frontmatter parsing blows up so a malformed file doesn't silently
yield a garbage title.
- docs-page-view unconditionally stripped the leading body H1, which
dropped distinct H1 headings whenever the MDX body didn't match
the frontmatter title. Mirror the ag-ui route: only strip when the
body H1 equals the FM title after whitespace normalization, and
use `\r?\n` so CRLF-authored MDX parses correctly too.
MDX authors pass component-map overrides via doubled-brace object
syntax, e.g. `<SharedContent components={{ Foo: Bar }} />`. The
previous regex used `[^}]*` which truncates at the inner `}`, so
the tag never matched and the snippet was never inlined. Match the
doubled-brace object form explicitly with `\{\{...\}\}` and
`[\s\S]*?` so multi-line prop objects still match.
JS regex alternation is leftmost-first, not leftmost-longest, so for
source like `<Tabs>` the pattern `Tab|Tabs` matches "Tab", `[^>]*`
consumes the "s", and the close-side alternation would then pair
with any container's `</...>` regardless of which tag opened. Two
separate bugs fell out of this:
- Prefix collisions (Tab vs Tabs) silently skipped table conversion.
Sort JSX_CONTAINER_TAGS longest-first so the opener always matches
the actual outer tag name.
- Mismatched pairs like `<Tabs><Tab>x</Tab></Tabs>` paired the outer
`<Tabs>` with the inner `</Tab>`, stranding `</Tabs>`. Use a
numbered backref (`\\2`) on the closing tag so only the same tag
name can close the match.
Also tighten the inline comment on the nested-same-tag bailout to
reflect that the non-greedy match now closes on the same tag, not
"any container in the set".
Extract the near-identical card markup from both route-level
error.tsx files into src/components/error-boundary-card.tsx. The
route-level handlers remain required (Next.js discovers them per
route segment) but are now one-line wrappers that forward props
with a scope label ("docs" / "ag-ui") so console.error entries
stay distinguishable per route segment (finding #14).
- Replace the className="capitalize" breadcrumb span (which only
cased the first character, rendering my-component as
My-component) with an explicit titleCase helper that splits on
hyphens and capitalizes each segment (finding #12).
- Derive the sidebar category list from loadAllReferenceItems
instead of hardcoding ["Components", "Hooks"]. A new
REFERENCE_SUBDIRS entry in reference-items.ts now shows up
automatically without a second edit here (finding #13).
- titleFromSlug now Title-Cases the result (multimodal-inputs →
Multimodal Inputs) instead of returning a lowercased fallback.
The lowercase labels previously clashed with the docs-render
and reference-breadcrumb conventions elsewhere (finding #10).
- Wrap getTitleForSlug in a process-scoped cache with dev-mode
invalidation, mirroring reference-items.ts. Previously every
entry in NAV_DEFINITION re-opened and re-parsed its MDX file on
every request — multiply across ~20 nav entries per render and
it added noticeable fs churn (finding #11).
- Reject RESERVED_ROUTE_SLUGS at the handler as defense-in-depth.
Next.js already prefers exact-match top-level routes over this
catch-all, so /docs, /ag-ui, etc. never reach here under normal
routing — but if the registry ever ships an integration whose slug
collides with a reserved segment, the handler now short-circuits
with notFound() rather than rendering garbage (finding #6).
- Import the shared findFrameworksWithCell helper instead of a
duplicated local copy (finding #3 follow-through).
- Filter out unresolved alternative-framework slugs BEFORE mapping
to React fragments so the ", " separator aligns with the final
rendered count. The pre-filter index previously emitted stray
commas when an entry dropped to null mid-sequence (finding #7).
- FrameworkLandingPage's four hardcoded LandingCards are now gated by
registry integration.features presence: strands (no HITL support)
no longer shows a dead "Human-in-the-Loop" card. The card
definitions are hoisted into a module-level constant with a TODO
pointing at the eventual feature-id → doc-slug mapping
(finding #8).
- Declare export const dynamicParams = true explicitly so a future
migration to output: "export" fails loudly here (under static
export dynamicParams must be false) rather than silently 404ing
every /<framework>/<slug> URL (finding #9).
- Extract findFrameworksWithCell into @/lib/docs-render so the docs
catch-all and framework-scoped catch-all share one implementation
instead of carrying a near-identical local copy in each (finding #3).
- Validate /docs/integrations/<slug> against the registry with
notFound() when the slug is unknown. Previously a crafted URL like
/docs/integrations/fake-framework silently fell through to an empty
nav tree, indistinguishable from a valid integration with no scoped
content (finding #4).
- Sort integrations by sort_order then slug before picking the
animated preview URL. Registry iteration order alone isn't
deterministic w.r.t. the visual priority the docs UI shows
everywhere else, so the preview now matches (finding #5).
Two additions in snippet.tsx:
1. parseLineRange now accepts comma-separated segments like
`lines="1-5,10-15"`. Each segment is validated and sliced
independently; discontinuous sections are stitched with a visible
`// ...` gap marker so readers see the jump. Invalid segments fail
the whole prop (clear error beats partial rendering). Single-range
and open-ended ("A-") forms continue to work unchanged.
2. resolveHljsLanguage now has explicit entries for tsx (→ typescript),
jsx (→ javascript), and sh / bash / shell (→ bash). These are the
common bundler-emitted hints that previously returned null and
fell through to highlightAuto — producing noisy one-shot warnings
and non-deterministic highlighting.