Skip to content

Commit 773631c

Browse files
committed
fix(shell-docs): expand snippet registry, make inlineSnippets fence-aware
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.
1 parent 648aeb7 commit 773631c

1 file changed

Lines changed: 104 additions & 17 deletions

File tree

showcase/shell-docs/src/lib/docs-render.tsx

Lines changed: 104 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -798,22 +798,33 @@ export const SNIPPET_MAP: Record<string, string> = {
798798
SelfHosting: "shared/premium/self-hosting.mdx",
799799
Slots: "shared/basics/slots.mdx",
800800
Threads: "shared/threads/threads.mdx",
801+
ToolRenderer: "shared/generative-ui/tool-rendering.mdx", // alias of ToolRendering
801802
ToolRendering: "shared/generative-ui/tool-rendering.mdx",
802803
DefaultToolRendering: "shared/guides/default-tool-rendering.mdx",
804+
// Versionless aliases retained for backward compat with older MDX that
805+
// emits `<MigrateTo />` / `<MigrateToV />`; both resolve to v2.
806+
MigrateTo: "shared/troubleshooting/migrate-to-v2.mdx",
807+
MigrateToV: "shared/troubleshooting/migrate-to-v2.mdx",
808+
CopilotUI: "copilot-ui.mdx",
809+
LandingCodeShowcase: "landing-code-showcase.mdx",
803810
UseAgentSnippet: "use-agent.mdx",
811+
InstallSDKSnippet: "install-sdk.mdx",
812+
InstallPythonSDK: "install-python-sdk.mdx",
813+
RunAndConnect: "coagents/run-and-connect-agent.mdx",
814+
RunAndConnectSnippet: "coagents/run-and-connect-agent.mdx", // alias of RunAndConnect
815+
CopilotCloudConfigureCopilotKitProvider:
816+
"copilot-cloud-configure-copilotkit-provider.mdx",
817+
// Historical spelling (no `Provider` suffix) still appears in tutorials.
818+
CopilotCloudConfigureCopilotKit:
819+
"copilot-cloud-configure-copilotkit-provider.mdx",
820+
SelfHostingCopilotRuntimeCreateEndpoint:
821+
"self-hosting-copilot-runtime-create-endpoint.mdx",
822+
SelfHostingCopilotRuntimeConfigureCopilotKitProvider:
823+
"self-hosting-copilot-runtime-configure-copilotkit-provider.mdx",
824+
SelfHostingCopilotRuntimeConfigureCopilotKit:
825+
"self-hosting-copilot-runtime-configure-copilotkit-provider.mdx",
804826
};
805827

