Add v1 headless integration surface and polish docs#35
Conversation
New public API:
- `createDocsSource()` at the root — framework-neutral source primitive
(navigation, page loading, search, includes) for Next, Astro, Vite,
Nuxt, SvelteKit, and any MDX-aware bundler
- `leadtype/mdx` subpath — tag prop contracts for every custom MDX tag
plus `mdxSourcePlugins` (build-time include + ExtractedTypeTable
resolution, leaves custom tags as JSX)
- `leadtype/fumadocs` subpath — thin Source adapter for fumadocs-core,
walks pages + meta.json. Optional peer dep on fumadocs-core >= 15
New helpers:
- `convertMdxFile(path, plugins)` — in-memory MDX → {ast, frontmatter,
data, markdown}
- `resolveInclude` / `parseIncludeSpecifier` / `extractMdxSection` /
`resolveIncludePath` — standalone include resolution, no remark
transform required
- `remarkResolveTypeTableJsx` — JSX-preserving variant of the type-table
plugin for the source preset
Frontmatter:
- New optional `order:` field for sidebar ordering within a group
Navigation:
- `resolveDocsNavigation` accepts an optional `docsDirName` config
(defaults to "docs") for projects whose docs folder isn't named docs/
Bug fixes:
- Mermaid `<br/>` syntax preserved in agent-flattened markdown (was
being mangled to "/" and "-" by two separate passes, breaking
downstream renderers)
- Mermaid plugin's backtick stripper handles inline template-literal
form, not just backticks-on-their-own-lines
Docs polish:
- New `/docs/build/build-a-docs-site` umbrella with explicit decision
tree (source primitive vs CLI artifacts)
- New `/docs/build/use-the-source-primitive` — multi-framework
walkthrough with anchor-linkable per-host sections
- New `/docs/build/integrate-with-fumadocs` — first-party fumadocs
recipe with TL;DR
- New `/docs/build/generate-static-artifacts` — CLI workflow
- New reference pages `/docs/reference/mdx` and `/docs/reference/source`
- Quickstart rewritten as framework-neutral primitive walkthrough
(was Next-flavored CLI walkthrough); home page tightened; deleted
the overlapping connect-docs-site + render-mdx-and-toc pages
- Code blocks now use `title="..."` fence meta instead of leading
`// filename` comments — cleaner rendering via rehype-code
- apps/example refactored to a single `[[...slug]]` catch-all powered
by a createDocsSource-generated manifest (was 18 hand-rolled route
files)
- New apps/fumadocs-example demonstrates leadtype/fumadocs end-to-end
with c15t docs as content
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds a v1 headless docs integration: a framework-neutral ChangesCore library: docs source, MDX, conversion, remark plugins, navigation, packaging
Fumadocs adapter
Example apps, build manifest, and catch-all routing
Fumadocs example Next.js app & MDX components
Docs, tests, and polish
Sequence Diagram(s)sequenceDiagram
participant Host as Docs App
participant Source as createDocsSource()
participant FS as File System
participant Parser as MDX Parser (remark)
participant Plugins as mdxSourcePlugins
participant TOC as TOC Extractor
Host->>Source: loadPage(slug)
activate Source
Source->>FS: read .mdx file
FS-->>Source: raw MDX + frontmatter
Source->>Parser: parse with remark
activate Parser
Parser->>Plugins: apply mdxSourcePlugins
activate Plugins
Plugins->>FS: resolve <include> refs
FS-->>Plugins: include content
Plugins->>Plugins: expand includes & resolve <ExtractedTypeTable>
Plugins-->>Parser: transformed MDAST
deactivate Plugins
Parser->>Parser: serialize to markdown
Parser-->>Source: ast + markdown
deactivate Parser
Source->>TOC: extract headings (if enabled)
activate TOC
TOC-->>Source: toc array
deactivate TOC
Source-->>Host: DocsPage { ast, markdown, frontmatter, toc, meta }
deactivate Source
sequenceDiagram
participant Next as Next.js Build
participant FumadocsAdapter as fumadocsSource()
participant LeadtypeSource as createDocsSource()
participant FS as File System
participant Fumadocs as fumadocs-core loader
Next->>FumadocsAdapter: fumadocsSource({ contentDir })
activate FumadocsAdapter
FumadocsAdapter->>LeadtypeSource: createDocsSource(config)
activate LeadtypeSource
LeadtypeSource->>FS: scan contentDir
FS-->>LeadtypeSource: .md/.mdx files
LeadtypeSource-->>FumadocsAdapter: DocsSource
deactivate LeadtypeSource
FumadocsAdapter->>LeadtypeSource: listPages()
LeadtypeSource-->>FumadocsAdapter: DocsPageMeta[]
FumadocsAdapter->>FS: glob **/meta.json (optional)
FS-->>FumadocsAdapter: meta.json entries
FumadocsAdapter-->>Next: fumadocs Source object (files + loadPage)
deactivate FumadocsAdapter
Next->>Fumadocs: loader({ baseUrl, source })
Fumadocs-->>Next: runtime source wiring
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 12
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/example/scripts/docs-source-manifest.ts`:
- Line 48: The generated globKey is using path.relative which yields
OS-dependent separators (backslashes on Windows), causing mismatches with
import.meta.glob keys; update the code that builds globKey (the expression using
relative(join(appRoot, "src", "routes", "docs"), join(contentDir,
page.relativePath)) and page.extension) to normalize to POSIX separators before
writing the manifest — e.g., compute the relative path, replace backslashes with
forward slashes (or use path.posix equivalents) and then append page.extension
so globKey always uses forward slashes compatible with import.meta.glob.
In `@apps/example/src/routes/docs/`$.tsx:
- Around line 74-80: The useMemo for MdxComponent currently lists [page.globKey,
page.urlPath] as dependencies; simplify this to depend on the whole page object
instead (i.e., use [page]) so that any change to _splat producing a new page
reference will retrigger the memo; keep the inner logic using
mdxModules[page.globKey], lazy(loader), and MissingMdxModule(urlPath) unchanged.
- Line 72: The code uses a type assertion "as DocsPage" on resolvePage(_splat)
which bypasses TypeScript safety; instead remove the cast and either use a
non-null assertion on resolvePage(_splat) or perform an explicit runtime null
check and narrow the type: call resolvePage(_splat) into a const (e.g.,
pageCandidate), if it's null/undefined throw a descriptive error, otherwise
assign it to page typed as DocsPage so the type is safely narrowed without using
"as DocsPage" (referencing resolvePage, _splat, and DocsPage to locate where to
change).
In `@docs/build/integrate-with-fumadocs.mdx`:
- Around line 108-121: The snippet calls notFound() inside the Page component
but never imports it; add an import for notFound from "next/navigation" at the
top of the snippet so the call in the Page function resolves (locate the Page
function and the notFound() call in the app/docs/[[...slug]]/page.tsx code and
add the missing import for notFound from "next/navigation").
In `@packages/leadtype/src/convert/convert.ts`:
- Around line 353-357: The parameter enrichFromGitFlag in the convertMdxFile
function is missing an explicit type; update the function signature for
convertMdxFile to declare enrichFromGitFlag: boolean (keeping the default value
= false) so the parameter is explicitly typed, e.g. change the parameter list to
include enrichFromGitFlag: boolean = false to match the inferred type and
codebase conventions.
- Around line 227-232: The compactMermaidBlocks function is a no-op but
currently invokes markdown.replace with MERMAID_FENCE_REGEX; simplify by
returning the input directly: change compactMermaidBlocks to just return
markdown and remove the needless call to markdown.replace (you can keep
MERMAID_FENCE_REGEX if still referenced elsewhere, but do not use it in this
function).
In `@packages/leadtype/src/fumadocs/index.ts`:
- Line 114: Replace the redundant arrow wrapper for loadPage with a direct
reference to the underlying function: remove the wrapper expression "loadPage:
(slug) => leadtype.loadPage(slug)" and use the function reference "loadPage:
leadtype.loadPage" so the property directly points to leadtype.loadPage
(referencing the loadPage property in this module and the leadtype.loadPage
method).
In `@packages/leadtype/src/lint/schema.ts`:
- Line 64: The schema's order field currently allows any number via
v.optional(v.number()); change this to enforce integers (and optionally
non-negative) to prevent fractional orders—e.g., replace the current
v.optional(v.number()) with something like v.optional(v.number().int().min(0))
(or v.optional(v.integer().min(0)) if your validation lib exposes integer()) in
the schema where the order field is declared so ordering always uses whole
numbers.
In `@packages/leadtype/src/remark/plugins/type-table-jsx.remark.ts`:
- Around line 99-103: The double-cast "as unknown as Root['children'][number]"
bypasses TypeScript checks; replace it by giving the replacement a proper typed
identity or narrowing it with a type guard before splicing. Define a union MDX
node type (e.g., type MdxNode = Root['children'][number] | SomeOtherNode...) or
adjust the function that produces replacement to return
Root['children'][number], add a predicate function isMdxNode(node): node is
Root['children'][number] to validate the value, and only call
parentChildren.splice(index, 1, replacement) after the type guard ensures
replacement is the correct type (remove the double-cast). Ensure references to
replacement, parentChildren.splice and Root['children'][number] are updated
accordingly.
- Around line 84-91: Add an explicit return type to the main plugin factory
function remarkResolveTypeTableJsx: annotate its signature to return a
Transformer (or a function type like (tree: Root) => Root) to make the complex
transformer return explicit; keep the existing parameter type
RemarkResolveTypeTableJsxOptions and ensure you import/retain the Transformer or
Root types used for the annotation.
In `@packages/leadtype/src/source/index.ts`:
- Around line 216-219: The nested ternary for tocOptions (const tocOptions =
config.toc === false ? false : (config.toc ?? ({} as
DocsTableOfContentsOptions));) is hard to read—simplify by removing the inner
ternary and unnecessary inline type-assertion: either use an explicit if/else to
set tocOptions when config.toc === false else assign config.toc ?? {} with
tocOptions typed as DocsTableOfContentsOptions | false, or extract a small
helper like getTocOptions(config) that returns DocsTableOfContentsOptions |
false; update references to tocOptions accordingly.
- Around line 250-262: findMetaForSlug currently does a linear search on every
loadPage call causing O(n) cost; instead, when cachedMetas is computed create a
Map keyed by slug.join("/") (eg. cachedMetaMap: Map<string, DocsPageMeta>) and
populate it from cachedMetas, then replace uses of findMetaForSlug in loadPage
with a constant-time lookup cachedMetaMap.get(targetSlug). Update or remove the
findMetaForSlug function as needed and ensure cachedMetaMap is initialized once
alongside cachedMetas so subsequent loadPage calls use O(1) lookups.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 640aa7b8-3114-4c34-a073-2b69503040ba
⛔ Files ignored due to path filters (6)
apps/example/src/generated/agent-readability.jsonis excluded by!**/generated/**apps/example/src/generated/docs-nav.jsonis excluded by!**/generated/**apps/example/src/generated/docs-pages.jsonis excluded by!**/generated/**apps/example/src/generated/docs-search-content.jsonis excluded by!**/generated/**apps/example/src/generated/docs-search-index.jsonis excluded by!**/generated/**bun.lockis excluded by!**/*.lock
📒 Files selected for processing (80)
.changeset/v1-public-surface-and-docs-polish.mdapps/example/package.jsonapps/example/scripts/docs-source-manifest.tsapps/example/src/components/docs-mdx/callout.tsxapps/example/src/routeTree.gen.tsapps/example/src/routes/docs/$.tsxapps/example/src/routes/docs/authoring/components.tsxapps/example/src/routes/docs/authoring/frontmatter.tsxapps/example/src/routes/docs/build/add-search.tsxapps/example/src/routes/docs/build/connect-docs-site.tsxapps/example/src/routes/docs/build/optimize-docs-for-agents.tsxapps/example/src/routes/docs/build/render-mdx-and-toc.tsxapps/example/src/routes/docs/build/validate-in-ci.tsxapps/example/src/routes/docs/how-it-works.tsxapps/example/src/routes/docs/methodology.tsxapps/example/src/routes/docs/package-docs/bundle.tsxapps/example/src/routes/docs/quickstart.tsxapps/example/src/routes/docs/reference/cli.tsxapps/example/src/routes/docs/reference/convert.tsxapps/example/src/routes/docs/reference/evals.tsxapps/example/src/routes/docs/reference/lint.tsxapps/example/src/routes/docs/reference/llm.tsxapps/example/src/routes/docs/reference/remark.tsxapps/example/src/routes/docs/reference/search.tsxapps/example/vite.config.tsapps/fumadocs-example/app/docs/[[...slug]]/page.tsxapps/fumadocs-example/app/docs/layout.tsxapps/fumadocs-example/app/global.cssapps/fumadocs-example/app/layout.tsxapps/fumadocs-example/app/page.tsxapps/fumadocs-example/css.d.tsapps/fumadocs-example/lib/framework-switcher.tsxapps/fumadocs-example/lib/mdx-components.tsxapps/fumadocs-example/lib/source.tsapps/fumadocs-example/mdx-components.tsxapps/fumadocs-example/next-env.d.tsapps/fumadocs-example/next.config.mjsapps/fumadocs-example/package.jsonapps/fumadocs-example/postcss.config.mjsapps/fumadocs-example/scripts/setup-source.tsapps/fumadocs-example/tsconfig.jsondocs/authoring/components.mdxdocs/authoring/frontmatter.mdxdocs/build/add-search.mdxdocs/build/build-a-docs-site.mdxdocs/build/generate-static-artifacts.mdxdocs/build/integrate-with-fumadocs.mdxdocs/build/optimize-docs-for-agents.mdxdocs/build/render-mdx-and-toc.mdxdocs/build/use-the-source-primitive.mdxdocs/build/validate-in-ci.mdxdocs/docs.config.tsdocs/index.mdxdocs/methodology.mdxdocs/package-docs/bundle.mdxdocs/quickstart.mdxdocs/reference/evals.mdxdocs/reference/llm.mdxdocs/reference/mdx.mdxdocs/reference/source.mdxpackages/leadtype/package.jsonpackages/leadtype/rollup.config.tspackages/leadtype/src/cli.test.tspackages/leadtype/src/convert/convert.test.tspackages/leadtype/src/convert/convert.tspackages/leadtype/src/convert/index.tspackages/leadtype/src/fumadocs/index.tspackages/leadtype/src/index.tspackages/leadtype/src/internal/package-surface.test.tspackages/leadtype/src/lint/schema.tspackages/leadtype/src/llm/llm.tspackages/leadtype/src/mdx/index.tspackages/leadtype/src/mdx/source-preset.tspackages/leadtype/src/mdx/tag-types.tspackages/leadtype/src/remark/plugins/include.remark.test.tspackages/leadtype/src/remark/plugins/include.remark.tspackages/leadtype/src/remark/plugins/mermaid.remark.tspackages/leadtype/src/remark/plugins/type-table-jsx.remark.tspackages/leadtype/src/source/index.tspackages/leadtype/src/source/source.test.ts
💤 Files with no reviewable changes (19)
- apps/example/src/routes/docs/authoring/frontmatter.tsx
- apps/example/src/routes/docs/build/optimize-docs-for-agents.tsx
- apps/example/src/routes/docs/authoring/components.tsx
- apps/example/src/routes/docs/reference/llm.tsx
- apps/example/src/routes/docs/build/render-mdx-and-toc.tsx
- apps/example/src/routes/docs/build/add-search.tsx
- apps/example/src/routes/docs/quickstart.tsx
- apps/example/src/routes/docs/build/validate-in-ci.tsx
- apps/example/src/routes/docs/reference/evals.tsx
- apps/example/src/routes/docs/methodology.tsx
- apps/example/src/routes/docs/reference/remark.tsx
- apps/example/src/routes/docs/reference/lint.tsx
- apps/example/src/routes/docs/reference/cli.tsx
- apps/example/src/routes/docs/reference/convert.tsx
- apps/example/src/routes/docs/build/connect-docs-site.tsx
- apps/example/src/routes/docs/package-docs/bundle.tsx
- apps/example/src/routes/docs/reference/search.tsx
- docs/build/render-mdx-and-toc.mdx
- apps/example/src/routes/docs/how-it-works.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Preferunknownoveranywhen the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions
Files:
apps/fumadocs-example/next-env.d.tsapps/fumadocs-example/css.d.tsapps/fumadocs-example/app/page.tsxpackages/leadtype/src/internal/package-surface.test.tspackages/leadtype/src/convert/convert.test.tsapps/example/vite.config.tsapps/fumadocs-example/app/layout.tsxapps/fumadocs-example/mdx-components.tsxpackages/leadtype/src/lint/schema.tsapps/example/scripts/docs-source-manifest.tspackages/leadtype/src/convert/index.tsapps/fumadocs-example/scripts/setup-source.tspackages/leadtype/src/remark/plugins/type-table-jsx.remark.tspackages/leadtype/src/index.tsapps/fumadocs-example/lib/framework-switcher.tsxpackages/leadtype/src/fumadocs/index.tspackages/leadtype/src/mdx/source-preset.tsapps/fumadocs-example/app/docs/layout.tsxpackages/leadtype/rollup.config.tsapps/example/src/routes/docs/$.tsxdocs/docs.config.tspackages/leadtype/src/remark/plugins/mermaid.remark.tspackages/leadtype/src/mdx/index.tsapps/example/src/components/docs-mdx/callout.tsxpackages/leadtype/src/cli.test.tspackages/leadtype/src/llm/llm.tsapps/fumadocs-example/lib/source.tsapps/fumadocs-example/lib/mdx-components.tsxpackages/leadtype/src/source/index.tspackages/leadtype/src/source/source.test.tsapps/fumadocs-example/app/docs/[[...slug]]/page.tsxpackages/leadtype/src/mdx/tag-types.tspackages/leadtype/src/remark/plugins/include.remark.test.tsapps/example/src/routeTree.gen.tspackages/leadtype/src/remark/plugins/include.remark.tspackages/leadtype/src/convert/convert.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Preferfor...ofloops over.forEach()and indexedforloops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Useconstby default,letonly when reassignment is needed, nevervar
Alwaysawaitpromises in async functions - don't forget to use the return value
Useasync/awaitsyntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Removeconsole.log,debugger, andalertstatements from production code
ThrowErrorobjects with descriptive messages, not strings or other values
Usetry-catchblocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Don't useeval()or assign directly todocument.cookie
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Use descriptive names for functions, variables, and types for meaningful naming
Add comments for complex logic, but prefer self-documenting code
Files:
apps/fumadocs-example/next-env.d.tsapps/fumadocs-example/css.d.tsapps/fumadocs-example/app/page.tsxpackages/leadtype/src/internal/package-surface.test.tspackages/leadtype/src/convert/convert.test.tsapps/example/vite.config.tsapps/fumadocs-example/app/layout.tsxapps/fumadocs-example/mdx-components.tsxpackages/leadtype/src/lint/schema.tsapps/example/scripts/docs-source-manifest.tspackages/leadtype/src/convert/index.tsapps/fumadocs-example/scripts/setup-source.tspackages/leadtype/src/remark/plugins/type-table-jsx.remark.tspackages/leadtype/src/index.tsapps/fumadocs-example/lib/framework-switcher.tsxpackages/leadtype/src/fumadocs/index.tspackages/leadtype/src/mdx/source-preset.tsapps/fumadocs-example/app/docs/layout.tsxpackages/leadtype/rollup.config.tsapps/example/src/routes/docs/$.tsxdocs/docs.config.tspackages/leadtype/src/remark/plugins/mermaid.remark.tspackages/leadtype/src/mdx/index.tsapps/example/src/components/docs-mdx/callout.tsxpackages/leadtype/src/cli.test.tspackages/leadtype/src/llm/llm.tsapps/fumadocs-example/lib/source.tsapps/fumadocs-example/lib/mdx-components.tsxpackages/leadtype/src/source/index.tspackages/leadtype/src/source/source.test.tsapps/fumadocs-example/app/docs/[[...slug]]/page.tsxpackages/leadtype/src/mdx/tag-types.tspackages/leadtype/src/remark/plugins/include.remark.test.tsapps/example/src/routeTree.gen.tspackages/leadtype/src/remark/plugins/include.remark.tspackages/leadtype/src/convert/convert.ts
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{jsx,tsx}: Use function components over class components in React
Call hooks at the top level only, never conditionally
Specify all dependencies in hook dependency arrays correctly
Use thekeyprop for elements in iterables (prefer unique IDs over array indices)
Nest children between opening and closing tags instead of passing as props
Don't define components inside other components
AvoiddangerouslySetInnerHTMLunless absolutely necessary
Use proper image components (e.g., Next.js<Image>) over<img>tags
Use Next.js<Image>component for images
Usenext/heador App Router metadata API for head elements in Next.js
Use Server Components for async data fetching instead of async Client Components in Next.js
Use ref as a prop instead ofReact.forwardRefin React 19+
Files:
apps/fumadocs-example/app/page.tsxapps/fumadocs-example/app/layout.tsxapps/fumadocs-example/mdx-components.tsxapps/fumadocs-example/lib/framework-switcher.tsxapps/fumadocs-example/app/docs/layout.tsxapps/example/src/routes/docs/$.tsxapps/example/src/components/docs-mdx/callout.tsxapps/fumadocs-example/lib/mdx-components.tsxapps/fumadocs-example/app/docs/[[...slug]]/page.tsx
**/*.{jsx,tsx,html}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{jsx,tsx,html}: Use semantic HTML and ARIA attributes for accessibility: provide meaningful alt text for images, use proper heading hierarchy, add labels for form inputs, include keyboard event handlers alongside mouse events, use semantic elements instead of divs with roles
Addrel="noopener"when usingtarget="_blank"on links
Files:
apps/fumadocs-example/app/page.tsxapps/fumadocs-example/app/layout.tsxapps/fumadocs-example/mdx-components.tsxapps/fumadocs-example/lib/framework-switcher.tsxapps/fumadocs-example/app/docs/layout.tsxapps/example/src/routes/docs/$.tsxapps/example/src/components/docs-mdx/callout.tsxapps/fumadocs-example/lib/mdx-components.tsxapps/fumadocs-example/app/docs/[[...slug]]/page.tsx
**/*.{test,spec}.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{test,spec}.{js,ts,jsx,tsx}: Write assertions insideit()ortest()blocks
Avoid done callbacks in async tests - use async/await instead
Don't use.onlyor.skipin committed code
Keep test suites reasonably flat - avoid excessivedescribenesting
Files:
packages/leadtype/src/internal/package-surface.test.tspackages/leadtype/src/convert/convert.test.tspackages/leadtype/src/cli.test.tspackages/leadtype/src/source/source.test.tspackages/leadtype/src/remark/plugins/include.remark.test.ts
**/index.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Avoid barrel files (index files that re-export everything)
Files:
packages/leadtype/src/convert/index.tspackages/leadtype/src/index.tspackages/leadtype/src/fumadocs/index.tspackages/leadtype/src/mdx/index.tspackages/leadtype/src/source/index.ts
🪛 LanguageTool
docs/reference/mdx.mdx
[style] ~225-~225: This phrase is redundant. Consider using “outside”.
Context: ... remark — useful when loading a partial outside of the bundler pipeline: ```ts import { r...
(OUTSIDE_OF)
docs/reference/source.mdx
[style] ~9-~9: Consider using “who” when you are referring to a person instead of an object.
Context: ...-neutral** entry point for any consumer that wants to render leadtype-authored MDX i...
(THAT_WHO)
[style] ~96-~96: This phrase is redundant. Consider using “outside”.
Context: ...ontentDir`. Useful for loading partials outside of the MDX compile path. ## Choosing betw...
(OUTSIDE_OF)
docs/build/generate-static-artifacts.mdx
[uncategorized] ~203-~203: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ... — hosted routing index with page-level markdown links - public/llms-full.txt — all ge...
(MARKDOWN_NNP)
[uncategorized] ~204-~204: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ... public/llms-full.txt — all generated markdown docs in one fallback file - `public/doc...
(MARKDOWN_NNP)
🪛 markdownlint-cli2 (0.22.1)
.changeset/v1-public-surface-and-docs-polish.md
[warning] 5-5: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
🪛 Stylelint (17.11.0)
apps/fumadocs-example/app/global.css
[error] 9-9: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)
(scss/at-rule-no-unknown)
🔇 Additional comments (65)
apps/fumadocs-example/next-env.d.ts (1)
1-7: LGTM!docs/reference/evals.mdx (1)
19-19: LGTM!apps/fumadocs-example/postcss.config.mjs (1)
1-5: LGTM!apps/fumadocs-example/css.d.ts (1)
1-1: LGTM!docs/authoring/frontmatter.mdx (1)
15-16: LGTM!Also applies to: 60-60
packages/leadtype/src/cli.test.ts (1)
179-179: LGTM!Also applies to: 204-204, 812-812, 834-834, 844-844, 1043-1043
apps/fumadocs-example/tsconfig.json (1)
1-29: LGTM!apps/fumadocs-example/app/layout.tsx (1)
1-19: LGTM!apps/fumadocs-example/app/global.css (1)
9-9: ⚡ Quick winNo action needed — stylelint is not configured in this project, and
@sourceis already used successfully inapps/example/src/styles.css. The directive is valid Tailwind v4 syntax with no CI/lint risk.apps/fumadocs-example/app/page.tsx (1)
1-21: LGTM!docs/build/add-search.mdx (1)
5-5: LGTM!packages/leadtype/src/remark/plugins/mermaid.remark.ts (2)
14-15: LGTM!
27-42: LGTM!apps/fumadocs-example/lib/framework-switcher.tsx (4)
5-14: LGTM!
16-28: LGTM!
30-43: LGTM!
45-75: LGTM!packages/leadtype/src/remark/plugins/type-table-jsx.remark.ts (4)
1-25: LGTM!
50-82: LGTM!
106-159: LGTM!
38-48: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueConsider adding explicit return types to helper functions.
While the return types are clear from the implementation, adding explicit
: JsxAttributereturn type annotations would enhance clarity and catch potential errors if the implementation changes. As per coding guidelines, explicit types should be used when they enhance clarity.💡 Suggested improvement
-function stringAttribute(name: string, value: string) { +function stringAttribute(name: string, value: string): JsxAttribute { return { type: "mdxJsxAttribute", name, value }; } -function expressionAttribute(name: string, expression: string) { +function expressionAttribute(name: string, expression: string): JsxAttribute { return {> Likely an incorrect or invalid review comment.packages/leadtype/src/convert/convert.ts (1)
331-340: LGTM!packages/leadtype/src/convert/index.ts (1)
1-10: LGTM!packages/leadtype/src/convert/convert.test.ts (1)
56-102: LGTM!packages/leadtype/src/remark/plugins/include.remark.ts (6)
66-79: LGTM!
87-114: LGTM!
322-374: LGTM!
376-449: LGTM!
466-559: LGTM!
284-320: LGTM!packages/leadtype/src/mdx/source-preset.ts (1)
1-36: LGTM!packages/leadtype/src/remark/plugins/include.remark.test.ts (1)
1-177: LGTM!packages/leadtype/src/mdx/tag-types.ts (1)
1-317: LGTM!packages/leadtype/src/mdx/index.ts (1)
1-79: LGTM!apps/example/vite.config.ts (1)
5-5: LGTM!Also applies to: 39-48
docs/reference/mdx.mdx (1)
1-258: LGTM!docs/build/validate-in-ci.mdx (1)
5-5: LGTM!docs/reference/llm.mdx (1)
455-455: LGTM!docs/build/optimize-docs-for-agents.mdx (1)
5-5: LGTM!Also applies to: 210-210
docs/authoring/components.mdx (1)
30-30: LGTM!docs/package-docs/bundle.mdx (1)
34-34: LGTM!Also applies to: 96-96, 171-171
packages/leadtype/src/internal/package-surface.test.ts (1)
11-12: LGTM!packages/leadtype/src/index.ts (1)
1-10: LGTM!Also applies to: 23-29
apps/fumadocs-example/mdx-components.tsx (1)
11-12: ⚡ Quick winVerify the component merge order.
The spread order
{ ...components, ...mdxComponents }meansmdxComponentswill override any caller-provided components. Typically, user-provided components should take precedence over defaults, suggesting the order should be{ ...mdxComponents, ...components }.If the intent is for the shared
mdxComponentsmap to always take precedence (preventing overrides), then this is correct. Otherwise, consider reversing the merge order.🔄 Proposed fix if user components should override defaults
export function useMDXComponents(components: MDXComponents): MDXComponents { - return { ...components, ...mdxComponents }; + return { ...mdxComponents, ...components }; }docs/docs.config.ts (1)
18-19: LGTM!apps/fumadocs-example/next.config.mjs (1)
1-27: LGTM!apps/fumadocs-example/scripts/setup-source.ts (1)
20-36: LGTM!apps/fumadocs-example/package.json (1)
35-35: ⚖️ Poor tradeoffVerify the TypeScript version.
TypeScript 6.0.3 does not exist in the public npm registry (latest stable is 5.x). Along with the
@typescript/native-previewexperimental package dated May 2026, this suggests experimental or internal tooling.Confirm whether:
- This is an internal/preview build that all team members can access
- CI/CD is configured to resolve this version
- This is intentional for testing upcoming TypeScript features
If this is a typo, the latest stable TypeScript 5.x should be used instead.
#!/bin/bash # Check if TypeScript 6.0.3 exists in npm registry and find latest stable version npm view typescript versions --json | jq -r '.[-10:]' || echo "Failed to fetch versions" npm view typescript version || echo "Failed to fetch latest version"packages/leadtype/rollup.config.ts (1)
8-9: LGTM!apps/example/package.json (1)
20-21: LGTM!apps/fumadocs-example/app/docs/layout.tsx (1)
1-57: LGTM!docs/build/build-a-docs-site.mdx (1)
1-84: LGTM!apps/fumadocs-example/lib/source.ts (1)
1-29: LGTM!packages/leadtype/src/source/source.test.ts (1)
1-177: LGTM!docs/build/use-the-source-primitive.mdx (1)
1-239: LGTM!packages/leadtype/package.json (1)
28-35: LGTM!packages/leadtype/src/llm/llm.ts (1)
68-82: LGTM!Also applies to: 195-201, 540-604, 670-683, 1204-1209
apps/fumadocs-example/lib/mdx-components.tsx (1)
1-243: LGTM!apps/example/src/components/docs-mdx/callout.tsx (1)
1-13: LGTM!apps/fumadocs-example/app/docs/[[...slug]]/page.tsx (1)
21-70: LGTM!docs/reference/source.mdx (1)
1-198: LGTM!docs/build/generate-static-artifacts.mdx (1)
134-196: LGTM!apps/example/src/routeTree.gen.ts (1)
1-392: LGTM!packages/leadtype/src/source/index.ts (1)
204-364: LGTM!packages/leadtype/src/fumadocs/index.ts (1)
138-144: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueConsider logging malformed meta.json files for easier debugging.
The catch block silently ignores JSON parse errors. While the comment indicates fumadocs will surface a warning during page-tree building, adding a console warning here would help developers debug malformed files during development.
📝 Proposed logging addition
try { data = JSON.parse(raw) as LeadtypeFumadocsMetaData; - } catch { + } catch (err) { // Malformed meta.json: keep the entry so fumadocs can surface a // helpful warning during page-tree building instead of silently // ignoring it. + console.warn(`Failed to parse ${relativePath}:`, err); }> Likely an incorrect or invalid review comment.
| const MdxComponent = useMemo(() => { | ||
| const loader = mdxModules[page.globKey]; | ||
| if (!loader) { | ||
| return () => <MissingMdxModule urlPath={page.urlPath} />; | ||
| } | ||
| return lazy(loader); | ||
| }, [page.globKey, page.urlPath]); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | 💤 Low value
Consider simplifying useMemo dependencies.
The useMemo depends on page.globKey and page.urlPath individually, but since page itself is derived from _splat, you could simplify by depending on page directly:
- }, [page.globKey, page.urlPath]);
+ }, [page]);This is clearer and equally correct since any change to _splat produces a new page object reference.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const MdxComponent = useMemo(() => { | |
| const loader = mdxModules[page.globKey]; | |
| if (!loader) { | |
| return () => <MissingMdxModule urlPath={page.urlPath} />; | |
| } | |
| return lazy(loader); | |
| }, [page.globKey, page.urlPath]); | |
| const MdxComponent = useMemo(() => { | |
| const loader = mdxModules[page.globKey]; | |
| if (!loader) { | |
| return () => <MissingMdxModule urlPath={page.urlPath} />; | |
| } | |
| return lazy(loader); | |
| }, [page]); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/example/src/routes/docs/`$.tsx around lines 74 - 80, The useMemo for
MdxComponent currently lists [page.globKey, page.urlPath] as dependencies;
simplify this to depend on the whole page object instead (i.e., use [page]) so
that any change to _splat producing a new page reference will retrigger the
memo; keep the inner logic using mdxModules[page.globKey], lazy(loader), and
MissingMdxModule(urlPath) unchanged.
| parentChildren.splice( | ||
| index, | ||
| 1, | ||
| replacement as unknown as Root["children"][number] | ||
| ); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | ⚖️ Poor tradeoff
Type assertion reduces type safety.
The as unknown as Root["children"][number] double-cast bypasses TypeScript's type checking. Consider defining a proper union type for MDX nodes that can appear in the tree, or using a type guard to validate the replacement node's type before insertion.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/leadtype/src/remark/plugins/type-table-jsx.remark.ts` around lines
99 - 103, The double-cast "as unknown as Root['children'][number]" bypasses
TypeScript checks; replace it by giving the replacement a proper typed identity
or narrowing it with a type guard before splicing. Define a union MDX node type
(e.g., type MdxNode = Root['children'][number] | SomeOtherNode...) or adjust the
function that produces replacement to return Root['children'][number], add a
predicate function isMdxNode(node): node is Root['children'][number] to validate
the value, and only call parentChildren.splice(index, 1, replacement) after the
type guard ensures replacement is the correct type (remove the double-cast).
Ensure references to replacement, parentChildren.splice and
Root['children'][number] are updated accordingly.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6c6dfa2b5e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (metaPath === target) { | ||
| return meta; |
There was a problem hiding this comment.
Reject duplicate slugs before serving a page
When contentDir contains two files that normalize to the same route, such as guide.mdx and guide/index.mdx or both .md and .mdx variants, deriveSlug() maps both to the same joined slug. This loop silently returns whichever file sorts first, while listPages() and buildSearchIndex() still expose duplicate urlPath/document IDs; resolveDocsNavigation() already treats duplicate routes as an error, so the new source primitive can render/search a different page instead of failing fast. Please validate cachedMetas for duplicate slug/url paths before load/search.
Useful? React with 👍 / 👎.
| // Path the runtime catch-all can use to look up the MDX module against | ||
| // `import.meta.glob('../../../../docs/**/*.mdx')`. Always relative to | ||
| // src/routes/docs/$.tsx so the glob key matches exactly. | ||
| globKey: `${relative(join(appRoot, "src", "routes", "docs"), join(contentDir, page.relativePath))}${page.extension}`, |
There was a problem hiding this comment.
Normalize manifest glob keys to POSIX paths
When pipeline:source-manifest runs on Windows, path.relative() returns backslash-separated paths like ..\..\..\..\..\docs\quickstart.mdx, but Vite's import.meta.glob() keys are forward-slash paths. That makes mdxModules[page.globKey] undefined for every generated docs page and the catch-all route renders the missing-module fallback. Normalize this value to / separators before writing the manifest.
Useful? React with 👍 / 👎.
|
@pullfrog review |
Applied: - docs-source-manifest: normalize globKey to POSIX so the manifest works on Windows (import.meta.glob keys are always forward-slash) - docs/$.tsx: replace `as DocsPage` cast with an explicit null check that surfaces a clear error if the beforeLoad invariant ever breaks - integrate-with-fumadocs.mdx: add missing `notFound` import to the Next App Router snippet - convert.ts: simplify `compactMermaidBlocks` to `return markdown` (drop the no-op .replace call and the now-unused regex) - fumadocs/index.ts: replace `loadPage: (slug) => leadtype.loadPage(slug)` with `loadPage: leadtype.loadPage` — no this binding needed - lint/schema.ts: enforce integer `order:` via valibot pipe + integer - type-table-jsx.remark.ts: add explicit `(tree: Root) => Root` return type to the public plugin factory - source/index.ts: simplify the tocOptions ternary by dropping the awkward `as DocsTableOfContentsOptions` cast - source/index.ts: add `cachedMetaBySlug: Map<string, DocsPageMeta>` so loadPage runs in O(1) instead of O(n) per call Skipped (with reason): - useMemo deps `[page]` vs `[page.globKey, page.urlPath]`: page is a stable reference from a Map so the alternatives are equivalent; depending on primitives is more defensive - convertMdxFile `enrichFromGitFlag: boolean` annotation: matches the existing inferred-type convention used by convertMdxToMarkdown in the same file - type-table-jsx double cast: the type incompatibility is between mdast-util-mdx's MdxJsxFlowElement and mdast's Root["children"][number] — a runtime type guard can't bridge that; the cast is the conventional escape hatch
There was a problem hiding this comment.
Important
Three of the multi-framework walkthroughs the PR adds will mislead users following them verbatim (Nuxt MDC config, fumadocs server-component example, fumadocs-example layout's sidebar tabs), and the docs catch-all route prints a copy-paste command that doesn't exist. Inline comments below. There's also a small set of stale references left over from the connect-docs-site.mdx deletion that aren't anchored to lines in this diff — they're listed at the bottom of this body.
TL;DR — Ships the v1 headless integration surface (createDocsSource(), leadtype/mdx, leadtype/fumadocs), reshapes the onboarding docs around it, refactors the example app to a single catch-all route, and adds a fumadocs-example app. Also fixes a mermaid <br/> flattening bug that mangled labels in agent-flattened markdown.
Key changes
- v1 integration surface —
createDocsSource({ contentDir })at root; newleadtype/mdx(mdxSourcePluginspreset + tag prop types) andleadtype/fumadocs(fumadocsSourceadapter) subpaths; newconvertMdxFilein-memory convert helper. - Quickstart + multi-framework walkthrough — framework-agnostic Quickstart,
use-the-source-primitiveumbrella covering Next App Router, Astro, Vite +@mdx-js/rollup, Nuxt, and SvelteKit +mdsvex, plus TL;DR blocks on the two main recipes. - Docs reshuffle — deletes overlapping
connect-docs-site.mdxandrender-mdx-and-toc.mdx; converts// filenamecode-block comments totitle="..."fence meta; dropsapps/example/references from docs. - Mermaid
<br/>preservation — strips the old<br/>→-substitution incompactMermaidBlocksand updates the remark plugin's backtick stripper to handle the inlinechart={\…`}` form. - Example app catch-all —
apps/exampleswitches from 18 hand-rolled TanStack routes to a single[[...slug]]route powered by a build-timepipeline:source-manifestmanifest. - fumadocs-example app — Next 15 + fumadocs-ui 15 wired to c15t docs as the content fixture, with framework switcher and tailwind v4.
Summary | 86 files | 1 commit | base: main ← KayleeWilliams/better-rendering
v1 integration surface
Before: Consumers pieced together remark plugins, frontmatter parsing, and navigation by hand; no first-party fumadocs adapter.
After:createDocsSource()returns pages + navigation + search + include resolution;leadtype/mdxships the source plugin preset and tag prop contracts;leadtype/fumadocsadapts the source to fumadocs-core'sSource.
The new convertMdxFile returns the post-transform mdast ast alongside the serialized markdown so a renderer doesn't need to re-parse. Note the helper splits frontmatter with the regex form rather than going through parseFrontmatter — files whose frontmatter has no trailing newline before the body are silently treated as having no frontmatter, then synthesizeFrontmatter kicks in. Worth flagging once convertMdxFile is a documented public export.
packages/leadtype/src/source/index.ts · packages/leadtype/src/mdx/index.ts · packages/leadtype/src/fumadocs/index.ts · packages/leadtype/src/convert/convert.ts
Multi-framework walkthrough
Before: Quickstart linked to overlapping "connect-docs-site" / "render-mdx-and-toc" pages with framework details inlined per recipe.
After: Quickstart points at one umbrella page with anchor-linkable sections per framework, plus TL;DR copy-paste blocks on the two main recipes.
Three of the framework samples have concrete issues that will trip up the first user who pastes them — see the inline comments on the Nuxt and fumadocs blocks. The Astro, Vite + @mdx-js/rollup, and SvelteKit + mdsvex snippets line up with their upstream APIs (note: Vite users on @vitejs/plugin-react should add enforce: 'pre' to the mdx() plugin per the official @mdx-js/rollup guide, otherwise React parses MDX before mdx does).
docs/build/use-the-source-primitive.mdx · docs/build/integrate-with-fumadocs.mdx · docs/quickstart.mdx
Mermaid <br/> preservation
Before:
compactMermaidBlocksrewrote<br/>→-"for readability", breaking mermaid renderers downstream.
After: Pass is a stable seam that returns each block unchanged.
The remark plugin's backtick stripper also gained the inline chart={\…`} form. One asymmetry to note: the JSX-children form (flowchart LR
label) still flows through extractNodeText→mdastToString, which drops mdxJsxTextElementnodes entirely — sois lost beforecleanMermaidSource` ever sees it. The new behavior is preserved only for the template-literal attribute form. Worth a regression test if the JSX-children form is supposed to work too.
packages/leadtype/src/remark/plugins/mermaid.remark.ts · packages/leadtype/src/convert/convert.ts
Example app catch-all
Before: 18 hand-rolled per-page TanStack route files.
After: One[[...slug]]catch-all reads a build-timedocs-source-manifest.jsonand renders the matching MDX module viaimport.meta.glob.
The fallback MissingMdxModule component prints an actionable command — but the printed command doesn't match the actual script name (see inline). The manifest's globKey is also built from path.relative() which returns OS-native separators; the catch-all looks the key up against import.meta.glob keys that are always unix-style, so the manifest authored on Windows won't resolve any page.
apps/example/src/routes/docs/$.tsx · apps/example/scripts/docs-source-manifest.ts
Stale references after connect-docs-site / render-mdx-and-toc removal
These aren't anchored to lines in this diff, so they couldn't be left as inline comments — ordered by severity:
README.md:30,61andpackages/leadtype/README.md:43still link tohttps://leadtype.dev/docs/build/connect-docs-site. The package README ships in the npm tarball, so once published these are broken links in everynode_modules/leadtype/README.md. Repoint at/docs/build/build-a-docs-site.evals/evals/wire-content-negotiation/EVAL.ts:33-38asserts the agent readnode_modules/leadtype/docs/build/connect-docs-site.md. The target file no longer exists, so the eval will fail on every run after this PR lands. Retarget atbuild-a-docs-site.md.packages/leadtype/README.mdEntry points table doesn't list the new./mdxand./fumadocssubpath exports added by this PR (they're present inpackage.jsonand asserted bypackage-surface.test.ts).evals/lib/llms-variants.ts:127still usespath: "docs/build/connect-docs-site.md"/ title"Connect a docs site"in synthetic fixture content — self-contained, low impact, but terminology has drifted from the rest of the repo.
Also worth thinking about before 1.0: the newly-public resolveInclude / parseIncludeSpecifier / extractMdxSection helpers do plain path.resolve(baseDir, file) with no containment check, so an authored include of ../../../etc/passwd reads arbitrary files. Fine as long as include specifiers are trusted-author input, but it's worth documenting that contract on the export.
Claude Opus | 𝕏
| return ( | ||
| <div data-leadtype-mdx-error> | ||
| MDX module not found for <code>{urlPath}</code>. Re-run{" "} | ||
| <code>bun run docs-source-manifest</code> after adding docs files. |
There was a problem hiding this comment.
Script name is wrong — there's no docs-source-manifest script in apps/example/package.json (the one this PR adds is pipeline:source-manifest, see line 20 of the manifest). A user hitting this error gets command not found.
| <code>bun run docs-source-manifest</code> after adding docs files. | |
| <code>bun run pipeline:source-manifest</code> after adding docs files. |
|
|
||
| export default defineNuxtConfig({ | ||
| modules: ["@nuxtjs/mdc"], | ||
| mdc: { remarkPlugins: { ...mdxSourcePlugins } }, |
There was a problem hiding this comment.
mdxSourcePlugins is a PluggableList (array). Spreading it into an object literal produces { "0": pluginFn, "1": pluginFn, … }, which @nuxtjs/mdc won't recognize as plugins. Every other framework snippet on this page uses array spread; this one is the only outlier. Note also that @nuxtjs/mdc documents remarkPlugins as an object keyed by plugin name with { src, options } values, so the array form may still need adjusting for Nuxt — at minimum match the other snippets:
| mdc: { remarkPlugins: { ...mdxSourcePlugins } }, | |
| mdc: { remarkPlugins: [...mdxSourcePlugins] }, |
| ```tsx title="app/docs/[[...slug]]/page.tsx" | ||
| import { source } from "@/content/source"; | ||
| import { MDXRemote } from "next-mdx-remote/rsc"; | ||
|
|
||
| export default async function Page({ | ||
| params, | ||
| }: { | ||
| params: Promise<{ slug?: string[] }>; | ||
| }) { | ||
| const { slug } = await params; | ||
| const page = await source.loadPage(slug ?? []); | ||
| if (!page) { | ||
| notFound(); | ||
| } | ||
|
|
||
| return ( | ||
| <article> | ||
| <h1>{page.title}</h1> | ||
| <MDXRemote source={page.markdown} components={mdxComponents} /> | ||
| </article> | ||
| ); | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Three problems in this server-component sample: (1) notFound() is called without import { notFound } from "next/navigation";; (2) mdxComponents is referenced but never imported; (3) next-mdx-remote/rsc is the Hashicorp package, while the rest of this PR (the live apps/fumadocs-example/app/docs/[[...slug]]/page.tsx and use-the-source-primitive.mdx) standardizes on next-mdx-remote-client/rsc — readers comparing the two pages will get conflicting answers. Aligning with the working example:
| ```tsx title="app/docs/[[...slug]]/page.tsx" | |
| import { source } from "@/content/source"; | |
| import { MDXRemote } from "next-mdx-remote/rsc"; | |
| export default async function Page({ | |
| params, | |
| }: { | |
| params: Promise<{ slug?: string[] }>; | |
| }) { | |
| const { slug } = await params; | |
| const page = await source.loadPage(slug ?? []); | |
| if (!page) { | |
| notFound(); | |
| } | |
| return ( | |
| <article> | |
| <h1>{page.title}</h1> | |
| <MDXRemote source={page.markdown} components={mdxComponents} /> | |
| </article> | |
| ); | |
| } | |
| ``` | |
| ```tsx title="app/docs/[[...slug]]/page.tsx" | |
| import { notFound } from "next/navigation"; | |
| import { MDXRemote } from "next-mdx-remote-client/rsc"; | |
| import { source } from "@/content/source"; | |
| import { mdxComponents } from "@/lib/mdx-components"; | |
| export default async function Page({ | |
| params, | |
| }: { | |
| params: Promise<{ slug?: string[] }>; | |
| }) { | |
| const { slug } = await params; | |
| const page = await source.leadtype.loadPage(slug ?? []); | |
| if (!page) { | |
| notFound(); | |
| } | |
| return ( | |
| <article> | |
| <h1>{page.title}</h1> | |
| <MDXRemote source={page.markdown} components={mdxComponents} /> | |
| </article> | |
| ); | |
| } |
| nav={{ title: "c15t docs" }} | ||
| sidebar={{ | ||
| tabs: sidebarTabs, | ||
| banner: <FrameworkSwitcher knownRoutes={knownRoutes} />, |
There was a problem hiding this comment.
tabs is a top-level DocsLayout prop in fumadocs-ui v15, not a property of SidebarOptions. As written, sidebarTabs is silently dropped — the audience-grouping switcher won't render. The v15 SidebarOptions shape documents enabled, banner, components, collapsible, defaultOpenLevel, prefetch, footer only. Move tabs up one level:
| nav={{ title: "c15t docs" }} | |
| sidebar={{ | |
| tabs: sidebarTabs, | |
| banner: <FrameworkSwitcher knownRoutes={knownRoutes} />, | |
| tabs={sidebarTabs} | |
| sidebar={{ | |
| banner: <FrameworkSwitcher knownRoutes={knownRoutes} />, | |
| }} |
| // Path the runtime catch-all can use to look up the MDX module against | ||
| // `import.meta.glob('../../../../docs/**/*.mdx')`. Always relative to | ||
| // src/routes/docs/$.tsx so the glob key matches exactly. | ||
| globKey: `${relative(join(appRoot, "src", "routes", "docs"), join(contentDir, page.relativePath))}${page.extension}`, |
There was a problem hiding this comment.
relative() returns OS-native path separators, but the catch-all looks up keys against import.meta.glob(...) which always uses forward slashes. On Windows the manifest will contain backslashes and every page 404s. Normalize before joining the extension:
| globKey: `${relative(join(appRoot, "src", "routes", "docs"), join(contentDir, page.relativePath))}${page.extension}`, | |
| globKey: `${relative(join(appRoot, "src", "routes", "docs"), join(contentDir, page.relativePath)).split(sep).join("/")}${page.extension}`, |
There was a problem hiding this comment.
No new issues. Reviewed the following changes:
- Normalized the generated
globKeyindocs-source-manifest.tsto POSIX separators so the catch-all route resolves on Windows. - Replaced the
as DocsPagecast in the docs catch-all with an explicit null check that narrows the type and surfaces an invariant-break error. - Added the missing
notFoundimport to the fumadocs server-component recipe. - Simplified
compactMermaidBlocksto a true no-op and dropped the now-deadMERMAID_FENCE_REGEX. - Unwrapped
loadPagein the fumadocs adapter to a bare method reference (safe — the underlying function is a free closure, nothis). - Tightened the frontmatter
order:field to reject fractional values viav.pipe(v.number(), v.integer()). - Added an explicit
(tree: Root) => Rootreturn type toremarkResolveTypeTableJsx. - Introduced
cachedMetaBySlugfor O(1)findMetaForSluglookups, populated alongsidecachedMetas. - Cleaned up the
tocOptionsnested ternary and replaced theas DocsTableOfContentsOptionscast with an explicit annotation.
Claude Opus | 𝕏
There was a problem hiding this comment.
Note
Incremental review covering only 8f0b1f1 ("Address PR review: 9 fixes, 3 skips"). The new changes are clean — nothing new to flag.
TL;DR — Addresses several items from the previous review. New changes look correct.
Verified in this commit
- Windows
globKey—apps/example/scripts/docs-source-manifest.tsnow normalizes separators via atoPosixhelper. - Catch-all invariant —
apps/example/src/routes/docs/$.tsxreplaces theas DocsPagecast with an explicit null check that throws ifbeforeLoad's invariant ever breaks. compactMermaidBlocks— Simplified to a purereturn markdown;no-op, dropping the now-unusedMERMAID_FENCE_REGEX.findMetaForSlugO(1) —createDocsSourcenow keeps aMap<string, DocsPageMeta>alongsidecachedMetas, soloadPage()is a hash lookup after the first call.fumadocsSource.loadPage— Bound directly toleadtype.loadPagerather than wrapped.integrate-with-fumadocsserver-component sample —notFoundis now imported.orderfrontmatter validation — Tightened fromv.number()tov.pipe(v.number(), v.integer()), with a JSDoc note that fractional orders are rejected.
Summary | 11 files | 1 incremental commit | base: main ← KayleeWilliams/better-rendering
Claude Opus | 𝕏
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
packages/leadtype/src/remark/plugins/type-table-jsx.remark.ts (1)
99-103:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReplace unsafe cast with type-preserving transformation to avoid mdast structure violations.
The plugin accepts both
mdxJsxTextElementandmdxJsxFlowElement(lines 120–122) but unconditionally returnsmdxJsxFlowElement(line 77), creating a silent type mismatch. When<ExtractedTypeTable>appears inline within a paragraph (phrasing context), the replacement violates mdast constraints since flow elements cannot appear in phrasing content. The cast on line 102 masks this error.Preserve the node kind by accepting it as a parameter:
Preserve text vs flow node type, remove unsafe cast
import type { Root } from "mdast"; -import type { MdxJsxFlowElement } from "mdast-util-mdx"; +import type { MdxJsxFlowElement, MdxJsxTextElement } from "mdast-util-mdx"; function buildTypeTableNode( opts: { properties: Record<string, unknown>; title?: string; description?: string; name?: string; path?: string; -}): MdxJsxFlowElement { + }, + kind: "flow" | "text" +): MdxJsxFlowElement | MdxJsxTextElement { const attributes: JsxAttribute[] = [ expressionAttribute("properties", JSON.stringify(opts.properties)), ]; if (opts.title) { attributes.push(stringAttribute("title", opts.title)); } if (opts.description) { attributes.push(stringAttribute("description", opts.description)); } if (opts.name) { attributes.push(stringAttribute("name", opts.name)); } if (opts.path) { attributes.push(stringAttribute("path", opts.path)); } return { - type: "mdxJsxFlowElement", + type: kind === "flow" ? "mdxJsxFlowElement" : "mdxJsxTextElement", name: "TypeTable", attributes: attributes as MdxJsxFlowElement["attributes"], children: [], }; } - const replace = ( - parentChildren: Root["children"], + const replace = <TNode>( + parentChildren: TNode[], index: number, - replacement: MdxJsxFlowElement + replacement: TNode ) => { - parentChildren.splice( - index, - 1, - replacement as unknown as Root["children"][number] - ); + parentChildren.splice(index, 1, replacement); }; const extracted = extractTypeFromFile(path, name, overrideBasePath); + const kind = child.type === "mdxJsxFlowElement" ? "flow" : "text"; replace( parentChildren, index, buildTypeTableNode({ properties: extracted ?? {}, title, description, name, path, - }) + }, kind) );Add test coverage for both block-level and inline usages of
<ExtractedTypeTable>and<AutoTypeTable>to prevent regression.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/leadtype/src/remark/plugins/type-table-jsx.remark.ts` around lines 99 - 103, The replacement currently forces an mdxJsxFlowElement and masks that with an unsafe cast in parentChildren.splice (replacement as unknown as Root["children"][number]), which breaks mdast when the original node was an mdxJsxTextElement; modify the transformer that creates replacement nodes (the function producing replacement) to accept the original node kind (or the original node itself) and construct a replacement of the same kind (create an mdxJsxTextElement when input is mdxJsxTextElement, mdxJsxFlowElement when input is mdxJsxFlowElement) instead of casting, update parentChildren.splice to insert that correctly typed node, and add tests covering inline (phrasing) and block (flow) usages of <ExtractedTypeTable> and <AutoTypeTable> to prevent regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/leadtype/src/fumadocs/index.ts`:
- Around line 137-140: Summary: JSON.parse(raw) may produce non-object values
which are being assigned to data typed as LeadtypeFumadocsMetaData and can break
consumers. Fix: inside the try block, capture the parsed value (e.g., parsed =
JSON.parse(raw)) and validate its shape before assigning to data: ensure parsed
is a non-null object (and not an array) and optionally contains expected keys,
otherwise keep data as {} (and optionally log or throw). Reference symbols to
change: the variable data, the raw input, JSON.parse(raw), and the
LeadtypeFumadocsMetaData type.
---
Duplicate comments:
In `@packages/leadtype/src/remark/plugins/type-table-jsx.remark.ts`:
- Around line 99-103: The replacement currently forces an mdxJsxFlowElement and
masks that with an unsafe cast in parentChildren.splice (replacement as unknown
as Root["children"][number]), which breaks mdast when the original node was an
mdxJsxTextElement; modify the transformer that creates replacement nodes (the
function producing replacement) to accept the original node kind (or the
original node itself) and construct a replacement of the same kind (create an
mdxJsxTextElement when input is mdxJsxTextElement, mdxJsxFlowElement when input
is mdxJsxFlowElement) instead of casting, update parentChildren.splice to insert
that correctly typed node, and add tests covering inline (phrasing) and block
(flow) usages of <ExtractedTypeTable> and <AutoTypeTable> to prevent
regressions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: d0054611-7477-42be-b2b4-9a9a5828ca25
⛔ Files ignored due to path filters (3)
apps/example/src/generated/agent-readability.jsonis excluded by!**/generated/**apps/example/src/generated/docs-search-content.jsonis excluded by!**/generated/**apps/example/src/generated/docs-search-index.jsonis excluded by!**/generated/**
📒 Files selected for processing (8)
apps/example/scripts/docs-source-manifest.tsapps/example/src/routes/docs/$.tsxdocs/build/integrate-with-fumadocs.mdxpackages/leadtype/src/convert/convert.tspackages/leadtype/src/fumadocs/index.tspackages/leadtype/src/lint/schema.tspackages/leadtype/src/remark/plugins/type-table-jsx.remark.tspackages/leadtype/src/source/index.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Preferunknownoveranywhen the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions
Files:
packages/leadtype/src/lint/schema.tspackages/leadtype/src/source/index.tspackages/leadtype/src/convert/convert.tspackages/leadtype/src/fumadocs/index.tsapps/example/scripts/docs-source-manifest.tsapps/example/src/routes/docs/$.tsxpackages/leadtype/src/remark/plugins/type-table-jsx.remark.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Preferfor...ofloops over.forEach()and indexedforloops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Useconstby default,letonly when reassignment is needed, nevervar
Alwaysawaitpromises in async functions - don't forget to use the return value
Useasync/awaitsyntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Removeconsole.log,debugger, andalertstatements from production code
ThrowErrorobjects with descriptive messages, not strings or other values
Usetry-catchblocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Don't useeval()or assign directly todocument.cookie
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Use descriptive names for functions, variables, and types for meaningful naming
Add comments for complex logic, but prefer self-documenting code
Files:
packages/leadtype/src/lint/schema.tspackages/leadtype/src/source/index.tspackages/leadtype/src/convert/convert.tspackages/leadtype/src/fumadocs/index.tsapps/example/scripts/docs-source-manifest.tsapps/example/src/routes/docs/$.tsxpackages/leadtype/src/remark/plugins/type-table-jsx.remark.ts
**/index.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Avoid barrel files (index files that re-export everything)
Files:
packages/leadtype/src/source/index.tspackages/leadtype/src/fumadocs/index.ts
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{jsx,tsx}: Use function components over class components in React
Call hooks at the top level only, never conditionally
Specify all dependencies in hook dependency arrays correctly
Use thekeyprop for elements in iterables (prefer unique IDs over array indices)
Nest children between opening and closing tags instead of passing as props
Don't define components inside other components
AvoiddangerouslySetInnerHTMLunless absolutely necessary
Use proper image components (e.g., Next.js<Image>) over<img>tags
Use Next.js<Image>component for images
Usenext/heador App Router metadata API for head elements in Next.js
Use Server Components for async data fetching instead of async Client Components in Next.js
Use ref as a prop instead ofReact.forwardRefin React 19+
Files:
apps/example/src/routes/docs/$.tsx
**/*.{jsx,tsx,html}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{jsx,tsx,html}: Use semantic HTML and ARIA attributes for accessibility: provide meaningful alt text for images, use proper heading hierarchy, add labels for form inputs, include keyboard event handlers alongside mouse events, use semantic elements instead of divs with roles
Addrel="noopener"when usingtarget="_blank"on links
Files:
apps/example/src/routes/docs/$.tsx
🔇 Additional comments (7)
packages/leadtype/src/lint/schema.ts (1)
57-65: LGTM!packages/leadtype/src/convert/convert.ts (1)
7-7: LGTM!Also applies to: 227-233, 331-405
packages/leadtype/src/source/index.ts (1)
1-360: LGTM!apps/example/scripts/docs-source-manifest.ts (1)
1-64: LGTM!apps/example/src/routes/docs/$.tsx (1)
1-95: LGTM!docs/build/integrate-with-fumadocs.mdx (1)
1-150: LGTM!packages/leadtype/src/remark/plugins/type-table-jsx.remark.ts (1)
1-93: LGTM!Also applies to: 155-165
Applied (5 of 6):
- docs-source-manifest reference fixed: error fallback in docs/$.tsx
now points at `bun run pipeline:source-manifest` (the actual script
name in apps/example/package.json), not the non-existent
`docs-source-manifest`
- Nuxt snippet uses array spread `[...mdxSourcePlugins]` instead of
object spread `{ ...mdxSourcePlugins }` — same fix in both
docs/build/use-the-source-primitive.mdx and docs/reference/source.mdx.
Object spread on a PluggableList produced `{ "0": fn, ... }` which
@nuxtjs/mdc would reject
- Fumadocs server-component snippet aligned with the rest of the PR:
imports MDXRemote from `next-mdx-remote-client/rsc` (matching the
working apps/fumadocs-example app), adds the missing `mdxComponents`
import, calls `source.leadtype.loadPage()` (the actual adapter shape)
instead of bare `source.loadPage()`
- createDocsSource throws on duplicate slugs or urlPaths instead of
silently picking whichever file sorts first. Matches the existing
behavior of resolveDocsNavigation (which already errors on
collisions) so the source primitive is consistent
- readMetaFiles validates JSON.parse output is a plain object before
treating it as LeadtypeFumadocsMetaData — arrays/strings/numbers/null
now fall through to the default `{}` rather than being passed to
fumadocs as misshapen metadata
Skipped (1):
- fumadocs-example DocsLayout `tabs` prop placement: the pullfrog
feedback says `tabs` is a top-level prop, but fumadocs-ui@15.8.5's
installed `SidebarOptions` type explicitly includes
`tabs?: Option[] | GetSidebarTabsOptions | false`. The nested form
is correct for this version
There was a problem hiding this comment.
Important
Two of the second-round fixes ship docs snippets that won't work as written: the fumadocs server-component snippet calls loadPage on undefined, and the Nuxt snippet still mismatches @nuxtjs/mdc's expected remarkPlugins shape. Other changes in this round verified correct.
Reviewed the following changes:
- Pointed the catch-all error fallback at
pipeline:source-manifest(the actual script name) - Aligned the fumadocs server-component snippet with the working example app (
next-mdx-remote-client/rsc,mdxComponentsimport) - Switched the Nuxt
remarkPluginsspread from object-spread to array-spread in both the source-primitive walkthrough and source reference (still not the right shape — see comment) - Added duplicate-slug/urlPath rejection to
createDocsSourceso it mirrorsresolveDocsNavigation's collision behavior - Validated
JSON.parseoutput is a plain object inreadMetaFilesbefore treating it as fumadocs metadata
Claude Opus | 𝕏
| params: Promise<{ slug?: string[] }>; | ||
| }) { | ||
| const { slug } = await params; | ||
| const page = await source.leadtype.loadPage(slug ?? []); |
There was a problem hiding this comment.
source here is the result of fumadocs-core's loader({ source: fumadocsSource(...) }). loader() returns a fixed-shape LoaderOutput (getPage, getPages, pageTree, …) and does not preserve arbitrary properties from the input source, so source.leadtype is undefined and this line throws TypeError: Cannot read properties of undefined (reading 'loadPage') at runtime. The working apps/fumadocs-example/lib/source.ts:29 deliberately exports leadtypeSource = fumadocsSourceResult.leadtype from the pre-loader value — match that pattern here. The same defect exists at the TL;DR claim on line 66 and the search snippet on line 143 (outside this commit's diff, but worth fixing in the same pass).
|
|
||
| export default defineNuxtConfig({ | ||
| modules: ["@nuxtjs/mdc"], | ||
| mdc: { remarkPlugins: [...mdxSourcePlugins] }, |
There was a problem hiding this comment.
@nuxtjs/mdc types remarkPlugins as Record<string, UnistPlugin | false> keyed by plugin name (see nuxt-modules/mdc/src/types/module.ts), not a unified PluggableList. Spreading mdxSourcePlugins (an array of plugin functions) into either { ... } or [ ... ] won't produce that shape — the snippet still won't compile. Either drop the Nuxt section or restructure it so each plugin is registered under a name (e.g. { 'leadtype-include': { src: '…' }, … }). The same applies to docs/reference/source.mdx:168.
| export default defineNuxtConfig({ | ||
| modules: ["@nuxtjs/mdc"], | ||
| mdc: { | ||
| remarkPlugins: [...mdxSourcePlugins], |
There was a problem hiding this comment.
Same issue as use-the-source-primitive.mdx:115 — @nuxtjs/mdc.remarkPlugins is Record<string, UnistPlugin | false> keyed by plugin name, so an array spread of mdxSourcePlugins (PluggableList of bare functions) doesn't match. The fix from object-spread to array-spread improves PluggableList type alignment but still isn't what @nuxtjs/mdc accepts.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/leadtype/src/source/index.ts`:
- Around line 126-129: The JSDoc comment for the include-resolution option uses
the wrong name (`fromPath`) which doesn't match the implementation/API
(`fromDir`); update the JSDoc to mention `fromDir` (or alternatively rename the
API to `fromPath` if you prefer that external name) so the exported DocsSource
type and its include-resolution method signature are consistent; look for the
exported DocsSource type and the include resolve method in
packages/leadtype/src/source/index.ts and change the JSDoc option name from
`fromPath` to `fromDir` (or rename the corresponding option in the
implementation and type if you choose the `fromPath` spelling).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 11b73fac-384a-49d2-a8fc-2d06a8247479
⛔ Files ignored due to path filters (3)
apps/example/src/generated/agent-readability.jsonis excluded by!**/generated/**apps/example/src/generated/docs-search-content.jsonis excluded by!**/generated/**apps/example/src/generated/docs-search-index.jsonis excluded by!**/generated/**
📒 Files selected for processing (6)
apps/example/src/routes/docs/$.tsxdocs/build/integrate-with-fumadocs.mdxdocs/build/use-the-source-primitive.mdxdocs/reference/source.mdxpackages/leadtype/src/fumadocs/index.tspackages/leadtype/src/source/index.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Preferunknownoveranywhen the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions
Files:
apps/example/src/routes/docs/$.tsxpackages/leadtype/src/source/index.tspackages/leadtype/src/fumadocs/index.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Preferfor...ofloops over.forEach()and indexedforloops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Useconstby default,letonly when reassignment is needed, nevervar
Alwaysawaitpromises in async functions - don't forget to use the return value
Useasync/awaitsyntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Removeconsole.log,debugger, andalertstatements from production code
ThrowErrorobjects with descriptive messages, not strings or other values
Usetry-catchblocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Don't useeval()or assign directly todocument.cookie
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Use descriptive names for functions, variables, and types for meaningful naming
Add comments for complex logic, but prefer self-documenting code
Files:
apps/example/src/routes/docs/$.tsxpackages/leadtype/src/source/index.tspackages/leadtype/src/fumadocs/index.ts
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{jsx,tsx}: Use function components over class components in React
Call hooks at the top level only, never conditionally
Specify all dependencies in hook dependency arrays correctly
Use thekeyprop for elements in iterables (prefer unique IDs over array indices)
Nest children between opening and closing tags instead of passing as props
Don't define components inside other components
AvoiddangerouslySetInnerHTMLunless absolutely necessary
Use proper image components (e.g., Next.js<Image>) over<img>tags
Use Next.js<Image>component for images
Usenext/heador App Router metadata API for head elements in Next.js
Use Server Components for async data fetching instead of async Client Components in Next.js
Use ref as a prop instead ofReact.forwardRefin React 19+
Files:
apps/example/src/routes/docs/$.tsx
**/*.{jsx,tsx,html}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{jsx,tsx,html}: Use semantic HTML and ARIA attributes for accessibility: provide meaningful alt text for images, use proper heading hierarchy, add labels for form inputs, include keyboard event handlers alongside mouse events, use semantic elements instead of divs with roles
Addrel="noopener"when usingtarget="_blank"on links
Files:
apps/example/src/routes/docs/$.tsx
**/index.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Avoid barrel files (index files that re-export everything)
Files:
packages/leadtype/src/source/index.tspackages/leadtype/src/fumadocs/index.ts
🪛 LanguageTool
docs/reference/source.mdx
[style] ~9-~9: Consider using “who” when you are referring to a person instead of an object.
Context: ...-neutral** entry point for any consumer that wants to render leadtype-authored MDX i...
(THAT_WHO)
[style] ~96-~96: This phrase is redundant. Consider using “outside”.
Context: ...ontentDir`. Useful for loading partials outside of the MDX compile path. ## Choosing betw...
(OUTSIDE_OF)
🔇 Additional comments (5)
apps/example/src/routes/docs/$.tsx (1)
1-94: LGTM!packages/leadtype/src/fumadocs/index.ts (1)
1-165: LGTM!docs/build/integrate-with-fumadocs.mdx (1)
1-151: LGTM!docs/build/use-the-source-primitive.mdx (1)
1-239: LGTM!docs/reference/source.mdx (1)
1-198: LGTM!
| * Resolve an `<include>` reference outside of a remark pass (e.g. when | ||
| * loading a partial for direct rendering). `fromPath` defaults to | ||
| * `contentDir`. | ||
| */ |
There was a problem hiding this comment.
Fix JSDoc option name mismatch for include resolution.
The public comment says fromPath, but the API and implementation use fromDir. This can mislead consumers of the exported DocsSource type.
Suggested patch
- * loading a partial for direct rendering). `fromPath` defaults to
+ * loading a partial for direct rendering). `fromDir` defaults to
* `contentDir`.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| * Resolve an `<include>` reference outside of a remark pass (e.g. when | |
| * loading a partial for direct rendering). `fromPath` defaults to | |
| * `contentDir`. | |
| */ | |
| * Resolve an `<include>` reference outside of a remark pass (e.g. when | |
| * loading a partial for direct rendering). `fromDir` defaults to | |
| * `contentDir`. | |
| */ |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/leadtype/src/source/index.ts` around lines 126 - 129, The JSDoc
comment for the include-resolution option uses the wrong name (`fromPath`) which
doesn't match the implementation/API (`fromDir`); update the JSDoc to mention
`fromDir` (or alternatively rename the API to `fromPath` if you prefer that
external name) so the exported DocsSource type and its include-resolution method
signature are consistent; look for the exported DocsSource type and the include
resolve method in packages/leadtype/src/source/index.ts and change the JSDoc
option name from `fromPath` to `fromDir` (or rename the corresponding option in
the implementation and type if you choose the `fromPath` spelling).
Was missing from the multi-framework section even though apps/example itself runs on TanStack Start. Added the section in three places: - docs/build/use-the-source-primitive.mdx — new `### TanStack Start` section between Astro and Vite, with the vite.config.ts wiring (mdx + tanstackStart + viteReact) and a catch-all route snippet - docs/quickstart.mdx — new Card linking to the TanStack Start anchor - docs/reference/source.mdx — parallel TanStack Start snippet in the Framework integrations section, with a cross-link to the longer walkthrough for the catch-all pattern The TanStack Router catch-all snippet uses the same import.meta.glob + build-time manifest pattern that apps/example dogfoods, generalized so docs readers can reproduce it without reading the apps/ source.
There was a problem hiding this comment.
♻️ Duplicate comments (2)
docs/reference/source.mdx (1)
189-196:⚠️ Potential issue | 🟠 Major | ⚡ Quick winNuxt snippet likely uses the wrong
remarkPluginsshape.At Line 195,
mdc.remarkPlugins: [...mdxSourcePlugins]appears incompatible with@nuxtjs/mdc’s keyed plugin registration shape, so this example can fail for users copying it.For `@nuxtjs/mdc`, what is the exact expected type/shape for `mdc.remarkPlugins` in `defineNuxtConfig`, and how should unified remark plugins (function arrays like `PluggableList`) be registered?🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/reference/source.mdx` around lines 189 - 196, The example sets mdc.remarkPlugins to an array of functions (spread mdxSourcePlugins) which doesn't match `@nuxtjs/mdc`'s expected keyed plugin registration shape; update the snippet so that inside defineNuxtConfig you assign mdc.remarkPlugins as an object mapping a key (e.g., "default" or a filename) to the unified plugin list (PluggableList) or wrap the PluggableList in the expected { remarkPlugins: { "<key>": [...] } } shape; locate the mdxSourcePlugins usage and change the registration to the keyed form required by `@nuxtjs/mdc` so unified remark plugins are registered under a key rather than passed directly as top-level array.docs/build/use-the-source-primitive.mdx (1)
179-185:⚠️ Potential issue | 🟠 Major | ⚡ Quick winNuxt config repeats the same
remarkPluginsshape mismatch.Line 184 uses array spread for
mdc.remarkPlugins; this appears to conflict with Nuxt MDC’s expected keyed plugin config and can produce a non-working recipe.In current `@nuxtjs/mdc` docs/source, does `mdc.remarkPlugins` accept an array of plugin functions, or only an object keyed by plugin name? Please include a minimal valid `nuxt.config.ts` example.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/build/use-the-source-primitive.mdx` around lines 179 - 185, The Nuxt example incorrectly spreads mdxSourcePlugins into mdc.remarkPlugins (using mdxSourcePlugins and mdc.remarkPlugins) which mismatches `@nuxtjs/mdc`'s expected keyed-plugin config; update the docs to show the correct shape by replacing the array spread with an object keyed by plugin name(s) (e.g., map the imported mdxSourcePlugins to the plugin key expected by `@nuxtjs/mdc`) and provide a minimal valid nuxt.config.ts that uses defineNuxtConfig and imports mdxSourcePlugins, sets modules: ["@nuxtjs/mdc"], and assigns mdc.remarkPlugins to the proper keyed object form instead of [...mdxSourcePlugins].
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@docs/build/use-the-source-primitive.mdx`:
- Around line 179-185: The Nuxt example incorrectly spreads mdxSourcePlugins
into mdc.remarkPlugins (using mdxSourcePlugins and mdc.remarkPlugins) which
mismatches `@nuxtjs/mdc`'s expected keyed-plugin config; update the docs to show
the correct shape by replacing the array spread with an object keyed by plugin
name(s) (e.g., map the imported mdxSourcePlugins to the plugin key expected by
`@nuxtjs/mdc`) and provide a minimal valid nuxt.config.ts that uses
defineNuxtConfig and imports mdxSourcePlugins, sets modules: ["@nuxtjs/mdc"],
and assigns mdc.remarkPlugins to the proper keyed object form instead of
[...mdxSourcePlugins].
In `@docs/reference/source.mdx`:
- Around line 189-196: The example sets mdc.remarkPlugins to an array of
functions (spread mdxSourcePlugins) which doesn't match `@nuxtjs/mdc`'s expected
keyed plugin registration shape; update the snippet so that inside
defineNuxtConfig you assign mdc.remarkPlugins as an object mapping a key (e.g.,
"default" or a filename) to the unified plugin list (PluggableList) or wrap the
PluggableList in the expected { remarkPlugins: { "<key>": [...] } } shape;
locate the mdxSourcePlugins usage and change the registration to the keyed form
required by `@nuxtjs/mdc` so unified remark plugins are registered under a key
rather than passed directly as top-level array.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2589a7ca-fbef-460f-80f3-c017c6f7224e
⛔ Files ignored due to path filters (4)
apps/example/src/generated/agent-readability.jsonis excluded by!**/generated/**apps/example/src/generated/docs-nav.jsonis excluded by!**/generated/**apps/example/src/generated/docs-search-content.jsonis excluded by!**/generated/**apps/example/src/generated/docs-search-index.jsonis excluded by!**/generated/**
📒 Files selected for processing (3)
docs/build/use-the-source-primitive.mdxdocs/quickstart.mdxdocs/reference/source.mdx
📜 Review details
🧰 Additional context used
🪛 LanguageTool
docs/reference/source.mdx
[style] ~9-~9: Consider using “who” when you are referring to a person instead of an object.
Context: ...-neutral** entry point for any consumer that wants to render leadtype-authored MDX i...
(THAT_WHO)
[style] ~96-~96: This phrase is redundant. Consider using “outside”.
Context: ...ontentDir`. Useful for loading partials outside of the MDX compile path. ## Choosing betw...
(OUTSIDE_OF)
🔇 Additional comments (1)
docs/quickstart.mdx (1)
3-3: LGTM!Also applies to: 9-10, 17-110
There was a problem hiding this comment.
Reviewed the following changes:
- Added a TanStack Start recipe to
use-the-source-primitive.mdx(vite.config.ts wiring + catch-allsrc/routes/docs/$.tsx). - Duplicated the TanStack Start vite.config.ts into
reference/source.mdxwith a link back to the walkthrough. - Added a TanStack Start Card to the Quickstart "Pick the recipe" grid.
- Regenerated
apps/exampleagent-readability, docs-nav, and docs-search artifacts to pick up the new section.
Claude Opus | 𝕏
| if (!page) { | ||
| return null; | ||
| } | ||
| const Mdx = lazy(mdxModules[page.globKey]); |
There was a problem hiding this comment.
Calling lazy() in the render body creates a fresh lazy component on every render, defeating Suspense caching and triggering remount loops on parent re-renders. React's lazy is documented to be called at module scope or memoized (https://react.dev/reference/react/lazy#caveats). The working reference at apps/example/src/routes/docs/$.tsx:81 wraps this in useMemo keyed by page.globKey and also guards mdxModules[page.globKey] being undefined — worth mirroring here since this snippet is meant to be copy-pasted.
The snippet I added in a194396 called lazy(mdxModules[page.globKey]) inline in the render body. React's lazy() is documented to be called at module scope or memoized — calling it inline returns a fresh lazy component on every render, busts Suspense's cache, and remounts the MDX page on every parent re-render. Also added a guard for mdxModules[page.globKey] being undefined (stale manifest, file deleted between builds), matching the working pattern in apps/example's catch-all. Since this snippet is meant to be copy- pasted, it needs to mirror the production-ready shape rather than the shortest-possible one. Wraps the lazy() call in useMemo keyed by [page.globKey, page.urlPath] and adds a MissingMdxModule fallback for the unresolved-loader case.
There was a problem hiding this comment.
No new issues. Reviewed the following changes:
- Memoized the TanStack catch-all's
lazy()call indocs/build/use-the-source-primitive.mdx, addressing the prior round's MINOR finding — the snippet now mirrors the working reference atapps/example/src/routes/docs/$.tsx(typedDocsPage,resolvePagehelper,useMemokeyed byglobKey/urlPath,MissingMdxModulefallback when the glob is stale). - Regenerated
apps/example/src/generated/{agent-readability,docs-search-content,docs-search-index}.jsonto reflect the updated snippet.
Prior open findings on docs/build/integrate-with-fumadocs.mdx (source.leadtype.loadPage() on a fumadocs loader result) and Nuxt remarkPlugins: [...mdxSourcePlugins] typing in use-the-source-primitive.mdx / reference/source.mdx are not touched by this commit.
false
Claude Opus | 𝕏
There was a problem hiding this comment.
♻️ Duplicate comments (1)
docs/build/use-the-source-primitive.mdx (1)
204-210:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winVerify the
@nuxtjs/mdcremarkPluginsconfiguration.Previous review identified that
@nuxtjs/mdctypesremarkPluginsasRecord<string, UnistPlugin | false>keyed by plugin name, not as aPluggableListarray. SpreadingmdxSourcePlugins(an array) into[...]won't produce the expected object shape{ 'plugin-name': { src: '...' }, ... }.Either restructure the snippet so each plugin is registered under a name, or consider removing the Nuxt example until the correct API shape is confirmed.
Run the following script to verify the
@nuxtjs/mdctype signature:#!/bin/bash # Description: Check `@nuxtjs/mdc` remarkPlugins type definition # Search for the type definition in node_modules if `@nuxtjs/mdc` is installed fd -t f "module.ts|types.ts" node_modules/@nuxtjs/mdc -x cat {} | rg -A5 -B5 "remarkPlugins" # If not installed locally, check the npm registry metadata echo "---" echo "Checking npm package info:" npm view `@nuxtjs/mdc` repository.urlAlternatively, search for the official
@nuxtjs/mdcdocumentation on the correctremarkPluginsformat:`@nuxtjs/mdc` remarkPlugins configuration format🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/build/use-the-source-primitive.mdx` around lines 204 - 210, The Nuxt snippet incorrectly spreads the array mdxSourcePlugins into mdc.remarkPlugins which expects a Record keyed by plugin name (not a PluggableList); update the nuxt.config.ts example to register each plugin under a name (e.g., build an object like { "plugin-name": pluginConfig, ... } derived from mdxSourcePlugins) or remove the `@nuxtjs/mdc` example until you confirm the API shape; locate the config in the file where mdxSourcePlugins and mdc.remarkPlugins are referenced and either transform the array into the required named object mapping or delete the block and add a comment to reintroduce it after verifying the `@nuxtjs/mdc` remarkPlugins type signature.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@docs/build/use-the-source-primitive.mdx`:
- Around line 204-210: The Nuxt snippet incorrectly spreads the array
mdxSourcePlugins into mdc.remarkPlugins which expects a Record keyed by plugin
name (not a PluggableList); update the nuxt.config.ts example to register each
plugin under a name (e.g., build an object like { "plugin-name": pluginConfig,
... } derived from mdxSourcePlugins) or remove the `@nuxtjs/mdc` example until you
confirm the API shape; locate the config in the file where mdxSourcePlugins and
mdc.remarkPlugins are referenced and either transform the array into the
required named object mapping or delete the block and add a comment to
reintroduce it after verifying the `@nuxtjs/mdc` remarkPlugins type signature.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 8c3e006a-21d7-4a3b-9d86-9c56f1668be5
⛔ Files ignored due to path filters (3)
apps/example/src/generated/agent-readability.jsonis excluded by!**/generated/**apps/example/src/generated/docs-search-content.jsonis excluded by!**/generated/**apps/example/src/generated/docs-search-index.jsonis excluded by!**/generated/**
📒 Files selected for processing (1)
docs/build/use-the-source-primitive.mdx
📜 Review details
🔇 Additional comments (7)
docs/build/use-the-source-primitive.mdx (7)
1-27: LGTM!
42-78: LGTM!
80-186: LGTM!
188-200: LGTM!
213-223: LGTM!
225-319: LGTM!
321-333: LGTM!

Summary
createDocsSource()at the root, plus newleadtype/mdx(tag prop contracts +mdxSourcePluginssource preset) andleadtype/fumadocs(first-party Source adapter) subpaths.title="..."fence meta on every code block, kills the overlappingconnect-docs-site+render-mdx-and-tocpages.<br/>was getting mangled in agent-flattened markdown across two passes, breaking downstream renderers.New public API
createDocsSource({ contentDir })(root)leadtype/mdxmdxSourcePluginspreset + tag prop types (CalloutProps,TabsProps, …) + include helpersleadtype/fumadocscreateDocsSource()to fumadocs-core'sSourceinterface, walks pages +meta.jsonconvertMdxFile(path, plugins)(fromleadtype/convert){ ast, frontmatter, data, markdown }resolveInclude/parseIncludeSpecifier/extractMdxSection(fromleadtype/mdx)remarkResolveTypeTableJsxorder:frontmatter fieldresolveDocsNavigation({ docsDirName })docs/foldersBug fixes
<br/>newline-in-label syntax is preserved in agent-flattened markdown — was being mangled to/and-in two separate passes (mermaid.remark.tsandconvert.ts:compactMermaidBlocks), which broke any mermaid renderer downstream.chart={`flowchart LR\n…`}form, not just backticks-on-their-own-lines.Docs polish
createDocsSource()→ "Plug it into your framework" cards linking to per-framework anchors)./docs/build/use-the-source-primitiverestructured as a multi-framework umbrella with anchor-linkable sections for Next App Router, Astro Content Collections, Vite +@mdx-js/rollup, Nuxt, SvelteKit +mdsvex.use-the-source-primitiveandintegrate-with-fumadocsso readers in a hurry can copy-paste the minimum before scrolling into the full walkthroughs.connect-docs-site.mdx+render-mdx-and-toc.mdx; their content lives in the newbuild-a-docs-site(overview),use-the-source-primitive,generate-static-artifacts, andreference/mdx/reference/sourcepages.// filenamecode-block comments converted totitle="..."fence meta. Renders as a title bar inrehype-code; agent flattening preserves the meta string.apps/example/removed from docs (docs stand alone now).use-the-source-primitiveadded tobestStartingPointsindocs/docs.config.tsso it surfaces inllms.txt.Example app refactor
apps/exampleswitched from 18 hand-rolled TanStack route files to a single[[...slug]]catch-all powered by a build-timecreateDocsSource()manifest. Adds apipeline:source-manifestscript.apps/fumadocs-exampledemonstratesleadtype/fumadocsend-to-end with c15t docs as the content fixture — Next.js 15 + fumadocs-ui 15, with rehypeCode syntax highlighting, top tabs (Frontend / Integrations / Self Host), framework switcher, and tailwind v4.Test plan
bun --filter leadtype test— 179 / 179 passing (154 baseline + 25 new for include resolver, convert in-memory, source primitive)bun --filter leadtype check-types— cleanbun --filter leadtype build— clean, 23 docs convertedbun --filter example build— clean, catch-all resolves every pagebun --filter fumadocs-example build— 216 static pages generated from c15t docsgrep -rn "apps/example" docs/— zero matchesgrep -rnE '^// [a-z][a-z0-9./_-]*\.(mjs|ts|tsx|js|json|mdx)'in docs — zero matchesidheadings inuse-the-source-primitiveapps/example/public/docs/build/build-a-docs-site.md— valid mermaid syntax with<br/>preserved🤖 Generated with Claude Code