806-
// JSX components that legitimately appear as `<Component />` inside MDX
807-
// code fences (rendered example code) but are NOT snippet imports. The
808-
// `inlineSnippets()` regex isn't code-fence-aware, so without this set
809-
// these false positives spam the dev console and look like missing
810-
// snippet errors. CopilotChat is the runtime React component used in
811-
// example snippets across slots.mdx, threads.mdx, use-agent.mdx, etc.
812-
//
813-
// If a real snippet ever needs one of these names, register it in
814-
// SNIPPET_MAP above; the lookup short-circuits before this check.
815-
const KNOWN_REACT_COMPONENTS_IN_CODE_FENCES = new Set<string>(["CopilotChat"]);
816-
817828
export const SUBPATH_TO_COMPONENT: Record<string, string> = {
818829
"ag-ui": "AGUI",
819830
"coding-agents": "CodingAgents",
@@ -940,6 +951,68 @@ export function stripLeadingImports(source: string): string {
940951
return out.join("\n");
941952
}
942953

954+
/**
955+
* Returns true if `offset` falls inside a Markdown fenced code block
956+
* (```...``` or ~~~...~~~) or an inline code span (`...`) within
957+
* `content`. Best-effort: scans from the start of `content` and tracks
958+
* fence state line by line. Markdown requires fence markers at the start
959+
* of a line (optionally preceded by up to three spaces), so we anchor on
960+
* that. Used by `inlineSnippets()` to skip JSX-looking matches that
961+
* appear inside example code (e.g. `<CopilotChat />` shown as runtime
962+
* usage in slots.mdx) rather than as snippet imports.
963+
*/
964+
function isInsideCodeFence(content: string, offset: number): boolean {
965+
// Split the text up to the match into completed lines + a possibly
966+
// partial trailing line. We treat all completed lines as candidate
967+
// fence boundaries and the trailing partial line as the context for
968+
// inline-code (single-backtick) detection.
969+
const lines = content.slice(0, offset).split("\n");
970+
const completed = lines.slice(0, -1);
971+
const currentLine = lines[lines.length - 1] ?? "";
972+
973+
// Fenced blocks: walk completed lines and toggle on matching
974+
// opener/closer. CommonMark allows up to 3 leading spaces; MDX in
975+
// shell-docs is more permissive — fences inside `<Step>` and other
976+
// JSX containers are routinely indented 8+ spaces. Match any
977+
// leading whitespace so those fences aren't missed.
978+
let inFence = false;
979+
let openerChar: string | null = null;
980+
for (const line of completed) {
981+
const fenceMatch = line.match(/^\s*(`{3,}|~{3,})/);
982+
if (!fenceMatch) continue;
983+
const marker = fenceMatch[1];
984+
if (!inFence) {
985+
inFence = true;
986+
openerChar = marker[0];
987+
} else if (marker[0] === openerChar) {
988+
inFence = false;
989+
openerChar = null;
990+
}
991+
}
992+
if (inFence) return true;
993+
994+
// Inline code: count single-backtick toggles on the partial current
995+
// line. A single backtick opens an inline span that closes on the
996+
// next single backtick. Runs of 2+ backticks are rare in prose
997+
// (literal-backtick spans) and intentionally ignored so the common
998+
// `<Component />` case is caught reliably.
999+
let inlineToggles = 0;
1000+
let i = 0;
1001+
while (i < currentLine.length) {
1002+
if (currentLine[i] !== "`") {
1003+
i++;
1004+
continue;
1005+
}
1006+
let run = 0;
1007+
while (i + run < currentLine.length && currentLine[i + run] === "`") {
1008+
run++;
1009+
}
1010+
if (run === 1) inlineToggles++;
1011+
i += run;
1012+
}
1013+
return inlineToggles % 2 === 1;
1014+
}
1015+
9431016
export function inlineSnippets(
9441017
content: string,
9451018
slugPath: string = "",
@@ -949,7 +1022,16 @@ export function inlineSnippets(
9491022

9501023
result = result.replace(
9511024
/<([A-Z]\w*)\s*(?:components=\{[^}]*\}\s*)?\/>/g,
952-
(match, componentName) => {
1025+
(match, componentName, offset: number, source: string) => {
1026+
// Skip JSX-looking strings inside code fences / inline code: those
1027+
// are rendered example code, not snippet imports. Suppresses the
1028+
// bulk of `[docs-render] snippet missing` warnings that surfaced
1029+
// post-cutover (e.g. <CopilotChat />, <YourApp />, <WeatherCard />
1030+
// shown as usage examples inside ```tsx ... ``` blocks).
1031+
if (isInsideCodeFence(source, offset)) {
1032+
return match;
1033+
}
1034+
9531035
let snippetRel = SNIPPET_MAP[componentName];
9541036

9551037
if (!snippetRel && componentName === "SharedContent" && slugPath) {
@@ -979,15 +1061,20 @@ export function inlineSnippets(
9791061
}
9801062

9811063
if (!snippetRel) {
982-
// Suppress noise for runtime React components that appear inside
983-
// example code fences (the regex isn't fence-aware — see comment
984-
// above KNOWN_REACT_COMPONENTS_IN_CODE_FENCES).
985-
if (KNOWN_REACT_COMPONENTS_IN_CODE_FENCES.has(componentName)) {
1064+
// Components ending in `Icon` are conventionally lucide-react
1065+
// icons. shell-docs's MDX renders them via the `docsComponents`
1066+
// global registry in mdx-registry.tsx, so they're real runtime
1067+
// React components — not snippet imports. Skip silently rather
1068+
// than warn (matches the same shape as the fence-aware short
1069+
// circuit above for `<CopilotChat />` in prose backticks).
1070+
if (componentName.endsWith("Icon")) {
9861071
return match;
9871072
}
9881073
// Log so docs authors see a clean signal when a <Component />
9891074
// reference can't be mapped to a snippet file (previously the
990-
// component just silently rendered nothing).
1075+
// component just silently rendered nothing). Matches inside code
1076+
// fences are short-circuited above so this warning only fires on
1077+
// genuine prose-level references.
9911078
console.warn(
9921079
"[docs-render] snippet missing for component",
9931080
componentName,

0 commit comments

Comments
 (0)