diff --git a/.changeset/v1-public-surface-and-docs-polish.md b/.changeset/v1-public-surface-and-docs-polish.md new file mode 100644 index 0000000..5534577 --- /dev/null +++ b/.changeset/v1-public-surface-and-docs-polish.md @@ -0,0 +1,30 @@ +--- +"leadtype": minor +--- + +Ship the v1 headless integration surface plus docs polish. + +**New public API** + +- `createDocsSource({ contentDir })` at the root — framework-neutral docs source primitive returning navigation, page loading, search index, and standalone include resolution. Works with Next App Router, Astro, Vite + Vue/Solid/Svelte, Nuxt, SvelteKit, and any MDX-aware bundler. +- `leadtype/mdx` subpath — typed prop contracts for every custom MDX tag (`CalloutProps`, `TabsProps`, `StepProps`, `TypeTableProps`, …) plus `mdxSourcePlugins` (a remark preset that expands `` and resolves `` at build time while leaving custom tags as JSX). Framework-neutral — `children` is typed as `unknown` so consumers intersect with React/Vue/Svelte/Solid/Astro child types. +- `leadtype/fumadocs` subpath — thin adapter mapping `createDocsSource()` to fumadocs-core's `Source` interface, including `meta.json` walking. `fumadocs-core >= 15` is an optional peer dependency. + +**New helpers** + +- `convertMdxFile(path, plugins)` from `leadtype/convert` — returns `{ ast, frontmatter, data, markdown }` in memory for a single MDX file. +- `resolveInclude(specifier, options)` from `leadtype/mdx` — standalone include resolver, no remark transform required. Plus `parseIncludeSpecifier`, `extractMdxSection`, `resolveIncludePath`. +- `remarkResolveTypeTableJsx` — source-preset variant of the type-table plugin that emits `` JSX (vs. the existing markdown-flattening variant). + +**Frontmatter contract** + +- New optional `order:` field. Pages with explicit order sort first within their group (ascending); pages without `order` fall back to alphabetical urlPath ordering. Conventionally numbered in tens for insertion room. + +**Navigation** + +- `resolveDocsNavigation` accepts an optional `docsDirName` config field (defaults to `"docs"`) for projects whose docs folder isn't named `docs/`. + +**Bug fixes** + +- Mermaid blocks in agent-flattened markdown no longer destroy `
` line-break syntax (was being replaced with ` / ` and ` - ` in two passes). Mermaid renderers now receive valid syntax with multi-line node labels intact. +- The mermaid plugin's outer-backtick stripper now handles the common `` chart={`flowchart LR\n...`} `` inline form, not just backticks-on-their-own-lines. diff --git a/apps/example/package.json b/apps/example/package.json index 460e1aa..21bc34f 100644 --- a/apps/example/package.json +++ b/apps/example/package.json @@ -17,7 +17,8 @@ "pipeline:convert": "bun run scripts/mdx-convert.ts", "pipeline:llm": "bun run scripts/llm-generate.ts", "pipeline:search": "bun run scripts/search-generate.ts", - "pipeline:build": "bun run pipeline:convert && bun run pipeline:llm && bun run pipeline:search", + "pipeline:source-manifest": "bun run scripts/docs-source-manifest.ts", + "pipeline:build": "bun run pipeline:convert && bun run pipeline:llm && bun run pipeline:search && bun run pipeline:source-manifest", "pipeline:test": "bun run scripts/test-pipeline.ts", "pipeline:setup-real": "bun run scripts/setup-real-content.ts", "pipeline:test-real": "bun run pipeline:setup-real && bun run scripts/test-real.ts", diff --git a/apps/example/scripts/docs-source-manifest.ts b/apps/example/scripts/docs-source-manifest.ts new file mode 100644 index 0000000..73b1bb8 --- /dev/null +++ b/apps/example/scripts/docs-source-manifest.ts @@ -0,0 +1,64 @@ +#!/usr/bin/env bun +/** + * Build-time dogfood of `createDocsSource()` — the v1 source primitive. + * + * Writes `src/generated/docs-pages.json` with `{ slug, urlPath, title, + * description, relativePath, extension }` for every page in `/docs`. The + * catch-all docs route uses this manifest to wire slugs → MDX modules + * without hand-rolling one route file per page. + * + * The companion `llm-generate.ts` script still emits the on-disk + * `llms.txt` / `agent-readability.json` artifacts via the older composed + * APIs. The two paths coexist deliberately — they demonstrate both + * integration shapes from `/docs/build/build-a-docs-site`. + */ + +import { mkdir, writeFile } from "node:fs/promises"; +import { dirname, join, sep as platformSep, relative } from "node:path"; +import { fileURLToPath } from "node:url"; +import { createDocsSource } from "leadtype"; +import docsConfig from "../../../docs/docs.config"; + +/** `import.meta.glob` keys are always POSIX even on Windows. */ +function toPosix(input: string): string { + return platformSep === "/" ? input : input.replaceAll(platformSep, "/"); +} + +const scriptsRoot = dirname(fileURLToPath(import.meta.url)); +const appRoot = join(scriptsRoot, ".."); +const repoRoot = join(appRoot, "..", ".."); +const contentDir = join(repoRoot, "docs"); +const generatedDir = join(appRoot, "src", "generated"); +const manifestPath = join(generatedDir, "docs-pages.json"); + +const source = await createDocsSource({ + contentDir, + baseUrl: process.env.BASE_URL?.trim() || "https://leadtype.dev", + groups: docsConfig.groups, +}); + +const pages = await source.listPages(); + +const manifest = pages.map((page) => ({ + slug: page.slug, + urlPath: page.urlPath, + title: page.title, + description: page.description, + relativePath: page.relativePath, + extension: page.extension, + groups: page.groups, + // 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 with POSIX separators so the key matches the + // glob output on every platform (Windows otherwise emits backslashes). + globKey: toPosix( + `${relative(join(appRoot, "src", "routes", "docs"), join(contentDir, page.relativePath))}${page.extension}` + ), +})); + +await mkdir(generatedDir, { recursive: true }); +await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`); + +process.stdout.write( + `Wrote ${manifest.length} pages to ${relative(repoRoot, manifestPath)}\n` +); diff --git a/apps/example/src/components/docs-mdx/callout.tsx b/apps/example/src/components/docs-mdx/callout.tsx index 4bc4566..226a2f0 100644 --- a/apps/example/src/components/docs-mdx/callout.tsx +++ b/apps/example/src/components/docs-mdx/callout.tsx @@ -1,29 +1,16 @@ +import type { + CalloutTypeAlias, + CalloutVariant, + CalloutProps as LeadtypeCalloutProps, +} from "leadtype/mdx"; import type { HTMLAttributes, ReactNode } from "react"; -export type CalloutVariant = - | "info" - | "note" - | "tip" - | "warning" - | "success" - | "error" - | "canary" - | "deprecated" - | "experimental"; +export type { CalloutTypeAlias, CalloutVariant } from "leadtype/mdx"; -/** - * Aliases accepted by the deprecated `type` prop. Mirrors `CalloutVariant` - * but also accepts `"warn"` (Fumadocs-style) which maps to `"warning"`. - */ -export type CalloutTypeAlias = CalloutVariant | "warn"; - -export type CalloutProps = HTMLAttributes & { - variant?: CalloutVariant; - /** @deprecated Use `variant` instead. Kept for Fumadocs-authored MDX compatibility. */ - type?: CalloutTypeAlias; - title?: string; - children?: ReactNode; -}; +export type CalloutProps = Omit & + HTMLAttributes & { + children?: ReactNode; + }; function normalizeVariant( variant: CalloutVariant | undefined, diff --git a/apps/example/src/generated/agent-readability.json b/apps/example/src/generated/agent-readability.json index 2652084..135a041 100644 --- a/apps/example/src/generated/agent-readability.json +++ b/apps/example/src/generated/agent-readability.json @@ -1,6 +1,6 @@ { "version": 1, - "generatedAt": "2026-05-11T23:20:44.255Z", + "generatedAt": "2026-05-13T16:53:52.978Z", "baseUrl": "https://leadtype.dev", "product": { "name": "Leadtype", @@ -18,7 +18,7 @@ "groups": [ "get-started" ], - "lastModified": "2026-05-11T17:53:02.000Z" + "lastModified": "2026-05-13T07:19:12.000Z" }, { "title": "Components", @@ -31,7 +31,7 @@ "groups": [ "authoring" ], - "lastModified": "2026-05-11T17:53:02.000Z" + "lastModified": "2026-05-13T07:19:12.000Z" }, { "title": "Frontmatter", @@ -44,7 +44,7 @@ "groups": [ "authoring" ], - "lastModified": "2026-05-11T17:53:02.000Z" + "lastModified": "2026-05-13T07:19:12.000Z" }, { "title": "Add search", @@ -57,20 +57,46 @@ "groups": [ "docs-site" ], - "lastModified": "2026-05-11T17:53:02.000Z" + "lastModified": "2026-05-13T07:19:12.000Z" }, { - "title": "Connect a docs site", - "description": "Build a shared docs app from source docs that live with the code they document.", - "urlPath": "/docs/build/connect-docs-site", - "absoluteUrl": "https://leadtype.dev/docs/build/connect-docs-site", - "markdownUrlPath": "/docs/build/connect-docs-site.md", - "markdownAbsoluteUrl": "https://leadtype.dev/docs/build/connect-docs-site.md", - "relativePath": "build/connect-docs-site", + "title": "Build a docs site", + "description": "Pick the right leadtype integration shape for your docs app, and what each path gives you.", + "urlPath": "/docs/build/build-a-docs-site", + "absoluteUrl": "https://leadtype.dev/docs/build/build-a-docs-site", + "markdownUrlPath": "/docs/build/build-a-docs-site.md", + "markdownAbsoluteUrl": "https://leadtype.dev/docs/build/build-a-docs-site.md", + "relativePath": "build/build-a-docs-site", "groups": [ "docs-site" ], - "lastModified": "2026-05-11T17:53:02.000Z" + "lastModified": "2026-05-13T07:19:12.000Z" + }, + { + "title": "Generate static artifacts", + "description": "Run leadtype generate from your build pipeline to write llms.txt, markdown mirrors, search index, sitemap, and agent-readability files to disk.", + "urlPath": "/docs/build/generate-static-artifacts", + "absoluteUrl": "https://leadtype.dev/docs/build/generate-static-artifacts", + "markdownUrlPath": "/docs/build/generate-static-artifacts.md", + "markdownAbsoluteUrl": "https://leadtype.dev/docs/build/generate-static-artifacts.md", + "relativePath": "build/generate-static-artifacts", + "groups": [ + "docs-site" + ], + "lastModified": "2026-05-13T07:19:12.000Z" + }, + { + "title": "Integrate with Fumadocs", + "description": "Wire leadtype's content layer into a fumadocs app for nav, search, and includes.", + "urlPath": "/docs/build/integrate-with-fumadocs", + "absoluteUrl": "https://leadtype.dev/docs/build/integrate-with-fumadocs", + "markdownUrlPath": "/docs/build/integrate-with-fumadocs.md", + "markdownAbsoluteUrl": "https://leadtype.dev/docs/build/integrate-with-fumadocs.md", + "relativePath": "build/integrate-with-fumadocs", + "groups": [ + "docs-site" + ], + "lastModified": "2026-05-13T16:32:01.000Z" }, { "title": "Optimize docs for agents", @@ -83,20 +109,20 @@ "groups": [ "docs-site" ], - "lastModified": "2026-05-11T17:53:02.000Z" + "lastModified": "2026-05-13T07:19:12.000Z" }, { - "title": "Render MDX and TOC", - "description": "Set up runtime MDX components, stable heading IDs, and a table of contents from the generated navigation manifest.", - "urlPath": "/docs/build/render-mdx-and-toc", - "absoluteUrl": "https://leadtype.dev/docs/build/render-mdx-and-toc", - "markdownUrlPath": "/docs/build/render-mdx-and-toc.md", - "markdownAbsoluteUrl": "https://leadtype.dev/docs/build/render-mdx-and-toc.md", - "relativePath": "build/render-mdx-and-toc", + "title": "Use the source primitive", + "description": "Wire createDocsSource into Next, Astro, Vite, Nuxt, SvelteKit, or any MDX-aware bundler. Same primitive, multiple host shapes.", + "urlPath": "/docs/build/use-the-source-primitive", + "absoluteUrl": "https://leadtype.dev/docs/build/use-the-source-primitive", + "markdownUrlPath": "/docs/build/use-the-source-primitive.md", + "markdownAbsoluteUrl": "https://leadtype.dev/docs/build/use-the-source-primitive.md", + "relativePath": "build/use-the-source-primitive", "groups": [ "docs-site" ], - "lastModified": "2026-05-11T17:53:02.000Z" + "lastModified": "2026-05-13T16:48:26.000Z" }, { "title": "Validate in CI", @@ -109,7 +135,7 @@ "groups": [ "docs-site" ], - "lastModified": "2026-05-11T17:53:02.000Z" + "lastModified": "2026-05-13T07:19:12.000Z" }, { "title": "How it works", @@ -135,7 +161,7 @@ "groups": [ "get-started" ], - "lastModified": "2026-05-11T17:53:02.000Z" + "lastModified": "2026-05-13T07:19:12.000Z" }, { "title": "Bundle docs into a package", @@ -148,11 +174,11 @@ "groups": [ "package-docs" ], - "lastModified": "2026-05-11T17:53:02.000Z" + "lastModified": "2026-05-13T07:19:12.000Z" }, { "title": "Quickstart", - "description": "Install leadtype, run the hosted-docs pipeline once, and inspect the generated artifacts.", + "description": "Install leadtype, author your first MDX page, and create a source. Then plug it into whatever framework you're using.", "urlPath": "/docs/quickstart", "absoluteUrl": "https://leadtype.dev/docs/quickstart", "markdownUrlPath": "/docs/quickstart.md", @@ -161,7 +187,7 @@ "groups": [ "get-started" ], - "lastModified": "2026-05-11T17:53:02.000Z" + "lastModified": "2026-05-13T16:48:26.000Z" }, { "title": "CLI", @@ -174,7 +200,7 @@ "groups": [ "reference" ], - "lastModified": "2026-05-11T17:53:02.000Z" + "lastModified": "2026-05-12T02:59:25.000Z" }, { "title": "Convert", @@ -187,7 +213,7 @@ "groups": [ "reference" ], - "lastModified": "2026-05-09T22:38:26.000Z" + "lastModified": "2026-05-12T02:59:25.000Z" }, { "title": "Evals", @@ -200,7 +226,7 @@ "groups": [ "reference" ], - "lastModified": "2026-05-11T17:53:02.000Z" + "lastModified": "2026-05-13T07:19:12.000Z" }, { "title": "Lint rules", @@ -226,7 +252,20 @@ "groups": [ "reference" ], - "lastModified": "2026-05-11T17:53:02.000Z" + "lastModified": "2026-05-13T07:19:12.000Z" + }, + { + "title": "leadtype/mdx", + "description": "Tag type contracts and the build-time source preset for consumers rendering MDX themselves.", + "urlPath": "/docs/reference/mdx", + "absoluteUrl": "https://leadtype.dev/docs/reference/mdx", + "markdownUrlPath": "/docs/reference/mdx.md", + "markdownAbsoluteUrl": "https://leadtype.dev/docs/reference/mdx.md", + "relativePath": "reference/mdx", + "groups": [ + "reference" + ], + "lastModified": "2026-05-13T07:19:12.000Z" }, { "title": "Remark plugins", @@ -239,7 +278,7 @@ "groups": [ "reference" ], - "lastModified": "2026-05-11T01:01:43.000Z" + "lastModified": "2026-05-12T02:59:25.000Z" }, { "title": "Search", @@ -252,7 +291,20 @@ "groups": [ "reference" ], - "lastModified": "2026-05-10T20:56:41.000Z" + "lastModified": "2026-05-12T02:59:25.000Z" + }, + { + "title": "createDocsSource", + "description": "Framework-neutral docs source primitive — navigation, page loader, search index, and include resolver.", + "urlPath": "/docs/reference/source", + "absoluteUrl": "https://leadtype.dev/docs/reference/source", + "markdownUrlPath": "/docs/reference/source.md", + "markdownAbsoluteUrl": "https://leadtype.dev/docs/reference/source.md", + "relativePath": "reference/source", + "groups": [ + "reference" + ], + "lastModified": "2026-05-13T16:48:26.000Z" } ], "navigation": { @@ -274,21 +326,21 @@ ], "toc": [ { - "id": "choose-your-path", - "title": "Choose your path", + "id": "start-here", + "title": "Start here", "level": 2, "urlPath": "/docs", - "urlWithHash": "/docs#choose-your-path", - "absoluteUrlWithHash": "https://leadtype.dev/docs#choose-your-path", + "urlWithHash": "/docs#start-here", + "absoluteUrlWithHash": "https://leadtype.dev/docs#start-here", "children": [] }, { - "id": "what-you-get", - "title": "What you get", + "id": "what-leadtype-produces", + "title": "What leadtype produces", "level": 2, "urlPath": "/docs", - "urlWithHash": "/docs#what-you-get", - "absoluteUrlWithHash": "https://leadtype.dev/docs#what-you-get", + "urlWithHash": "/docs#what-leadtype-produces", + "absoluteUrlWithHash": "https://leadtype.dev/docs#what-leadtype-produces", "children": [] }, { @@ -424,7 +476,7 @@ { "urlPath": "/docs/quickstart", "title": "Quickstart", - "description": "Install leadtype, run the hosted-docs pipeline once, and inspect the generated artifacts.", + "description": "Install leadtype, author your first MDX page, and create a source. Then plug it into whatever framework you're using.", "groups": [ "get-started" ], @@ -448,21 +500,39 @@ "children": [] }, { - "id": "generate-hosted-docs-output", - "title": "Generate hosted docs output", + "id": "create-the-source", + "title": "Create the source", "level": 2, "urlPath": "/docs/quickstart", - "urlWithHash": "/docs/quickstart#generate-hosted-docs-output", - "absoluteUrlWithHash": "https://leadtype.dev/docs/quickstart#generate-hosted-docs-output", + "urlWithHash": "/docs/quickstart#create-the-source", + "absoluteUrlWithHash": "https://leadtype.dev/docs/quickstart#create-the-source", "children": [] }, { - "id": "choose-the-next-setup-step", - "title": "Choose the next setup step", + "id": "plug-it-into-your-framework", + "title": "Plug it into your framework", "level": 2, "urlPath": "/docs/quickstart", - "urlWithHash": "/docs/quickstart#choose-the-next-setup-step", - "absoluteUrlWithHash": "https://leadtype.dev/docs/quickstart#choose-the-next-setup-step", + "urlWithHash": "/docs/quickstart#plug-it-into-your-framework", + "absoluteUrlWithHash": "https://leadtype.dev/docs/quickstart#plug-it-into-your-framework", + "children": [] + }, + { + "id": "or-write-static-artifacts-to-disk", + "title": "Or: write static artifacts to disk", + "level": 2, + "urlPath": "/docs/quickstart", + "urlWithHash": "/docs/quickstart#or-write-static-artifacts-to-disk", + "absoluteUrlWithHash": "https://leadtype.dev/docs/quickstart#or-write-static-artifacts-to-disk", + "children": [] + }, + { + "id": "next-steps", + "title": "Next steps", + "level": 2, + "urlPath": "/docs/quickstart", + "urlWithHash": "/docs/quickstart#next-steps", + "absoluteUrlWithHash": "https://leadtype.dev/docs/quickstart#next-steps", "children": [] } ] @@ -504,6 +574,15 @@ "absoluteUrlWithHash": "https://leadtype.dev/docs/authoring/components#the-naming-contract", "children": [] }, + { + "id": "reuse-shared-content", + "title": "Reuse shared content", + "level": 2, + "urlPath": "/docs/authoring/components", + "urlWithHash": "/docs/authoring/components#reuse-shared-content", + "absoluteUrlWithHash": "https://leadtype.dev/docs/authoring/components#reuse-shared-content", + "children": [] + }, { "id": "component-reference", "title": "Component reference", @@ -783,9 +862,64 @@ ] }, { - "urlPath": "/docs/build/connect-docs-site", - "title": "Connect a docs site", - "description": "Build a shared docs app from source docs that live with the code they document.", + "urlPath": "/docs/build/build-a-docs-site", + "title": "Build a docs site", + "description": "Pick the right leadtype integration shape for your docs app, and what each path gives you.", + "groups": [ + "docs-site" + ], + "toc": [ + { + "id": "pick-your-path", + "title": "Pick your path", + "level": 2, + "urlPath": "/docs/build/build-a-docs-site", + "urlWithHash": "/docs/build/build-a-docs-site#pick-your-path", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/build-a-docs-site#pick-your-path", + "children": [] + }, + { + "id": "source-primitive-the-common-path", + "title": "Source primitive: the common path", + "level": 2, + "urlPath": "/docs/build/build-a-docs-site", + "urlWithHash": "/docs/build/build-a-docs-site#source-primitive-the-common-path", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/build-a-docs-site#source-primitive-the-common-path", + "children": [] + }, + { + "id": "static-artifacts-the-cli-path", + "title": "Static artifacts: the CLI path", + "level": 2, + "urlPath": "/docs/build/build-a-docs-site", + "urlWithHash": "/docs/build/build-a-docs-site#static-artifacts-the-cli-path", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/build-a-docs-site#static-artifacts-the-cli-path", + "children": [] + }, + { + "id": "add-the-cross-cutting-features", + "title": "Add the cross-cutting features", + "level": 2, + "urlPath": "/docs/build/build-a-docs-site", + "urlWithHash": "/docs/build/build-a-docs-site#add-the-cross-cutting-features", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/build-a-docs-site#add-the-cross-cutting-features", + "children": [] + }, + { + "id": "configure-product-and-groups", + "title": "Configure product and groups", + "level": 2, + "urlPath": "/docs/build/build-a-docs-site", + "urlWithHash": "/docs/build/build-a-docs-site#configure-product-and-groups", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/build-a-docs-site#configure-product-and-groups", + "children": [] + } + ] + }, + { + "urlPath": "/docs/build/generate-static-artifacts", + "title": "Generate static artifacts", + "description": "Run leadtype generate from your build pipeline to write llms.txt, markdown mirrors, search index, sitemap, and agent-readability files to disk.", "groups": [ "docs-site" ], @@ -794,81 +928,145 @@ "id": "the-flow", "title": "The flow", "level": 2, - "urlPath": "/docs/build/connect-docs-site", - "urlWithHash": "/docs/build/connect-docs-site#the-flow", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/connect-docs-site#the-flow", + "urlPath": "/docs/build/generate-static-artifacts", + "urlWithHash": "/docs/build/generate-static-artifacts#the-flow", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/generate-static-artifacts#the-flow", "children": [] }, { "id": "fetch-the-source-repo", "title": "Fetch the source repo", "level": 2, - "urlPath": "/docs/build/connect-docs-site", - "urlWithHash": "/docs/build/connect-docs-site#fetch-the-source-repo", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/connect-docs-site#fetch-the-source-repo", + "urlPath": "/docs/build/generate-static-artifacts", + "urlWithHash": "/docs/build/generate-static-artifacts#fetch-the-source-repo", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/generate-static-artifacts#fetch-the-source-repo", "children": [] }, { "id": "lint-before-generate", "title": "Lint before generate", "level": 2, - "urlPath": "/docs/build/connect-docs-site", - "urlWithHash": "/docs/build/connect-docs-site#lint-before-generate", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/connect-docs-site#lint-before-generate", + "urlPath": "/docs/build/generate-static-artifacts", + "urlWithHash": "/docs/build/generate-static-artifacts#lint-before-generate", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/generate-static-artifacts#lint-before-generate", "children": [] }, { - "id": "generate-hosted-artifacts", - "title": "Generate hosted artifacts", + "id": "generate", + "title": "Generate", "level": 2, - "urlPath": "/docs/build/connect-docs-site", - "urlWithHash": "/docs/build/connect-docs-site#generate-hosted-artifacts", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/connect-docs-site#generate-hosted-artifacts", + "urlPath": "/docs/build/generate-static-artifacts", + "urlWithHash": "/docs/build/generate-static-artifacts#generate", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/generate-static-artifacts#generate", "children": [] }, { - "id": "wire-it-into-the-app-build", - "title": "Wire it into the app build", + "id": "multi-source-mounting", + "title": "Multi-source mounting", "level": 2, - "urlPath": "/docs/build/connect-docs-site", - "urlWithHash": "/docs/build/connect-docs-site#wire-it-into-the-app-build", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/connect-docs-site#wire-it-into-the-app-build", + "urlPath": "/docs/build/generate-static-artifacts", + "urlWithHash": "/docs/build/generate-static-artifacts#multi-source-mounting", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/generate-static-artifacts#multi-source-mounting", "children": [] }, { - "id": "configure-product-and-groups", - "title": "Configure product and groups", + "id": "wire-it-into-the-build", + "title": "Wire it into the build", "level": 2, - "urlPath": "/docs/build/connect-docs-site", - "urlWithHash": "/docs/build/connect-docs-site#configure-product-and-groups", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/connect-docs-site#configure-product-and-groups", + "urlPath": "/docs/build/generate-static-artifacts", + "urlWithHash": "/docs/build/generate-static-artifacts#wire-it-into-the-build", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/generate-static-artifacts#wire-it-into-the-build", "children": [] }, { - "id": "use-scripts-for-custom-pipelines", - "title": "Use scripts for custom pipelines", + "id": "use-library-apis-for-custom-pipelines", + "title": "Use library APIs for custom pipelines", "level": 2, - "urlPath": "/docs/build/connect-docs-site", - "urlWithHash": "/docs/build/connect-docs-site#use-scripts-for-custom-pipelines", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/connect-docs-site#use-scripts-for-custom-pipelines", + "urlPath": "/docs/build/generate-static-artifacts", + "urlWithHash": "/docs/build/generate-static-artifacts#use-library-apis-for-custom-pipelines", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/generate-static-artifacts#use-library-apis-for-custom-pipelines", "children": [] }, { - "id": "wire-the-app-runtime", - "title": "Wire the app runtime", + "id": "verify", + "title": "Verify", "level": 2, - "urlPath": "/docs/build/connect-docs-site", - "urlWithHash": "/docs/build/connect-docs-site#wire-the-app-runtime", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/connect-docs-site#wire-the-app-runtime", + "urlPath": "/docs/build/generate-static-artifacts", + "urlWithHash": "/docs/build/generate-static-artifacts#verify", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/generate-static-artifacts#verify", + "children": [] + } + ] + }, + { + "urlPath": "/docs/build/integrate-with-fumadocs", + "title": "Integrate with Fumadocs", + "description": "Wire leadtype's content layer into a fumadocs app for nav, search, and includes.", + "groups": [ + "docs-site" + ], + "toc": [ + { + "id": "tl-dr", + "title": "TL;DR", + "level": 2, + "urlPath": "/docs/build/integrate-with-fumadocs", + "urlWithHash": "/docs/build/integrate-with-fumadocs#tl-dr", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/integrate-with-fumadocs#tl-dr", "children": [] }, { - "id": "verify", - "title": "Verify", + "id": "install", + "title": "Install", + "level": 2, + "urlPath": "/docs/build/integrate-with-fumadocs", + "urlWithHash": "/docs/build/integrate-with-fumadocs#install", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/integrate-with-fumadocs#install", + "children": [] + }, + { + "id": "wire-the-source", + "title": "Wire the source", + "level": 2, + "urlPath": "/docs/build/integrate-with-fumadocs", + "urlWithHash": "/docs/build/integrate-with-fumadocs#wire-the-source", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/integrate-with-fumadocs#wire-the-source", + "children": [] + }, + { + "id": "add-the-source-preset-to-your-mdx-compiler", + "title": "Add the source preset to your MDX compiler", + "level": 2, + "urlPath": "/docs/build/integrate-with-fumadocs", + "urlWithHash": "/docs/build/integrate-with-fumadocs#add-the-source-preset-to-your-mdx-compiler", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/integrate-with-fumadocs#add-the-source-preset-to-your-mdx-compiler", + "children": [] + }, + { + "id": "implement-the-tag-components", + "title": "Implement the tag components", + "level": 2, + "urlPath": "/docs/build/integrate-with-fumadocs", + "urlWithHash": "/docs/build/integrate-with-fumadocs#implement-the-tag-components", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/integrate-with-fumadocs#implement-the-tag-components", + "children": [] + }, + { + "id": "load-a-page-from-a-server-component", + "title": "Load a page from a server component", "level": 2, - "urlPath": "/docs/build/connect-docs-site", - "urlWithHash": "/docs/build/connect-docs-site#verify", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/connect-docs-site#verify", + "urlPath": "/docs/build/integrate-with-fumadocs", + "urlWithHash": "/docs/build/integrate-with-fumadocs#load-a-page-from-a-server-component", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/integrate-with-fumadocs#load-a-page-from-a-server-component", + "children": [] + }, + { + "id": "add-search", + "title": "Add search", + "level": 2, + "urlPath": "/docs/build/integrate-with-fumadocs", + "urlWithHash": "/docs/build/integrate-with-fumadocs#add-search", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/integrate-with-fumadocs#add-search", "children": [] } ] @@ -966,56 +1164,156 @@ ] }, { - "urlPath": "/docs/build/render-mdx-and-toc", - "title": "Render MDX and TOC", - "description": "Set up runtime MDX components, stable heading IDs, and a table of contents from the generated navigation manifest.", + "urlPath": "/docs/build/use-the-source-primitive", + "title": "Use the source primitive", + "description": "Wire createDocsSource into Next, Astro, Vite, Nuxt, SvelteKit, or any MDX-aware bundler. Same primitive, multiple host shapes.", "groups": [ "docs-site" ], "toc": [ { - "id": "register-mdx-components", - "title": "Register MDX components", + "id": "tl-dr", + "title": "TL;DR", "level": 2, - "urlPath": "/docs/build/render-mdx-and-toc", - "urlWithHash": "/docs/build/render-mdx-and-toc#register-mdx-components", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/render-mdx-and-toc#register-mdx-components", + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#tl-dr", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#tl-dr", "children": [] }, { - "id": "use-the-same-heading-slugs", - "title": "Use the same heading slugs", + "id": "install", + "title": "Install", "level": 2, - "urlPath": "/docs/build/render-mdx-and-toc", - "urlWithHash": "/docs/build/render-mdx-and-toc#use-the-same-heading-slugs", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/render-mdx-and-toc#use-the-same-heading-slugs", + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#install", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#install", "children": [] }, { - "id": "generate-navigation-with-toc-data", - "title": "Generate navigation with TOC data", + "id": "wire-into-your-framework", + "title": "Wire into your framework", "level": 2, - "urlPath": "/docs/build/render-mdx-and-toc", - "urlWithHash": "/docs/build/render-mdx-and-toc#generate-navigation-with-toc-data", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/render-mdx-and-toc#generate-navigation-with-toc-data", + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#wire-into-your-framework", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#wire-into-your-framework", + "children": [ + { + "id": "next-app-router", + "title": "Next App Router", + "level": 3, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#next-app-router", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#next-app-router", + "children": [] + }, + { + "id": "astro-content-collections", + "title": "Astro Content Collections", + "level": 3, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#astro-content-collections", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#astro-content-collections", + "children": [] + }, + { + "id": "tanstack-start", + "title": "TanStack Start", + "level": 3, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#tanstack-start", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#tanstack-start", + "children": [] + }, + { + "id": "vite-mdx-js-rollup-works-for-vue-solid-svelte-starters", + "title": "Vite + @mdx-js/rollup works for Vue, Solid, Svelte starters", + "level": 3, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#vite-mdx-js-rollup-works-for-vue-solid-svelte-starters", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#vite-mdx-js-rollup-works-for-vue-solid-svelte-starters", + "children": [] + }, + { + "id": "nuxt", + "title": "Nuxt", + "level": 3, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#nuxt", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#nuxt", + "children": [] + }, + { + "id": "sveltekit-mdsvex", + "title": "SvelteKit + mdsvex", + "level": 3, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#sveltekit-mdsvex", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#sveltekit-mdsvex", + "children": [] + }, + { + "id": "pattern-for-any-other-host", + "title": "Pattern for any other host", + "level": 3, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#pattern-for-any-other-host", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#pattern-for-any-other-host", + "children": [] + } + ] + }, + { + "id": "implement-the-tag-components", + "title": "Implement the tag components", + "level": 2, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#implement-the-tag-components", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#implement-the-tag-components", + "children": [] + }, + { + "id": "build-the-sidebar-from-navigation", + "title": "Build the sidebar from navigation", + "level": 2, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#build-the-sidebar-from-navigation", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#build-the-sidebar-from-navigation", "children": [] }, { - "id": "render-the-sidebar", - "title": "Render the sidebar", + "id": "match-heading-slugs", + "title": "Match heading slugs", "level": 2, - "urlPath": "/docs/build/render-mdx-and-toc", - "urlWithHash": "/docs/build/render-mdx-and-toc#render-the-sidebar", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/render-mdx-and-toc#render-the-sidebar", + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#match-heading-slugs", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#match-heading-slugs", + "children": [] + }, + { + "id": "build-a-search-index", + "title": "Build a search index", + "level": 2, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#build-a-search-index", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#build-a-search-index", "children": [] }, { "id": "troubleshooting", "title": "Troubleshooting", "level": 2, - "urlPath": "/docs/build/render-mdx-and-toc", - "urlWithHash": "/docs/build/render-mdx-and-toc#troubleshooting", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/render-mdx-and-toc#troubleshooting", + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#troubleshooting", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#troubleshooting", + "children": [] + }, + { + "id": "reference", + "title": "Reference", + "level": 2, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#reference", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#reference", "children": [] } ] @@ -1222,6 +1520,15 @@ "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/cli#config-loading", "children": [] }, + { + "id": "multiple-source-folders", + "title": "Multiple source folders", + "level": 3, + "urlPath": "/docs/reference/cli", + "urlWithHash": "/docs/reference/cli#multiple-source-folders", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/cli#multiple-source-folders", + "children": [] + }, { "id": "bundle-mode", "title": "Bundle mode", @@ -1546,6 +1853,15 @@ "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/llm#typical-sequence", "children": [] }, + { + "id": "mounted-url-prefixes", + "title": "Mounted URL prefixes", + "level": 2, + "urlPath": "/docs/reference/llm", + "urlWithHash": "/docs/reference/llm#mounted-url-prefixes", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/llm#mounted-url-prefixes", + "children": [] + }, { "id": "generateagentreadabilityartifacts", "title": "generateAgentReadabilityArtifacts", @@ -1676,6 +1992,116 @@ } ] }, + { + "urlPath": "/docs/reference/mdx", + "title": "leadtype/mdx", + "description": "Tag type contracts and the build-time source preset for consumers rendering MDX themselves.", + "groups": [ + "reference" + ], + "toc": [ + { + "id": "the-mdx-source-preset", + "title": "The MDX-source preset", + "level": 2, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#the-mdx-source-preset", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#the-mdx-source-preset", + "children": [] + }, + { + "id": "framework-neutral-by-design", + "title": "Framework-neutral by design", + "level": 2, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#framework-neutral-by-design", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#framework-neutral-by-design", + "children": [ + { + "id": "react", + "title": "React", + "level": 3, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#react", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#react", + "children": [] + }, + { + "id": "vue", + "title": "Vue", + "level": 3, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#vue", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#vue", + "children": [] + }, + { + "id": "svelte", + "title": "Svelte", + "level": 3, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#svelte", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#svelte", + "children": [] + }, + { + "id": "solid", + "title": "Solid", + "level": 3, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#solid", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#solid", + "children": [] + }, + { + "id": "astro", + "title": "Astro", + "level": 3, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#astro", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#astro", + "children": [] + }, + { + "id": "full-type-inventory", + "title": "Full type inventory", + "level": 3, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#full-type-inventory", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#full-type-inventory", + "children": [] + }, + { + "id": "build-time-only", + "title": "Build-time only:", + "level": 3, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#build-time-only", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#build-time-only", + "children": [] + } + ] + }, + { + "id": "include-resolution", + "title": "Include resolution", + "level": 2, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#include-resolution", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#include-resolution", + "children": [] + }, + { + "id": "re-exported-path-helpers", + "title": "Re-exported path helpers", + "level": 2, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#re-exported-path-helpers", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#re-exported-path-helpers", + "children": [] + } + ] + }, { "urlPath": "/docs/reference/remark", "title": "Remark plugins", @@ -1831,6 +2257,171 @@ "children": [] } ] + }, + { + "urlPath": "/docs/reference/source", + "title": "createDocsSource", + "description": "Framework-neutral docs source primitive — navigation, page loader, search index, and include resolver.", + "groups": [ + "reference" + ], + "toc": [ + { + "id": "configuration", + "title": "Configuration", + "level": 2, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#configuration", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#configuration", + "children": [] + }, + { + "id": "source-methods", + "title": "Source methods", + "level": 2, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#source-methods", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#source-methods", + "children": [ + { + "id": "getnavigation-promise", + "title": "getNavigation : Promise", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#getnavigation-promise", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#getnavigation-promise", + "children": [] + }, + { + "id": "listpages-promise", + "title": "listPages : Promise", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#listpages-promise", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#listpages-promise", + "children": [] + }, + { + "id": "loadpage-slug-promise", + "title": "loadPage slug : Promise", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#loadpage-slug-promise", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#loadpage-slug-promise", + "children": [] + }, + { + "id": "buildsearchindex-promise", + "title": "buildSearchIndex : Promise", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#buildsearchindex-promise", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#buildsearchindex-promise", + "children": [] + }, + { + "id": "resolveinclude-specifier-options-promise", + "title": "resolveInclude specifier, options? : Promise", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#resolveinclude-specifier-options-promise", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#resolveinclude-specifier-options-promise", + "children": [] + } + ] + }, + { + "id": "choosing-between-loadpage-and-direct-mdx-imports", + "title": "Choosing between loadPage and direct .mdx imports", + "level": 2, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#choosing-between-loadpage-and-direct-mdx-imports", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#choosing-between-loadpage-and-direct-mdx-imports", + "children": [] + }, + { + "id": "framework-integrations", + "title": "Framework integrations", + "level": 2, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#framework-integrations", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#framework-integrations", + "children": [ + { + "id": "next-app-router", + "title": "Next App Router", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#next-app-router", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#next-app-router", + "children": [] + }, + { + "id": "astro-content-collections", + "title": "Astro Content Collections", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#astro-content-collections", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#astro-content-collections", + "children": [] + }, + { + "id": "tanstack-start", + "title": "TanStack Start", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#tanstack-start", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#tanstack-start", + "children": [] + }, + { + "id": "vite-mdx-js-rollup-works-for-vue-solid-svelte-starters", + "title": "Vite + @mdx-js/rollup works for Vue, Solid, Svelte starters", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#vite-mdx-js-rollup-works-for-vue-solid-svelte-starters", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#vite-mdx-js-rollup-works-for-vue-solid-svelte-starters", + "children": [] + }, + { + "id": "nuxt", + "title": "Nuxt", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#nuxt", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#nuxt", + "children": [] + }, + { + "id": "sveltekit-mdsvex", + "title": "SvelteKit + mdsvex", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#sveltekit-mdsvex", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#sveltekit-mdsvex", + "children": [] + }, + { + "id": "fumadocs", + "title": "Fumadocs", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#fumadocs", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#fumadocs", + "children": [] + }, + { + "id": "pattern-for-any-other-framework", + "title": "Pattern for any other framework", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#pattern-for-any-other-framework", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#pattern-for-any-other-framework", + "children": [] + } + ] + } + ] } ], "children": [] diff --git a/apps/example/src/generated/docs-nav.json b/apps/example/src/generated/docs-nav.json index 739df60..4039693 100644 --- a/apps/example/src/generated/docs-nav.json +++ b/apps/example/src/generated/docs-nav.json @@ -17,21 +17,21 @@ ], "toc": [ { - "id": "choose-your-path", - "title": "Choose your path", + "id": "start-here", + "title": "Start here", "level": 2, "urlPath": "/docs", - "urlWithHash": "/docs#choose-your-path", - "absoluteUrlWithHash": "https://leadtype.dev/docs#choose-your-path", + "urlWithHash": "/docs#start-here", + "absoluteUrlWithHash": "https://leadtype.dev/docs#start-here", "children": [] }, { - "id": "what-you-get", - "title": "What you get", + "id": "what-leadtype-produces", + "title": "What leadtype produces", "level": 2, "urlPath": "/docs", - "urlWithHash": "/docs#what-you-get", - "absoluteUrlWithHash": "https://leadtype.dev/docs#what-you-get", + "urlWithHash": "/docs#what-leadtype-produces", + "absoluteUrlWithHash": "https://leadtype.dev/docs#what-leadtype-produces", "children": [] }, { @@ -167,7 +167,7 @@ { "urlPath": "/docs/quickstart", "title": "Quickstart", - "description": "Install leadtype, run the hosted-docs pipeline once, and inspect the generated artifacts.", + "description": "Install leadtype, author your first MDX page, and create a source. Then plug it into whatever framework you're using.", "groups": [ "get-started" ], @@ -191,21 +191,39 @@ "children": [] }, { - "id": "generate-hosted-docs-output", - "title": "Generate hosted docs output", + "id": "create-the-source", + "title": "Create the source", "level": 2, "urlPath": "/docs/quickstart", - "urlWithHash": "/docs/quickstart#generate-hosted-docs-output", - "absoluteUrlWithHash": "https://leadtype.dev/docs/quickstart#generate-hosted-docs-output", + "urlWithHash": "/docs/quickstart#create-the-source", + "absoluteUrlWithHash": "https://leadtype.dev/docs/quickstart#create-the-source", "children": [] }, { - "id": "choose-the-next-setup-step", - "title": "Choose the next setup step", + "id": "plug-it-into-your-framework", + "title": "Plug it into your framework", "level": 2, "urlPath": "/docs/quickstart", - "urlWithHash": "/docs/quickstart#choose-the-next-setup-step", - "absoluteUrlWithHash": "https://leadtype.dev/docs/quickstart#choose-the-next-setup-step", + "urlWithHash": "/docs/quickstart#plug-it-into-your-framework", + "absoluteUrlWithHash": "https://leadtype.dev/docs/quickstart#plug-it-into-your-framework", + "children": [] + }, + { + "id": "or-write-static-artifacts-to-disk", + "title": "Or: write static artifacts to disk", + "level": 2, + "urlPath": "/docs/quickstart", + "urlWithHash": "/docs/quickstart#or-write-static-artifacts-to-disk", + "absoluteUrlWithHash": "https://leadtype.dev/docs/quickstart#or-write-static-artifacts-to-disk", + "children": [] + }, + { + "id": "next-steps", + "title": "Next steps", + "level": 2, + "urlPath": "/docs/quickstart", + "urlWithHash": "/docs/quickstart#next-steps", + "absoluteUrlWithHash": "https://leadtype.dev/docs/quickstart#next-steps", "children": [] } ] @@ -247,6 +265,15 @@ "absoluteUrlWithHash": "https://leadtype.dev/docs/authoring/components#the-naming-contract", "children": [] }, + { + "id": "reuse-shared-content", + "title": "Reuse shared content", + "level": 2, + "urlPath": "/docs/authoring/components", + "urlWithHash": "/docs/authoring/components#reuse-shared-content", + "absoluteUrlWithHash": "https://leadtype.dev/docs/authoring/components#reuse-shared-content", + "children": [] + }, { "id": "component-reference", "title": "Component reference", @@ -462,73 +489,292 @@ "description": "Generate hosted docs artifacts, wire them into an app, add search, and make pages agent-readable.", "pages": [ { - "urlPath": "/docs/build/add-search", - "title": "Add search", - "description": "Generate a static docs search index, query it at runtime, and optionally stream source-grounded answers.", + "urlPath": "/docs/build/build-a-docs-site", + "title": "Build a docs site", + "description": "Pick the right leadtype integration shape for your docs app, and what each path gives you.", "groups": [ "docs-site" ], "toc": [ { - "id": "generate-the-files", - "title": "Generate the files", + "id": "pick-your-path", + "title": "Pick your path", "level": 2, - "urlPath": "/docs/build/add-search", - "urlWithHash": "/docs/build/add-search#generate-the-files", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/add-search#generate-the-files", + "urlPath": "/docs/build/build-a-docs-site", + "urlWithHash": "/docs/build/build-a-docs-site#pick-your-path", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/build-a-docs-site#pick-your-path", "children": [] }, { - "id": "query-at-runtime", - "title": "Query at runtime", + "id": "source-primitive-the-common-path", + "title": "Source primitive: the common path", "level": 2, - "urlPath": "/docs/build/add-search", - "urlWithHash": "/docs/build/add-search#query-at-runtime", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/add-search#query-at-runtime", + "urlPath": "/docs/build/build-a-docs-site", + "urlWithHash": "/docs/build/build-a-docs-site#source-primitive-the-common-path", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/build-a-docs-site#source-primitive-the-common-path", "children": [] }, { - "id": "add-vocabulary-aliases", - "title": "Add vocabulary aliases", + "id": "static-artifacts-the-cli-path", + "title": "Static artifacts: the CLI path", "level": 2, - "urlPath": "/docs/build/add-search", - "urlWithHash": "/docs/build/add-search#add-vocabulary-aliases", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/add-search#add-vocabulary-aliases", + "urlPath": "/docs/build/build-a-docs-site", + "urlWithHash": "/docs/build/build-a-docs-site#static-artifacts-the-cli-path", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/build-a-docs-site#static-artifacts-the-cli-path", "children": [] }, { - "id": "optional-ai-answers", - "title": "Optional AI answers", + "id": "add-the-cross-cutting-features", + "title": "Add the cross-cutting features", "level": 2, - "urlPath": "/docs/build/add-search", - "urlWithHash": "/docs/build/add-search#optional-ai-answers", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/add-search#optional-ai-answers", + "urlPath": "/docs/build/build-a-docs-site", + "urlWithHash": "/docs/build/build-a-docs-site#add-the-cross-cutting-features", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/build-a-docs-site#add-the-cross-cutting-features", "children": [] }, { - "id": "guard-the-endpoint", - "title": "Guard the endpoint", + "id": "configure-product-and-groups", + "title": "Configure product and groups", "level": 2, - "urlPath": "/docs/build/add-search", - "urlWithHash": "/docs/build/add-search#guard-the-endpoint", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/add-search#guard-the-endpoint", + "urlPath": "/docs/build/build-a-docs-site", + "urlWithHash": "/docs/build/build-a-docs-site#configure-product-and-groups", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/build-a-docs-site#configure-product-and-groups", + "children": [] + } + ] + }, + { + "urlPath": "/docs/build/use-the-source-primitive", + "title": "Use the source primitive", + "description": "Wire createDocsSource into Next, Astro, Vite, Nuxt, SvelteKit, or any MDX-aware bundler. Same primitive, multiple host shapes.", + "groups": [ + "docs-site" + ], + "toc": [ + { + "id": "tl-dr", + "title": "TL;DR", + "level": 2, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#tl-dr", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#tl-dr", "children": [] }, { - "id": "verify", - "title": "Verify", + "id": "install", + "title": "Install", "level": 2, - "urlPath": "/docs/build/add-search", - "urlWithHash": "/docs/build/add-search#verify", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/add-search#verify", + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#install", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#install", + "children": [] + }, + { + "id": "wire-into-your-framework", + "title": "Wire into your framework", + "level": 2, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#wire-into-your-framework", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#wire-into-your-framework", + "children": [ + { + "id": "next-app-router", + "title": "Next App Router", + "level": 3, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#next-app-router", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#next-app-router", + "children": [] + }, + { + "id": "astro-content-collections", + "title": "Astro Content Collections", + "level": 3, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#astro-content-collections", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#astro-content-collections", + "children": [] + }, + { + "id": "tanstack-start", + "title": "TanStack Start", + "level": 3, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#tanstack-start", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#tanstack-start", + "children": [] + }, + { + "id": "vite-mdx-js-rollup-works-for-vue-solid-svelte-starters", + "title": "Vite + @mdx-js/rollup works for Vue, Solid, Svelte starters", + "level": 3, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#vite-mdx-js-rollup-works-for-vue-solid-svelte-starters", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#vite-mdx-js-rollup-works-for-vue-solid-svelte-starters", + "children": [] + }, + { + "id": "nuxt", + "title": "Nuxt", + "level": 3, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#nuxt", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#nuxt", + "children": [] + }, + { + "id": "sveltekit-mdsvex", + "title": "SvelteKit + mdsvex", + "level": 3, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#sveltekit-mdsvex", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#sveltekit-mdsvex", + "children": [] + }, + { + "id": "pattern-for-any-other-host", + "title": "Pattern for any other host", + "level": 3, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#pattern-for-any-other-host", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#pattern-for-any-other-host", + "children": [] + } + ] + }, + { + "id": "implement-the-tag-components", + "title": "Implement the tag components", + "level": 2, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#implement-the-tag-components", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#implement-the-tag-components", + "children": [] + }, + { + "id": "build-the-sidebar-from-navigation", + "title": "Build the sidebar from navigation", + "level": 2, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#build-the-sidebar-from-navigation", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#build-the-sidebar-from-navigation", + "children": [] + }, + { + "id": "match-heading-slugs", + "title": "Match heading slugs", + "level": 2, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#match-heading-slugs", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#match-heading-slugs", + "children": [] + }, + { + "id": "build-a-search-index", + "title": "Build a search index", + "level": 2, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#build-a-search-index", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#build-a-search-index", + "children": [] + }, + { + "id": "troubleshooting", + "title": "Troubleshooting", + "level": 2, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#troubleshooting", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#troubleshooting", + "children": [] + }, + { + "id": "reference", + "title": "Reference", + "level": 2, + "urlPath": "/docs/build/use-the-source-primitive", + "urlWithHash": "/docs/build/use-the-source-primitive#reference", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/use-the-source-primitive#reference", + "children": [] + } + ] + }, + { + "urlPath": "/docs/build/integrate-with-fumadocs", + "title": "Integrate with Fumadocs", + "description": "Wire leadtype's content layer into a fumadocs app for nav, search, and includes.", + "groups": [ + "docs-site" + ], + "toc": [ + { + "id": "tl-dr", + "title": "TL;DR", + "level": 2, + "urlPath": "/docs/build/integrate-with-fumadocs", + "urlWithHash": "/docs/build/integrate-with-fumadocs#tl-dr", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/integrate-with-fumadocs#tl-dr", + "children": [] + }, + { + "id": "install", + "title": "Install", + "level": 2, + "urlPath": "/docs/build/integrate-with-fumadocs", + "urlWithHash": "/docs/build/integrate-with-fumadocs#install", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/integrate-with-fumadocs#install", + "children": [] + }, + { + "id": "wire-the-source", + "title": "Wire the source", + "level": 2, + "urlPath": "/docs/build/integrate-with-fumadocs", + "urlWithHash": "/docs/build/integrate-with-fumadocs#wire-the-source", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/integrate-with-fumadocs#wire-the-source", + "children": [] + }, + { + "id": "add-the-source-preset-to-your-mdx-compiler", + "title": "Add the source preset to your MDX compiler", + "level": 2, + "urlPath": "/docs/build/integrate-with-fumadocs", + "urlWithHash": "/docs/build/integrate-with-fumadocs#add-the-source-preset-to-your-mdx-compiler", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/integrate-with-fumadocs#add-the-source-preset-to-your-mdx-compiler", + "children": [] + }, + { + "id": "implement-the-tag-components", + "title": "Implement the tag components", + "level": 2, + "urlPath": "/docs/build/integrate-with-fumadocs", + "urlWithHash": "/docs/build/integrate-with-fumadocs#implement-the-tag-components", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/integrate-with-fumadocs#implement-the-tag-components", + "children": [] + }, + { + "id": "load-a-page-from-a-server-component", + "title": "Load a page from a server component", + "level": 2, + "urlPath": "/docs/build/integrate-with-fumadocs", + "urlWithHash": "/docs/build/integrate-with-fumadocs#load-a-page-from-a-server-component", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/integrate-with-fumadocs#load-a-page-from-a-server-component", + "children": [] + }, + { + "id": "add-search", + "title": "Add search", + "level": 2, + "urlPath": "/docs/build/integrate-with-fumadocs", + "urlWithHash": "/docs/build/integrate-with-fumadocs#add-search", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/integrate-with-fumadocs#add-search", "children": [] } ] }, { - "urlPath": "/docs/build/connect-docs-site", - "title": "Connect a docs site", - "description": "Build a shared docs app from source docs that live with the code they document.", + "urlPath": "/docs/build/generate-static-artifacts", + "title": "Generate static artifacts", + "description": "Run leadtype generate from your build pipeline to write llms.txt, markdown mirrors, search index, sitemap, and agent-readability files to disk.", "groups": [ "docs-site" ], @@ -537,81 +783,72 @@ "id": "the-flow", "title": "The flow", "level": 2, - "urlPath": "/docs/build/connect-docs-site", - "urlWithHash": "/docs/build/connect-docs-site#the-flow", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/connect-docs-site#the-flow", + "urlPath": "/docs/build/generate-static-artifacts", + "urlWithHash": "/docs/build/generate-static-artifacts#the-flow", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/generate-static-artifacts#the-flow", "children": [] }, { "id": "fetch-the-source-repo", "title": "Fetch the source repo", "level": 2, - "urlPath": "/docs/build/connect-docs-site", - "urlWithHash": "/docs/build/connect-docs-site#fetch-the-source-repo", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/connect-docs-site#fetch-the-source-repo", + "urlPath": "/docs/build/generate-static-artifacts", + "urlWithHash": "/docs/build/generate-static-artifacts#fetch-the-source-repo", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/generate-static-artifacts#fetch-the-source-repo", "children": [] }, { "id": "lint-before-generate", "title": "Lint before generate", "level": 2, - "urlPath": "/docs/build/connect-docs-site", - "urlWithHash": "/docs/build/connect-docs-site#lint-before-generate", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/connect-docs-site#lint-before-generate", + "urlPath": "/docs/build/generate-static-artifacts", + "urlWithHash": "/docs/build/generate-static-artifacts#lint-before-generate", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/generate-static-artifacts#lint-before-generate", "children": [] }, { - "id": "generate-hosted-artifacts", - "title": "Generate hosted artifacts", - "level": 2, - "urlPath": "/docs/build/connect-docs-site", - "urlWithHash": "/docs/build/connect-docs-site#generate-hosted-artifacts", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/connect-docs-site#generate-hosted-artifacts", - "children": [] - }, - { - "id": "wire-it-into-the-app-build", - "title": "Wire it into the app build", + "id": "generate", + "title": "Generate", "level": 2, - "urlPath": "/docs/build/connect-docs-site", - "urlWithHash": "/docs/build/connect-docs-site#wire-it-into-the-app-build", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/connect-docs-site#wire-it-into-the-app-build", + "urlPath": "/docs/build/generate-static-artifacts", + "urlWithHash": "/docs/build/generate-static-artifacts#generate", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/generate-static-artifacts#generate", "children": [] }, { - "id": "configure-product-and-groups", - "title": "Configure product and groups", + "id": "multi-source-mounting", + "title": "Multi-source mounting", "level": 2, - "urlPath": "/docs/build/connect-docs-site", - "urlWithHash": "/docs/build/connect-docs-site#configure-product-and-groups", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/connect-docs-site#configure-product-and-groups", + "urlPath": "/docs/build/generate-static-artifacts", + "urlWithHash": "/docs/build/generate-static-artifacts#multi-source-mounting", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/generate-static-artifacts#multi-source-mounting", "children": [] }, { - "id": "use-scripts-for-custom-pipelines", - "title": "Use scripts for custom pipelines", + "id": "wire-it-into-the-build", + "title": "Wire it into the build", "level": 2, - "urlPath": "/docs/build/connect-docs-site", - "urlWithHash": "/docs/build/connect-docs-site#use-scripts-for-custom-pipelines", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/connect-docs-site#use-scripts-for-custom-pipelines", + "urlPath": "/docs/build/generate-static-artifacts", + "urlWithHash": "/docs/build/generate-static-artifacts#wire-it-into-the-build", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/generate-static-artifacts#wire-it-into-the-build", "children": [] }, { - "id": "wire-the-app-runtime", - "title": "Wire the app runtime", + "id": "use-library-apis-for-custom-pipelines", + "title": "Use library APIs for custom pipelines", "level": 2, - "urlPath": "/docs/build/connect-docs-site", - "urlWithHash": "/docs/build/connect-docs-site#wire-the-app-runtime", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/connect-docs-site#wire-the-app-runtime", + "urlPath": "/docs/build/generate-static-artifacts", + "urlWithHash": "/docs/build/generate-static-artifacts#use-library-apis-for-custom-pipelines", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/generate-static-artifacts#use-library-apis-for-custom-pipelines", "children": [] }, { "id": "verify", "title": "Verify", "level": 2, - "urlPath": "/docs/build/connect-docs-site", - "urlWithHash": "/docs/build/connect-docs-site#verify", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/connect-docs-site#verify", + "urlPath": "/docs/build/generate-static-artifacts", + "urlWithHash": "/docs/build/generate-static-artifacts#verify", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/generate-static-artifacts#verify", "children": [] } ] @@ -709,56 +946,65 @@ ] }, { - "urlPath": "/docs/build/render-mdx-and-toc", - "title": "Render MDX and TOC", - "description": "Set up runtime MDX components, stable heading IDs, and a table of contents from the generated navigation manifest.", + "urlPath": "/docs/build/add-search", + "title": "Add search", + "description": "Generate a static docs search index, query it at runtime, and optionally stream source-grounded answers.", "groups": [ "docs-site" ], "toc": [ { - "id": "register-mdx-components", - "title": "Register MDX components", + "id": "generate-the-files", + "title": "Generate the files", + "level": 2, + "urlPath": "/docs/build/add-search", + "urlWithHash": "/docs/build/add-search#generate-the-files", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/add-search#generate-the-files", + "children": [] + }, + { + "id": "query-at-runtime", + "title": "Query at runtime", "level": 2, - "urlPath": "/docs/build/render-mdx-and-toc", - "urlWithHash": "/docs/build/render-mdx-and-toc#register-mdx-components", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/render-mdx-and-toc#register-mdx-components", + "urlPath": "/docs/build/add-search", + "urlWithHash": "/docs/build/add-search#query-at-runtime", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/add-search#query-at-runtime", "children": [] }, { - "id": "use-the-same-heading-slugs", - "title": "Use the same heading slugs", + "id": "add-vocabulary-aliases", + "title": "Add vocabulary aliases", "level": 2, - "urlPath": "/docs/build/render-mdx-and-toc", - "urlWithHash": "/docs/build/render-mdx-and-toc#use-the-same-heading-slugs", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/render-mdx-and-toc#use-the-same-heading-slugs", + "urlPath": "/docs/build/add-search", + "urlWithHash": "/docs/build/add-search#add-vocabulary-aliases", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/add-search#add-vocabulary-aliases", "children": [] }, { - "id": "generate-navigation-with-toc-data", - "title": "Generate navigation with TOC data", + "id": "optional-ai-answers", + "title": "Optional AI answers", "level": 2, - "urlPath": "/docs/build/render-mdx-and-toc", - "urlWithHash": "/docs/build/render-mdx-and-toc#generate-navigation-with-toc-data", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/render-mdx-and-toc#generate-navigation-with-toc-data", + "urlPath": "/docs/build/add-search", + "urlWithHash": "/docs/build/add-search#optional-ai-answers", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/add-search#optional-ai-answers", "children": [] }, { - "id": "render-the-sidebar", - "title": "Render the sidebar", + "id": "guard-the-endpoint", + "title": "Guard the endpoint", "level": 2, - "urlPath": "/docs/build/render-mdx-and-toc", - "urlWithHash": "/docs/build/render-mdx-and-toc#render-the-sidebar", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/render-mdx-and-toc#render-the-sidebar", + "urlPath": "/docs/build/add-search", + "urlWithHash": "/docs/build/add-search#guard-the-endpoint", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/add-search#guard-the-endpoint", "children": [] }, { - "id": "troubleshooting", - "title": "Troubleshooting", + "id": "verify", + "title": "Verify", "level": 2, - "urlPath": "/docs/build/render-mdx-and-toc", - "urlWithHash": "/docs/build/render-mdx-and-toc#troubleshooting", - "absoluteUrlWithHash": "https://leadtype.dev/docs/build/render-mdx-and-toc#troubleshooting", + "urlPath": "/docs/build/add-search", + "urlWithHash": "/docs/build/add-search#verify", + "absoluteUrlWithHash": "https://leadtype.dev/docs/build/add-search#verify", "children": [] } ] @@ -965,6 +1211,15 @@ "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/cli#config-loading", "children": [] }, + { + "id": "multiple-source-folders", + "title": "Multiple source folders", + "level": 3, + "urlPath": "/docs/reference/cli", + "urlWithHash": "/docs/reference/cli#multiple-source-folders", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/cli#multiple-source-folders", + "children": [] + }, { "id": "bundle-mode", "title": "Bundle mode", @@ -1289,6 +1544,15 @@ "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/llm#typical-sequence", "children": [] }, + { + "id": "mounted-url-prefixes", + "title": "Mounted URL prefixes", + "level": 2, + "urlPath": "/docs/reference/llm", + "urlWithHash": "/docs/reference/llm#mounted-url-prefixes", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/llm#mounted-url-prefixes", + "children": [] + }, { "id": "generateagentreadabilityartifacts", "title": "generateAgentReadabilityArtifacts", @@ -1419,6 +1683,116 @@ } ] }, + { + "urlPath": "/docs/reference/mdx", + "title": "leadtype/mdx", + "description": "Tag type contracts and the build-time source preset for consumers rendering MDX themselves.", + "groups": [ + "reference" + ], + "toc": [ + { + "id": "the-mdx-source-preset", + "title": "The MDX-source preset", + "level": 2, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#the-mdx-source-preset", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#the-mdx-source-preset", + "children": [] + }, + { + "id": "framework-neutral-by-design", + "title": "Framework-neutral by design", + "level": 2, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#framework-neutral-by-design", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#framework-neutral-by-design", + "children": [ + { + "id": "react", + "title": "React", + "level": 3, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#react", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#react", + "children": [] + }, + { + "id": "vue", + "title": "Vue", + "level": 3, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#vue", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#vue", + "children": [] + }, + { + "id": "svelte", + "title": "Svelte", + "level": 3, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#svelte", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#svelte", + "children": [] + }, + { + "id": "solid", + "title": "Solid", + "level": 3, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#solid", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#solid", + "children": [] + }, + { + "id": "astro", + "title": "Astro", + "level": 3, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#astro", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#astro", + "children": [] + }, + { + "id": "full-type-inventory", + "title": "Full type inventory", + "level": 3, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#full-type-inventory", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#full-type-inventory", + "children": [] + }, + { + "id": "build-time-only", + "title": "Build-time only:", + "level": 3, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#build-time-only", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#build-time-only", + "children": [] + } + ] + }, + { + "id": "include-resolution", + "title": "Include resolution", + "level": 2, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#include-resolution", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#include-resolution", + "children": [] + }, + { + "id": "re-exported-path-helpers", + "title": "Re-exported path helpers", + "level": 2, + "urlPath": "/docs/reference/mdx", + "urlWithHash": "/docs/reference/mdx#re-exported-path-helpers", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/mdx#re-exported-path-helpers", + "children": [] + } + ] + }, { "urlPath": "/docs/reference/remark", "title": "Remark plugins", @@ -1574,6 +1948,171 @@ "children": [] } ] + }, + { + "urlPath": "/docs/reference/source", + "title": "createDocsSource", + "description": "Framework-neutral docs source primitive — navigation, page loader, search index, and include resolver.", + "groups": [ + "reference" + ], + "toc": [ + { + "id": "configuration", + "title": "Configuration", + "level": 2, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#configuration", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#configuration", + "children": [] + }, + { + "id": "source-methods", + "title": "Source methods", + "level": 2, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#source-methods", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#source-methods", + "children": [ + { + "id": "getnavigation-promise", + "title": "getNavigation : Promise", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#getnavigation-promise", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#getnavigation-promise", + "children": [] + }, + { + "id": "listpages-promise", + "title": "listPages : Promise", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#listpages-promise", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#listpages-promise", + "children": [] + }, + { + "id": "loadpage-slug-promise", + "title": "loadPage slug : Promise", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#loadpage-slug-promise", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#loadpage-slug-promise", + "children": [] + }, + { + "id": "buildsearchindex-promise", + "title": "buildSearchIndex : Promise", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#buildsearchindex-promise", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#buildsearchindex-promise", + "children": [] + }, + { + "id": "resolveinclude-specifier-options-promise", + "title": "resolveInclude specifier, options? : Promise", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#resolveinclude-specifier-options-promise", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#resolveinclude-specifier-options-promise", + "children": [] + } + ] + }, + { + "id": "choosing-between-loadpage-and-direct-mdx-imports", + "title": "Choosing between loadPage and direct .mdx imports", + "level": 2, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#choosing-between-loadpage-and-direct-mdx-imports", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#choosing-between-loadpage-and-direct-mdx-imports", + "children": [] + }, + { + "id": "framework-integrations", + "title": "Framework integrations", + "level": 2, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#framework-integrations", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#framework-integrations", + "children": [ + { + "id": "next-app-router", + "title": "Next App Router", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#next-app-router", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#next-app-router", + "children": [] + }, + { + "id": "astro-content-collections", + "title": "Astro Content Collections", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#astro-content-collections", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#astro-content-collections", + "children": [] + }, + { + "id": "tanstack-start", + "title": "TanStack Start", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#tanstack-start", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#tanstack-start", + "children": [] + }, + { + "id": "vite-mdx-js-rollup-works-for-vue-solid-svelte-starters", + "title": "Vite + @mdx-js/rollup works for Vue, Solid, Svelte starters", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#vite-mdx-js-rollup-works-for-vue-solid-svelte-starters", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#vite-mdx-js-rollup-works-for-vue-solid-svelte-starters", + "children": [] + }, + { + "id": "nuxt", + "title": "Nuxt", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#nuxt", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#nuxt", + "children": [] + }, + { + "id": "sveltekit-mdsvex", + "title": "SvelteKit + mdsvex", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#sveltekit-mdsvex", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#sveltekit-mdsvex", + "children": [] + }, + { + "id": "fumadocs", + "title": "Fumadocs", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#fumadocs", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#fumadocs", + "children": [] + }, + { + "id": "pattern-for-any-other-framework", + "title": "Pattern for any other framework", + "level": 3, + "urlPath": "/docs/reference/source", + "urlWithHash": "/docs/reference/source#pattern-for-any-other-framework", + "absoluteUrlWithHash": "https://leadtype.dev/docs/reference/source#pattern-for-any-other-framework", + "children": [] + } + ] + } + ] } ], "children": [] diff --git a/apps/example/src/generated/docs-pages.json b/apps/example/src/generated/docs-pages.json new file mode 100644 index 0000000..1373be4 --- /dev/null +++ b/apps/example/src/generated/docs-pages.json @@ -0,0 +1,341 @@ +[ + { + "slug": [ + "authoring", + "components" + ], + "urlPath": "/docs/authoring/components", + "title": "Components", + "description": "MDX components the pipeline knows how to flatten into agent-readable markdown.", + "relativePath": "authoring/components", + "extension": ".mdx", + "groups": [ + "authoring" + ], + "globKey": "../../../../../docs/authoring/components.mdx" + }, + { + "slug": [ + "authoring", + "frontmatter" + ], + "urlPath": "/docs/authoring/frontmatter", + "title": "Frontmatter", + "description": "Required fields, group semantics, and how authored MDX becomes a navigation tree.", + "relativePath": "authoring/frontmatter", + "extension": ".mdx", + "groups": [ + "authoring" + ], + "globKey": "../../../../../docs/authoring/frontmatter.mdx" + }, + { + "slug": [ + "build", + "add-search" + ], + "urlPath": "/docs/build/add-search", + "title": "Add search", + "description": "Generate a static docs search index, query it at runtime, and optionally stream source-grounded answers.", + "relativePath": "build/add-search", + "extension": ".mdx", + "groups": [ + "docs-site" + ], + "globKey": "../../../../../docs/build/add-search.mdx" + }, + { + "slug": [ + "build", + "build-a-docs-site" + ], + "urlPath": "/docs/build/build-a-docs-site", + "title": "Build a docs site", + "description": "Pick the right leadtype integration shape for your docs app, and what each path gives you.", + "relativePath": "build/build-a-docs-site", + "extension": ".mdx", + "groups": [ + "docs-site" + ], + "globKey": "../../../../../docs/build/build-a-docs-site.mdx" + }, + { + "slug": [ + "build", + "generate-static-artifacts" + ], + "urlPath": "/docs/build/generate-static-artifacts", + "title": "Generate static artifacts", + "description": "Run leadtype generate from your build pipeline to write llms.txt, markdown mirrors, search index, sitemap, and agent-readability files to disk.", + "relativePath": "build/generate-static-artifacts", + "extension": ".mdx", + "groups": [ + "docs-site" + ], + "globKey": "../../../../../docs/build/generate-static-artifacts.mdx" + }, + { + "slug": [ + "build", + "integrate-with-fumadocs" + ], + "urlPath": "/docs/build/integrate-with-fumadocs", + "title": "Integrate with Fumadocs", + "description": "Wire leadtype's content layer into a fumadocs app for nav, search, and includes.", + "relativePath": "build/integrate-with-fumadocs", + "extension": ".mdx", + "groups": [ + "docs-site" + ], + "globKey": "../../../../../docs/build/integrate-with-fumadocs.mdx" + }, + { + "slug": [ + "build", + "optimize-docs-for-agents" + ], + "urlPath": "/docs/build/optimize-docs-for-agents", + "title": "Optimize docs for agents", + "description": "Set up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.", + "relativePath": "build/optimize-docs-for-agents", + "extension": ".mdx", + "groups": [ + "docs-site" + ], + "globKey": "../../../../../docs/build/optimize-docs-for-agents.mdx" + }, + { + "slug": [ + "build", + "use-the-source-primitive" + ], + "urlPath": "/docs/build/use-the-source-primitive", + "title": "Use the source primitive", + "description": "Wire createDocsSource into Next, Astro, Vite, Nuxt, SvelteKit, or any MDX-aware bundler. Same primitive, multiple host shapes.", + "relativePath": "build/use-the-source-primitive", + "extension": ".mdx", + "groups": [ + "docs-site" + ], + "globKey": "../../../../../docs/build/use-the-source-primitive.mdx" + }, + { + "slug": [ + "build", + "validate-in-ci" + ], + "urlPath": "/docs/build/validate-in-ci", + "title": "Validate in CI", + "description": "Run leadtype lint in CI so frontmatter, navigation, and link issues fail PRs before publish.", + "relativePath": "build/validate-in-ci", + "extension": ".mdx", + "groups": [ + "docs-site" + ], + "globKey": "../../../../../docs/build/validate-in-ci.mdx" + }, + { + "slug": [ + "how-it-works" + ], + "urlPath": "/docs/how-it-works", + "title": "How it works", + "description": "The mental model: one MDX source, a remark pipeline, two output modes, three audiences.", + "relativePath": "how-it-works", + "extension": ".mdx", + "groups": [ + "get-started" + ], + "globKey": "../../../../../docs/how-it-works.mdx" + }, + { + "slug": [], + "urlPath": "/docs", + "title": "Leadtype", + "description": "One MDX source. Hosted docs artifacts, package-bundled AGENTS.md, and search output from the same pipeline.", + "relativePath": "index", + "extension": ".mdx", + "groups": [ + "get-started" + ], + "globKey": "../../../../../docs/index.mdx" + }, + { + "slug": [ + "methodology" + ], + "urlPath": "/docs/methodology", + "title": "Methodology", + "description": "How leadtype differs from Fumadocs, Starlight, and Mintlify.", + "relativePath": "methodology", + "extension": ".mdx", + "groups": [ + "get-started" + ], + "globKey": "../../../../../docs/methodology.mdx" + }, + { + "slug": [ + "package-docs", + "bundle" + ], + "urlPath": "/docs/package-docs/bundle", + "title": "Bundle docs into a package", + "description": "Ship agent-readable docs inside an npm tarball — AGENTS.md at the package root plus per-topic .md files.", + "relativePath": "package-docs/bundle", + "extension": ".mdx", + "groups": [ + "package-docs" + ], + "globKey": "../../../../../docs/package-docs/bundle.mdx" + }, + { + "slug": [ + "quickstart" + ], + "urlPath": "/docs/quickstart", + "title": "Quickstart", + "description": "Install leadtype, author your first MDX page, and create a source. Then plug it into whatever framework you're using.", + "relativePath": "quickstart", + "extension": ".mdx", + "groups": [ + "get-started" + ], + "globKey": "../../../../../docs/quickstart.mdx" + }, + { + "slug": [ + "reference", + "cli" + ], + "urlPath": "/docs/reference/cli", + "title": "CLI", + "description": "leadtype generate and leadtype lint — flags, exit codes, and JSON output.", + "relativePath": "reference/cli", + "extension": ".mdx", + "groups": [ + "reference" + ], + "globKey": "../../../../../docs/reference/cli.mdx" + }, + { + "slug": [ + "reference", + "convert" + ], + "urlPath": "/docs/reference/convert", + "title": "Convert", + "description": "MDX-to-markdown conversion APIs from leadtype/convert.", + "relativePath": "reference/convert", + "extension": ".mdx", + "groups": [ + "reference" + ], + "globKey": "../../../../../docs/reference/convert.mdx" + }, + { + "slug": [ + "reference", + "evals" + ], + "urlPath": "/docs/reference/evals", + "title": "Evals", + "description": "How Leadtype benchmarks AGENTS.md, llms.txt, and llms-full.txt output before changing defaults.", + "relativePath": "reference/evals", + "extension": ".mdx", + "groups": [ + "reference" + ], + "globKey": "../../../../../docs/reference/evals.mdx" + }, + { + "slug": [ + "reference", + "lint" + ], + "urlPath": "/docs/reference/lint", + "title": "Lint rules", + "description": "Schema, link, and navigation checks. CLI and library API.", + "relativePath": "reference/lint", + "extension": ".mdx", + "groups": [ + "reference" + ], + "globKey": "../../../../../docs/reference/lint.mdx" + }, + { + "slug": [ + "reference", + "llm" + ], + "urlPath": "/docs/reference/llm", + "title": "LLM files", + "description": "Generate llms.txt for hosted websites and AGENTS.md for npm-bundled offline reading.", + "relativePath": "reference/llm", + "extension": ".mdx", + "groups": [ + "reference" + ], + "globKey": "../../../../../docs/reference/llm.mdx" + }, + { + "slug": [ + "reference", + "mdx" + ], + "urlPath": "/docs/reference/mdx", + "title": "leadtype/mdx", + "description": "Tag type contracts and the build-time source preset for consumers rendering MDX themselves.", + "relativePath": "reference/mdx", + "extension": ".mdx", + "groups": [ + "reference" + ], + "globKey": "../../../../../docs/reference/mdx.mdx" + }, + { + "slug": [ + "reference", + "remark" + ], + "urlPath": "/docs/reference/remark", + "title": "Remark plugins", + "description": "The default plugin stack that flattens MDX components into markdown.", + "relativePath": "reference/remark", + "extension": ".mdx", + "groups": [ + "reference" + ], + "globKey": "../../../../../docs/reference/remark.mdx" + }, + { + "slug": [ + "reference", + "search" + ], + "urlPath": "/docs/reference/search", + "title": "Search", + "description": "Static search index, runtime helpers, and source-grounded answer streaming.", + "relativePath": "reference/search", + "extension": ".mdx", + "groups": [ + "reference" + ], + "globKey": "../../../../../docs/reference/search.mdx" + }, + { + "slug": [ + "reference", + "source" + ], + "urlPath": "/docs/reference/source", + "title": "createDocsSource", + "description": "Framework-neutral docs source primitive — navigation, page loader, search index, and include resolver.", + "relativePath": "reference/source", + "extension": ".mdx", + "groups": [ + "reference" + ], + "globKey": "../../../../../docs/reference/source.mdx" + } +] diff --git a/apps/example/src/generated/docs-search-content.json b/apps/example/src/generated/docs-search-content.json index 75d5ec8..d324cf6 100644 --- a/apps/example/src/generated/docs-search-content.json +++ b/apps/example/src/generated/docs-search-content.json @@ -1 +1 @@ -{"version":2,"generatedAt":"2026-05-11T23:20:44.313Z","chunks":["Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nLeadtype does not ship UI components. Your docs app owns runtime rendering, styling, and accessibility — it only has to honor a small naming contract so the remark pipeline can flatten each component into markdown for agents, search, and llms-full.txt .","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nWhy flatten at all?\n\nInteractive MDX components like Body content goes here. ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nCards\n\nA grid of short, linked entry points. Flattens to a bullet list of links. Convert Remark Search\n\n```tsx ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nSteps\n\nNumbered walkthroughs. Flattens to an ordered list with bold step titles. 1. Author docs in MDX Use the components in this list as authoring affordances. 2. Run the conversion leadtype generate writes flattened markdown to public/docs/ . 3. Serve both formats HTML for humans, .md for agents — same URL, content negotiated by the Accept header.\n\n```tsx Use the components in this list. `leadtype generate` writes flattened markdown. ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nTabs\n\nGroup equivalent content. Flattens to bold headings followed by content so agents do not need a JSX-aware renderer to read every variant. tanstack-start Use a Vite middleware dev/preview and a Nitro middleware prod to negotiate the Accept header. next-js Wire content negotiation in middleware.ts and serve .md from a route handler. vite A configureServer middleware is enough for static deployments where .md files live in public/ .\n\n```tsx ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nCommandTabs\n\nPackage-manager-aware install or run commands. Flattens to a markdown table with one row per manager. Use mode=\"install\" when command is a package name, mode=\"run\" when command is a CLI name, and mode=\"create\" for starter commands. Use the commands prop for exact per-manager overrides. Package manager Command -- -- npm npm install leadtype pnpm pnpm add leadtype yarn yarn add leadtype bun bun add leadtype Package manager Command -- -- npm npx leadtype lint pnpm pnpm dlx leadtype lint yarn yarn dlx leadtype lint bun bunx leadtype lint\n\n```tsx ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nPrompt\n\nDisplays an agent-ready prompt with a copy action. Flattening preserves the prompt body as a fenced prompt block so copied instructions also survive in .md , root llms-full.txt , and bundled AGENTS.md output. Copy prompt for your coding agent Use this after generating artifacts.\n\n```tsx Inspect `public/docs/agent-readability.json`, then wire markdown responses before the HTML docs route. ``` ```prompt Inspect `public/docs/agent-readability.json`, then wire markdown responses before the HTML docs route. ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nAudience\n\nSplits browser-only and agent-only guidance without forking the page. Human content renders in the docs UI and is omitted from markdown output; agent content is hidden in the browser and included in generated markdown. This sentence appears only in generated markdown for agents.\n\n```tsx Click the robot icon in the header. Read generated markdown before editing runtime routes. ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nFileTree\n\nShows project structure in guides and release instructions. Flattening emits a fenced text tree so agents can read the same hierarchy without JSX.\n\n```tsx ``` ```text public/ ├── llms.txt └── docs/ └── index.md ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nAccordion\n\nCollapsible details for secondary content. Flattening ignores open/closed state and emits every item — accordions are not a place to hide content from agents. When should I use accordions? Use them for supporting details, troubleshooting notes, and optional reference material. Closed content is still flattened by the remark pipeline. Are accordions a good place to hide content from LLMs? No. Conversion ignores the open/closed state and emits everything inside.\n\n```tsx Use them for supporting details and optional reference material. ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nTopicSwitcher\n\nNavigation across equivalent docs topics — frameworks, SDKs, runtimes, deployment targets, product areas. Reader-facing only; it does not automatically read LLM topic config. Framework React — React integration Vue — Vue integration Svelte — Svelte integration\n\n```tsx ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nTypeTable and ExtractedTypeTable\n\nTypeTable is for explicit prop or type rows you already know. ExtractedTypeTable reads a TypeScript file at conversion time and extracts the table from a named type — keep its path stable. Property Type Description Default Required -- -- -- -- -- title string Heading rendered above the callout body. - ✅ Required variant CalloutVariant Visual treatment for the callout. info Optional deprecated \\ \\ boolean\\ \\ deprecated Marks the row as deprecated. false Optional\n\n```tsx ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nExample\n\nData-driven preview and source examples. The host component receives code as data; add file loaders or dynamic imports outside leadtype when an example needs app-specific behavior.\n\n```tsx The host app owns styling and runtime components while leadtype owns conversion. ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nMermaid\n\nDiagrams authored as plain text. Renders client-side as interactive SVG. Flattening preserves the source as a fenced mermaid block so other tools can render the diagram from the markdown copy.\n\n```tsx |remark| Markdown Markdown -->|llms.txt| Agents`} /> ``` ```mermaid `graph LR MDX -->|remark| Markdown Markdown -->|search index| API Markdown -->|llms.txt| Agents` ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nGuidelines\n\nKeep runtime components in your docs app. Leadtype stays out of UI. Keep component names stable. Renaming resolver page1 --> resolver page2 --> resolver page3 --> resolver resolver --> nav resolver --> llms resolver --> agents` ```","Frontmatter\n\nRequired fields, group semantics, and how authored MDX becomes a navigation tree.\n\nFrontmatter\n\nHow groups become a nav tree\n\nNested groups\n\nDeclare children in the config to build deeper trees A page sets group remote-source and lands in that nested slot. Only leaf groups no children directly contain pages; non-leaf groups are headings only.\n\n```ts { slug: \"docs-site\", title: \"Build a Docs Site\", children: [ { slug: \"remote-source\", title: \"Remote source\" }, { slug: \"connect-docs-site\", title: \"Connect a docs site\" }, ], } ```","Frontmatter\n\nRequired fields, group semantics, and how authored MDX becomes a navigation tree.\n\nFrontmatter\n\nOptional fields\n\nThe default lint schema also accepts Property Type Description Default Required -- -- -- -- -- icon string Icon name resolved by your sidebar component. - Optional deprecated boolean Marks the page as deprecated in nav and search. - Optional deprecatedReason string Short message paired with deprecated. - Optional experimental boolean Marks the page as experimental. - Optional canary boolean Hides from stable channels. - Optional new boolean Highlights the page as recently added. - Optional draft boolean Excludes from generation entirely. - Optional tags string\\ Free-form tags for search facets. - Optional availableIn Array\\<\\ framework, url?, title? Cross-framework availability map for TopicSwitcher pages. - Optional full boolean Layout hint for docs UIs that support full-width pages. - Optional lastModified and lastAuthor are filled in automatically when you pass --enrich-git to the CLI. Don't author them by hand.","Frontmatter\n\nRequired fields, group semantics, and how authored MDX becomes a navigation tree.\n\nFrontmatter\n\nLint rules\n\nleadtype lint enforces the schema, so violations surface in CI before they reach a build. The relevant rules schema — a required field is missing or has the wrong type. unknown-field — a top-level field isn't in the schema warn by default; --error-unknown to fail . parse-error — frontmatter or meta.json doesn't parse. invalid-link — a /docs/... link points to a route that doesn't exist. unresolved-placeholder — a doc URL still contains an unresolved framework placeholder. cross-framework-link — a framework-scoped page links to another framework's docs. See Lint rules for the full reference and how to extend the schema.","Frontmatter\n\nRequired fields, group semantics, and how authored MDX becomes a navigation tree.\n\nFrontmatter\n\nWhat this gives you\n\nOnce frontmatter is consistent, the rest of the pipeline works without per-page configuration. The same group value drives The sidebar position The llms.txt section Search metadata and AGENTS.md grouping The search filtering UI if you build one Cross-framework link checks in lint One field, one source of truth.","Add search\n\nGenerate a static docs search index, query it at runtime, and optionally stream source-grounded answers.\n\nAdd search\n\nLeadtype search is static by default. Build time produces two JSON files; runtime code imports or fetches those files and queries them without a database.","Add search\n\nGenerate a static docs search index, query it at runtime, and optionally stream source-grounded answers.\n\nAdd search\n\nGenerate the files\n\nleadtype generate writes search files in site mode If you run the pipeline from scripts, call the search generator after conversion This writes The index contains compact ranking data. The content store contains the text used for excerpts and answer context.\n\n```bash npx leadtype generate --src . --out public --base-url https://example.com ``` ```ts import { generateDocsSearchFiles } from \"leadtype/search/node\"; await generateDocsSearchFiles({ outDir: \"public\", baseUrl: \"https://example.com\", }); ``` ```txt public/docs/search-index.json public/docs/search-content.json ```","Add search\n\nGenerate a static docs search index, query it at runtime, and optionally stream source-grounded answers.\n\nAdd search\n\nQuery at runtime\n\nResults include page URLs, heading paths, hash URLs, and snippets. Render them in your own search UI.\n\n```ts import { searchDocs, type DocsSearchContentStore, type DocsSearchIndex, } from \"leadtype/search\"; import contentJson from \"../public/docs/search-content.json\"; import indexJson from \"../public/docs/search-index.json\"; const index = indexJson as DocsSearchIndex; const content = contentJson as DocsSearchContentStore; const results = searchDocs(index, \"run lint\", { content }); ```","Add search\n\nGenerate a static docs search index, query it at runtime, and optionally stream source-grounded answers.\n\nAdd search\n\nAdd vocabulary aliases\n\nSearch starts with lexical matching, stemming, prefix matches, typo-tolerant fallbacks, and a small built-in synonym map. Add product-specific synonyms only when users search with words your docs do not use\n\n```ts const results = searchDocs(index, \"starter\", { content, synonyms: { starter: [\"quickstart\", \"getting started\"], }, }); ```","Add search\n\nGenerate a static docs search index, query it at runtime, and optionally stream source-grounded answers.\n\nAdd search\n\nOptional AI answers\n\nUse source-grounded answers only after basic search works. Leadtype retrieves chunks from the static index, builds a constrained prompt, and leaves model choice to the provider entry point you import Display sources next to the streamed response. Do not ask the model to answer from memory; the answer context is built from retrieved docs chunks.\n\n```ts import { streamDocsAnswer } from \"leadtype/search/vercel\"; const { response, sources } = streamDocsAnswer({ index, content, query: \"How do I run docs lint in CI?\", model: \"openai/gpt-5.5\", productName: \"My Library\", }); ```","Add search\n\nGenerate a static docs search index, query it at runtime, and optionally stream source-grounded answers.\n\nAdd search\n\nGuard the endpoint\n\nFor API routes that accept user queries, use the request helpers from leadtype/search validateDocsQuery to trim and cap query text. readJsonWithLimit to reject oversized JSON bodies. getClientIdentifier to read common proxy IP headers. createMemoryRateLimiter for demos. Production apps should adapt the rate limiter interface to a shared store such as Redis, Vercel KV, Cloudflare KV, or Durable Objects.","Add search\n\nGenerate a static docs search index, query it at runtime, and optionally stream source-grounded answers.\n\nAdd search\n\nVerify\n\npublic/docs/search-index.json and public/docs/search-content.json exist and are non-empty. Searching for an exact API name returns the expected reference page. Searching for a guide phrase returns a result with a section hash. AI answers cite sources from the returned sources metadata.","Connect a docs site\n\nBuild a shared docs app from source docs that live with the code they document.\n\nConnect a docs site\n\nUse this path when you run a docs app that renders docs from one or more source repos. This is the main Leadtype docs-site model content stays next to the code it documents, while the docs app clones or checks out that content during build and serves the generated artifacts. Your framework still owns routes, HTML, styling, and deployment. Leadtype owns the source-to-artifact pipeline.","Connect a docs site\n\nBuild a shared docs app from source docs that live with the code they document.\n\nConnect a docs site\n\nThe flow\n\n```mermaid `flowchart LR repo[\"source repo / docs/*.mdx\"] clone[\"checkout or clone / .docs-src/c15t\"] lint[\"leadtype lint\"] generate[\"leadtype generate\"] public[\"public/ / llms.txt · llms-full.txt / docs/*.md / search · agent metadata\"] app[\"docs app / routing + HTML\"] repo --> clone --> lint --> generate --> public --> app` ```","Connect a docs site\n\nBuild a shared docs app from source docs that live with the code they document.\n\nConnect a docs site\n\nFetch the source repo\n\nIn CI, check out the docs source before the docs app build. For a public repo, a shallow clone is enough For private repos, use your CI platform's checkout action or a read-only deploy key. The important part is that Leadtype receives a normal filesystem path whose root contains docs/ .\n\n```bash rm -rf .docs-src/c15t git clone --depth 1 https://github.com/c15t/c15t .docs-src/c15t ```","Connect a docs site\n\nBuild a shared docs app from source docs that live with the code they document.\n\nConnect a docs site\n\nLint before generate\n\nRun lint against the fetched docs before writing generated output Lint fails with file and line context for frontmatter, internal links, placeholders, and schema issues. That is easier to debug than a later app build using stale or partial artifacts.\n\n```bash npx leadtype lint .docs-src/c15t/docs \\ --format github \\ --error-unknown \\ --max-warnings 0 ```","Connect a docs site\n\nBuild a shared docs app from source docs that live with the code they document.\n\nConnect a docs site\n\nGenerate hosted artifacts\n\nPoint --src at the fetched repository root and --out at the docs app public directory That command writes public/llms.txt and public/llms-full.txt public/docs/ .md public/docs/search-index.json and public/docs/search-content.json public/docs/sitemap.xml , sitemap.md , robots.txt , and agent-readability.json Use --json in CI so automation can record resolved groups, output files, filters, and search index stats.\n\n```bash npx leadtype generate \\ --src .docs-src/c15t \\ --out public \\ --base-url https://docs.example.com \\ --json ```","Connect a docs site\n\nBuild a shared docs app from source docs that live with the code they document.\n\nConnect a docs site\n\nWire it into the app build\n\nMake the source checkout, lint, and generation steps run before the framework build If the docs source and docs app live in the same repo, skip docs fetch and point --src at .\n\n```json { \"scripts\": { \"docs:fetch\": \"rm -rf .docs-src/c15t && git clone --depth 1 https://github.com/c15t/c15t .docs-src/c15t\", \"docs:lint\": \"leadtype lint .docs-src/c15t/docs --format github --error-unknown --max-warnings 0\", \"docs:generate\": \"leadtype generate --src .docs-src/c15t --out public --base-url https://docs.example.com --json\", \"build\": \"npm run docs:fetch && npm run docs:lint && npm run docs:generate && vite build\" } } ``` ```bash npx leadtype lint docs --error-unknown npx leadtype generate --src . --out public --base-url https://docs.example.com ```","Connect a docs site\n\nBuild a shared docs app from source docs that live with the code they document.\n\nConnect a docs site\n\nConfigure product and groups\n\nThe source repo should own docs/docs.config.ts so its groups and product metadata version with the docs. leadtype generate loads this file automatically from the docs folder Pages declare group in frontmatter. The config declares titles, order, and descriptions. Together they drive navigation, llms.txt , search metadata, and package AGENTS.md sections. If no config exists, the CLI infers groups from frontmatter, but inferred groups have no descriptions or custom order. See Frontmatter.\n\n```ts import { defineDocsConfig } from \"leadtype\"; export default defineDocsConfig({ product: { name: \"c15t\", summary: \"Consent infrastructure for modern web apps.\", bullets: [\"Framework integrations.\", \"Consent primitives.\", \"Audit-friendly docs.\"], bestStartingPoints: [{ urlPath: \"/docs\" }, { urlPath: \"/docs/quickstart\" }], }, groups: [ { slug: \"get-started\", title: \"Get Started\" }, { slug: \"guides\", title: \"Guides\" }, { slug: \"reference\", title: \"Reference\" }, ], }); ```","Connect a docs site\n\nBuild a shared docs app from source docs that live with the code they document.\n\nConnect a docs site\n\nUse scripts for custom pipelines\n\nThe CLI is the happy path. Use the library APIs directly when you need custom plugin order, filters, or generated JSON paths. Keep conversion first because LLM files, search, and Agent Readability artifacts read the generated markdown. Write navigation to a generated JSON file if your sidebar should use the same group tree as llms.txt . Write agentReadability.manifest if your runtime needs to merge docs pages with marketing, blog, changelog, or product routes.\n\n```ts import { convertAllMdx } from \"leadtype/convert\"; import { generateAgentReadabilityArtifacts, generateLLMFullContextFiles, generateLlmsTxt, resolveDocsNavigation, } from \"leadtype/llm\"; import { defaultRemarkPlugins, remarkInclude } from \"leadtype/remark\"; import { generateDocsSearchFiles } from \"leadtype/search/node\"; import docsConfig from \"../.docs-src/c15t/docs/docs.config\"; const sourceRoot = \".docs-src/c15t\"; await convertAllMdx({ srcDir: `${sourceRoot}/docs`, outDir: \"public/docs\", remarkPlugins: [remarkInclude, ...defaultRemarkPlugins], enrichFrontmatterFromGit: true, }); await generateLlmsTxt({ srcDir: sourceRoot, outDir: \"public\", baseUrl: \"https://docs.example.com\", product: docsConfig.product, groups: docsConfig.groups, }); await generateLLMFullContextFiles({ outDir: \"public\", baseUrl: \"https://docs.example.com\", product: { name: docsConfig.product.name }, groups: docsConfig.groups, }); await generateDocsSearchFiles({ outDir: \"public\", baseUrl: \"https://docs.example.com\", }); const agentReadability = await generateAgentReadabilityArtifacts({ outDir: \"public\", baseUrl: \"https://docs.example.com\", product: docsConfig.product, groups: docsConfig.groups, }); const","Connect a docs site\n\nBuild a shared docs app from source docs that live with the code they document.\n\nConnect a docs site\n\nUse scripts for custom pipelines\n\nait generateAgentReadabilityArtifacts({ outDir: \"public\", baseUrl: \"https://docs.example.com\", product: docsConfig.product, groups: docsConfig.groups, }); const navigation = await resolveDocsNavigation({ srcDir: sourceRoot, baseUrl: \"https://docs.example.com\", groups: docsConfig.groups, }); ```","Connect a docs site\n\nBuild a shared docs app from source docs that live with the code they document.\n\nConnect a docs site\n\nWire the app runtime\n\nAfter generation, choose the runtime pieces your site needs Render source MDX as HTML with your own components Render MDX and TOC. Return markdown for agent requests and serve discovery files Optimize docs for agents. Add local search and optional source-grounded answers Add search.","Connect a docs site\n\nBuild a shared docs app from source docs that live with the code they document.\n\nConnect a docs site\n\nVerify\n\nAfter a clean build, inspect these files public/docs/index.md — converted markdown for the source repo home page. public/llms.txt — hosted routing index with page-level markdown links. public/llms-full.txt — all generated markdown docs in one fallback file. public/docs/search-index.json and public/docs/search-content.json — non-empty search files. public/docs/agent-readability.json — manifest for markdown responses, JSON-LD, sitemap, and robots helpers. Then start your app and check at least one browser HTML page and one markdown response path.","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\nUse this guide when you already have a docs site and want agents to find, fetch, attribute, and cite the same content humans read in the browser. Leadtype handles the generated files. Your app wires those files into routing and HTML. The default output shape is based on the repo's agent evals. See Evals for the benchmark summary and the open question around larger-corpus llms-full.txt scaling.","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\nWhat good looks like\n\nAn agent-readable docs site has four layers 1. Discovery files /llms.txt , /sitemap.xml , /sitemap.md , and /robots.txt tell agents what exists and where to start. 2. Markdown retrieval Each docs page has a markdown mirror at /docs/page.md , and agent requests to /docs/page can receive markdown instead of HTML. 3. Structured HTML metadata Human HTML pages include JSON-LD, canonical links, and markdown alternate links so agents can extract page identity without guessing from the DOM. 4. Attribution metadata Markdown responses include canonical url and last updated frontmatter so copied content keeps its source and freshness.","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\n1. Generate the artifacts\n\nRun the site-mode pipeline before your app build This writes the docs-scoped files under public/docs/ and the top-level public/llms.txt The generated agent-readability.json manifest is the bridge between build-time content and runtime requests. It contains page URLs, markdown mirror paths, titles, descriptions, group navigation, and freshness dates.\n\n```bash npx leadtype generate \\ --src . \\ --out public \\ --base-url https://leadtype.dev \\ --name \"My product\" \\ --summary \"One sentence about the product.\" ``` ```txt public/ ├── llms.txt ├── llms-full.txt └── docs/ ├── index.md ├── quickstart.md ├── llms.txt ├── sitemap.xml ├── sitemap.md ├── robots.txt └── agent-readability.json ```","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\n2. Add one middleware\n\nPut the agent-readable routes before your HTML docs route. This Node/Bun example handles root discovery files, docs-scoped discovery files, direct .md URLs, and Accept text/markdown requests in one place If you also have marketing, blog, changelog, or product pages, pass them through the optional pages field — the regenerator merges them into the rebased output The other generated artifacts — /llms.txt , /docs/llms.txt , /llms-full.txt , /docs/agent-readability.json — use root-relative links and serve fine as static files straight from public/ . Keep the docs-scoped versions too /docs/sitemap.xml etc. . Audits and agents may request both /sitemap.xml and /docs/sitemap.xml , especially when the audited URL is /docs .\n\n```ts import { readFile } from \"node:fs/promises\"; import { join } from \"node:path\"; import manifestJson from \"../public/docs/agent-readability.json\" with { type: \"json\", }; import { createAgentMarkdownResponse, createRobotsTxtResponse, createSitemapMarkdownResponse, createSitemapXmlResponse, type AgentReadabilityManifest, type MarkdownMirrorTarget, } from \"leadtype/llm/readability\"; const manifest = { ...manifestJson, version: 1, } as AgentReadabilityManifest; async function readMarkdownFile( target: MarkdownMirrorTarget ): Promise { try { return await readFile(join(process.cwd(), \"public\", target.filePath), \"utf8\"); } catch (error) { if ( typeof error === \"object\" && error !== null && \"code\" in error && (error.code === \"ENOENT\" || error.code === \"ENOTDIR\") ) { return null; } throw error; } } export async function handleDocsRequest( request: Request ): Promise { if (request.method !== \"GET\" && request.method !== \"HEAD\") { return null; } const url = new URL(request.url); const requestOrigin = url.origin; switch (url.pathname) { case \"/sitemap.xml\": case \"/docs/sitemap.xml\": return createSitemapXmlResponse({ manifest, requestOrigin }); case","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\n2. Add one middleware\n\nstOrigin = url.origin; switch (url.pathname) { case \"/sitemap.xml\": case \"/docs/sitemap.xml\": return createSitemapXmlResponse({ manifest, requestOrigin }); case \"/sitemap.md\": case \"/docs/sitemap.md\": return createSitemapMarkdownResponse({ manifest, requestOrigin }); case \"/robots.txt\": return createRobotsTxtResponse({ manifest, requestOrigin }); case \"/docs/robots.txt\": return createRobotsTxtResponse({ manifest, requestOrigin, sitemapUrlPath: \"/docs/sitemap.xml\", }); default: return createAgentMarkdownResponse({ urlPath: url.pathname, method: request.method, headers: Object.fromEntries(request.headers), manifest, readMarkdownFile, requestOrigin, }); } } ``` ```ts return createSitemapXmlResponse({ manifest, requestOrigin, pages: [...manifest.pages, ...marketingPages, ...blogPages], }); ```","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\n3. Add JSON-LD to docs pages\n\nUse the manifest entry for the current page and render Schema.org JSON-LD into the HTML head Use renderJsonLd page, manifest if your framework has a typed metadata API. Use renderJsonLdScript page, manifest if your framework expects an HTML string. Also add canonical and markdown alternate links The JSON-LD gives agents the page title, description, canonical URL, last modified date, and breadcrumbs without scraping your rendered layout.\n\n```ts import agentManifest from \"../public/docs/agent-readability.json\"; import { renderJsonLd, renderJsonLdScript } from \"leadtype/llm/readability\"; const page = agentManifest.pages.find( (entry) => entry.urlPath === \"/docs/quickstart\" ); if (page) { const jsonLd = renderJsonLd(page, agentManifest); const script = renderJsonLdScript(page, agentManifest); } ``` ```html ```","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\n4. Return markdown to agents\n\nThe middleware above uses createAgentMarkdownResponse . It returns a Web Response or null when the path is not an agent-oriented markdown request and handles Accept text/markdown and Accept text/plain content negotiation q-values respected . Known AI user-agent headers GPTBot, ClaudeBot, Bingbot, AmazonBot, MetaExternalAgent, PerplexityBot, MistralBot, AppleBot, ByteSpider, YouBot, … . Direct .md URLs such as /docs/quickstart.md . canonical url and last updated frontmatter aliases injected automatically. 200 markdown responses for missing docs pages, so agents do not discard the body. Content-Type text/markdown; charset=utf-8 , Vary Accept , User-Agent , Link <… ; rel=\"canonical\" , Cache-Control public, max-age=300, must-revalidate . readMarkdownFile may be sync or async. In Node/Bun, read from disk. In Cloudflare, fetch from KV/R2 or an asset binding. In Vercel Edge, fetch from the deployment's static asset URL. Put that logic wherever your framework can intercept docs requests before its HTML route Framework/runtime Where it usually goes -- -- TanStack Start / nitro server/middleware/agent-readability.ts h3 .","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\n4. Return markdown to agents\n\nn intercept docs requests before its HTML route Framework/runtime Where it usually goes -- -- TanStack Start / nitro server/middleware/agent-readability.ts h3 . One middleware handles both the markdown response and the sitemap/robots regenerators — runs in dev, preview, and prod. See apps/example/server/middleware/agent-readability.ts for the canonical reference. Nuxt server/middleware/agent-readability.ts h3 — same shape as the TanStack Start example. Next.js middleware.ts Edge or a catch-all route handler before the docs page. Astro An endpoint at pages/docs/ ...slug .md.ts or astro middleware . Cloudflare Workers/Pages Worker fetch handler with KV/R2 asset binding for the markdown reader. Express/Hono/Fastify Middleware before the docs HTML route. Tip if you keep static sitemap.xml / sitemap.md / robots.txt files in your build output, your framework's static handler may serve them before your middleware can rebase URLs to the live origin. Either delete the static copies after the build so the middleware always runs or make sure your middleware is registered ahead of static-asset serving.","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\n4. Return markdown to agents\n\norigin. Either delete the static copies after the build so the middleware always runs or make sure your middleware is registered ahead of static-asset serving. Do not rewrite llms.txt , sitemap.xml , sitemap.md , robots.txt , llms-full.txt , or agent-readability.json to page markdown. The helper leaves those artifact paths alone.","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\n4. Return markdown to agents\n\nWhy the sitemap and robots responses are regenerated, not static\n\nsitemap.xml 's ; function textFromChildren(children: unknown): string { if (typeof children === \"string\" || typeof children === \"number\") { return String(children); } if (Array.isArray(children)) { return children.map(textFromChildren).join(\"\"); } if (isValidElement(children)) { const elementProps = children.props as { children?: unknown }; return textFromChildren(elementProps.children); } return \"\"; } function createHeading(level: 1 | 2 | 3 | 4 | 5 | 6) { return ({ children, id, ...props }: HeadingProps) => { const Component = `h${level}` as const; const headingId = id ?? slugifyDocsHeading(textFromChildren(children)); return ( {children} ); }; } ```","Render MDX and TOC\n\nSet up runtime MDX components, stable heading IDs, and a table of contents from the generated navigation manifest.\n\nRender MDX and TOC\n\nGenerate navigation with TOC data\n\nresolveDocsNavigation includes toc on every page. The default range is h2 to h3 . Write the navigation object to a generated JSON file and import it from your sidebar.\n\n```ts import { resolveDocsNavigation } from \"leadtype/llm\"; import docsConfig from \"../docs/docs.config\"; const navigation = await resolveDocsNavigation({ srcDir: \".\", baseUrl: \"https://example.com\", groups: docsConfig.groups, toc: { minLevel: 2, maxLevel: 4 }, }); ```","Render MDX and TOC\n\nSet up runtime MDX components, stable heading IDs, and a table of contents from the generated navigation manifest.\n\nRender MDX and TOC\n\nRender the sidebar\n\nLook up the current page in the manifest and pass currentPage.toc to your sidebar component. The example app includes a complete React implementation with scroll-spy, hash sync, active highlighting, and sticky positioning apps/example/src/components/table-of-contents.tsx","Render MDX and TOC\n\nSet up runtime MDX components, stable heading IDs, and a table of contents from the generated navigation manifest.\n\nRender MDX and TOC\n\nTroubleshooting\n\nTOC links do not scroll anywhere. Your heading IDs do not match the extracted slugs. Use slugifyDocsHeading in the rendered heading components. A page TOC is empty. All headings are outside the configured minLevel and maxLevel range. Defaults exclude h1 and headings deeper than h3 . Sidebar order differs from llms.txt. The app is not using the same docs.config.ts groups as the generation step.","Validate in CI\n\nRun leadtype lint in CI so frontmatter, navigation, and link issues fail PRs before publish.\n\nValidate in CI\n\nleadtype lint reads source MDX, runs the schema and link checks, and exits non-zero on errors. Wire it into CI so docs PRs fail fast on the same rules that would otherwise blow up leadtype generate later in the pipeline.","Validate in CI\n\nRun leadtype lint in CI so frontmatter, navigation, and link issues fail PRs before publish.\n\nValidate in CI\n\nWhat it catches\n\nMissing or wrong-typed frontmatter fields schema rule . Top-level frontmatter fields not in the schema unknown-field . Frontmatter or meta.json that doesn't parse parse-error . Internal /docs/... links pointing at routes that don't exist invalid-link . Unresolved framework placeholders left in URLs unresolved-placeholder . Cross-framework links from a framework-scoped page to a different framework's docs cross-framework-link . See Lint reference for the full rule list and how to extend the schema.","Validate in CI\n\nRun leadtype lint in CI so frontmatter, navigation, and link issues fail PRs before publish.\n\nValidate in CI\n\nGitHub Actions\n\nUse --format github so violations render as inline annotations on the PR --error-unknown upgrades unknown-field warnings to errors — strict mode. --max-warnings 0 makes any remaining warning fail the job.\n\n```yaml name: Lint docs on: pull_request: paths: - \"docs/**\" - \"**/*.mdx\" jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 - run: bun install - run: npx leadtype lint docs --format github --error-unknown --max-warnings 0 ```","Validate in CI\n\nRun leadtype lint in CI so frontmatter, navigation, and link issues fail PRs before publish.\n\nValidate in CI\n\nOther CI providers\n\nUse --format json for any CI that doesn't speak the GitHub annotations format The JSON output includes per-file violations and summary counts. Pipe it into your provider's reporter, post a PR comment, or upload as an artifact.\n\n```bash npx leadtype lint docs --format json --error-unknown > lint-report.json ```","Validate in CI\n\nRun leadtype lint in CI so frontmatter, navigation, and link issues fail PRs before publish.\n\nValidate in CI\n\nLocal pre-push hook\n\nCatch issues before they reach CI by running lint in a husky pre-push hook Keep it under a second by limiting the scan to changed files when you have many pages. The CLI accepts repeated --ignore globs to skip stale or generated paths.\n\n```bash #!/usr/bin/env sh npx leadtype lint docs --max-warnings 0 ```","Validate in CI\n\nRun leadtype lint in CI so frontmatter, navigation, and link issues fail PRs before publish.\n\nValidate in CI\n\nRun before generate\n\nWhen leadtype lint and leadtype generate both run in the same job, lint first Generate fails noisily on unknown groups or broken includes. Lint fails specifically on content schema problems with file/line context — much easier to debug.\n\n```bash npx leadtype lint docs --error-unknown npx leadtype generate --src . --out public --json ```","Validate in CI\n\nRun leadtype lint in CI so frontmatter, navigation, and link issues fail PRs before publish.\n\nValidate in CI\n\nWhat to fix first\n\nWhen CI fails on a lot of violations, fix them in this order 1. parse-error — frontmatter is broken; nothing else can validate. 2. schema — missing or wrong-typed required fields. 3. unresolved-placeholder — content bug, not a config bug. 4. invalid-link and cross-framework-link — usually a stale link after a docs move. 5. unknown-field — last; either delete the field or extend the schema.","How it works\n\nThe mental model: one MDX source, a remark pipeline, two output modes, three audiences.\n\nHow it works\n\nLeadtype takes one input — a folder of MDX — and produces every shape your docs need to take. This page names every piece so the rest of the docs make sense.","How it works\n\nThe mental model: one MDX source, a remark pipeline, two output modes, three audiences.\n\nHow it works\n\nThe pipeline\n\nThe remark stack is what turns interactive MDX components into agent-readable markdown. JSX gets flattened — a fm fm --> remark fm --> groups remark --> md md --> site_idx md --> search groups --> site_idx groups --> agents_md groups --> nav` ```","How it works\n\nThe mental model: one MDX source, a remark pipeline, two output modes, three audiences.\n\nHow it works\n\nTwo output modes\n\nleadtype generate has two modes that read the same source and emit different shapes Property Type Description Default Required -- -- -- -- -- Site mode default leadtype generate --out public Writes llms.txt, llms-full.txt, docs/search-index.json, docs/sitemap.xml, docs/sitemap.md, docs/robots.txt, docs/agent-readability.json, and docs/\\ .md to a public/ directory your docs website serves. This is what you wire into a Vite, Next.js, Astro, or TanStack Start build. - Optional Bundle mode leadtype generate --bundle --out packages/foo Writes AGENTS.md at the package root and docs/\\ .md beneath it, both with relative paths. Skips llms.txt, llms-full.txt, search, sitemap, robots, and Agent Readability files — those are website-only. Designed for npm tarballs that ship docs alongside the published code. - Optional","How it works\n\nThe mental model: one MDX source, a remark pipeline, two output modes, three audiences.\n\nHow it works\n\nThe artifacts\n\nProperty Type Description Default Required -- -- -- -- -- Markdown .md docs/\\ human site_out -- \"absolute URLs\" --> http_agent site_out -- \"search-index.json\" --> search_ui bundle_out -- \"version-matched AGENTS.md\" --> offline_agent` ```","How it works\n\nThe mental model: one MDX source, a remark pipeline, two output modes, three audiences.\n\nHow it works\n\nVocabulary\n\nA few terms you will see throughout the docs. Property Type Description Default Required -- -- -- -- -- flatten verb Convert an interactive MDX component into a portable markdown equivalent. A \\ site_run src --> bundle_run site_run --> site_out bundle_run --> bundle_out site_out --> humans site_out --> http_agents site_out --> search bundle_out --> offline_agents` ```","Leadtype\n\nOne MDX source. Hosted docs artifacts, package-bundled AGENTS.md, and search output from the same pipeline.\n\nLeadtype\n\nChoose your path\n\nPick the job that matches what you are building. Each path starts with the minimum setup, then links to the lower-level API reference only when you need it. Connect a docs site Add search Ship docs in your package","Leadtype\n\nOne MDX source. Hosted docs artifacts, package-bundled AGENTS.md, and search output from the same pipeline.\n\nLeadtype\n\nWhat you get\n\n1. Write once Author MDX with familiar components — Callout , Tabs , Steps , Mermaid , TypeTable , and others. Add group in frontmatter to place pages in the navigation tree. 2. Run \\ leadtype generate\\ For a website converts MDX to markdown, builds llms.txt plus root llms-full.txt , generates a search index, writes Agent Readability discovery files, and resolves navigation. With --bundle emits AGENTS.md plus per-topic .md files with relative links that still work after npm install. 3. Serve all of it Humans get HTML from your app. HTTP agents get markdown via content negotiation or /llms.txt . Package consumers can point their root AGENTS.md or README at node modules/ cli cli --> bundle bundle --> publish publish --> install install --> consume` ```","Bundle docs into a package\n\nShip agent-readable docs inside an npm tarball — AGENTS.md at the package root plus per-topic .md files.\n\nBundle docs into a package\n\nWhy AGENTS.md, not llms.txt?\n\nllms.txt is a website convention — a file at /llms.txt with absolute URLs that an agent fetches over HTTP. Inside an npm tarball it's the wrong shape every link points at a hosted URL the agent may not be able to reach, and no major coding agent looks for node modules/ 0) { for (const { urlPath, slug } of navigation.unknown) { process.stderr.write(`error: ${urlPath} declares unknown group \"${slug}\".\\n`); } process.exit(1); } await generateAgentsMd({ srcDir: REPO_ROOT, outDir: PACKAGE_ROOT, product: docsConfig.product, groups: docsConfig.groups, }); ```","Bundle docs into a package\n\nShip agent-readable docs inside an npm tarball — AGENTS.md at the package root plus per-topic .md files.\n\nBundle docs into a package\n\nVerify before publishing\n\nnpm pack --dry-run should list AGENTS.md docs/ / .md If AGENTS.md is missing, check files in package.json . If .md files are missing, check the --include / --exclude filters.\n\n```bash npx leadtype generate --bundle --src . --out packages/my-package cd packages/my-package && npm pack --dry-run ```","Bundle docs into a package\n\nShip agent-readable docs inside an npm tarball — AGENTS.md at the package root plus per-topic .md files.\n\nBundle docs into a package\n\nTell consuming projects to use the bundle\n\nAGENTS.md inside your tarball is available at node modules/ When working with the `` library, read the bundled docs in `node_modules//AGENTS.md` first — they're version-matched to the installed package and stay accurate as the library updates. ```","Bundle docs into a package\n\nShip agent-readable docs inside an npm tarball — AGENTS.md at the package root plus per-topic .md files.\n\nBundle docs into a package\n\nWhen to use this\n\nUse this when agents should understand the package from the installed dependency itself — coding agents that don't have web access, IDE assistants, CLI tools, or air-gapped environments. Don't use this if your only goal is a public docs website. For that, see Connect a docs site. You can use both — they read the same source MDX, just emit different shapes.","Bundle docs into a package\n\nShip agent-readable docs inside an npm tarball — AGENTS.md at the package root plus per-topic .md files.\n\nBundle docs into a package\n\nWhat's next\n\nCLI reference LLM files Lint in CI","Quickstart\n\nInstall leadtype, run the hosted-docs pipeline once, and inspect the generated artifacts.\n\nQuickstart\n\nFive minutes from a folder of MDX to generated docs artifacts.","Quickstart\n\nInstall leadtype, run the hosted-docs pipeline once, and inspect the generated artifacts.\n\nQuickstart\n\nInstall\n\nPackage manager Command -- -- npm npm install leadtype pnpm pnpm add leadtype yarn yarn add leadtype bun bun add leadtype leadtype ships a CLI plus focused library entry points. Start with the CLI; use the APIs only when your build needs custom plugin order, filtering, or output paths.","Quickstart\n\nInstall leadtype, run the hosted-docs pipeline once, and inspect the generated artifacts.\n\nQuickstart\n\nAuthor one page\n\nCreate docs/index.mdx in your repo Every page needs a title . Add group when the page should appear in generated navigation and llms.txt sections. See Frontmatter for the full content contract.\n\n```mdx --- title: \"My library\" description: \"What it does in one sentence.\" group: get-started --- # My library Welcome. ```","Quickstart\n\nInstall leadtype, run the hosted-docs pipeline once, and inspect the generated artifacts.\n\nQuickstart\n\nGenerate hosted docs output\n\nThat command reads docs/ .mdx , converts it to markdown, resolves groups, builds search JSON, and writes hosted agent artifacts. Open public/docs/index.md first. It shows what your MDX becomes after the remark component flattening pipeline runs. Then open public/llms.txt to see the hosted routing file that HTTP agents start from. 💡 Tip Working reference apps/example/ in the leadtype repo is the production docs site for these docs. It runs the same pipeline you just ran, on TanStack Start, with markdown content negotiation, sitemap regeneration, and JSON-LD wired up. Clone it when you want a copy-pastable end-to-end setup.\n\n```bash npx leadtype generate \\ --src . \\ --out public \\ --base-url https://example.com ``` ```text public/ ├── llms.txt ├── llms-full.txt └── docs/ ├── index.md ├── llms.txt ├── sitemap.xml ├── sitemap.md ├── robots.txt ├── agent-readability.json ├── search-index.json └── search-content.json ```","Quickstart\n\nInstall leadtype, run the hosted-docs pipeline once, and inspect the generated artifacts.\n\nQuickstart\n\nChoose the next setup step\n\nGoal Next page -- -- Wire the generator into an app build Connect a docs site Render HTML pages and an \"On this page\" sidebar Render MDX and TOC Serve markdown, sitemaps, robots, and JSON-LD for agents Optimize docs for agents Query the generated static search index Add search Publish AGENTS.md inside an npm package Bundle docs into a package Connect a docs site Render MDX and TOC Make the site agent-readable Bundle docs into a package","CLI\n\nleadtype generate and leadtype lint — flags, exit codes, and JSON output.\n\nCLI\n\nTwo commands generate runs the full pipeline, lint validates content. Run leadtype help [options] ```","CLI\n\nleadtype generate and leadtype lint — flags, exit codes, and JSON output.\n\nCLI\n\ngenerate\n\nConvert MDX, then either produce website artifacts default or a package bundle --bundle . Flag Default Description -- -- -- --src ; summary: { filesScanned: number; errors: number; warnings: number; }; }; ```","Lint rules\n\nSchema, link, and navigation checks. CLI and library API.\n\nLint rules\n\nDefault schemas\n\nDocs frontmatter\n\nField Required Type -- -- -- title Yes non-empty string description No string icon No string deprecated No boolean deprecatedReason No string experimental No boolean canary No boolean new No boolean draft No boolean tags No string array group No string or string array availableIn No array of framework, url?, title? full No boolean lastModified and lastAuthor are produced by the converter when --enrich-git is set. Don't author them.","Lint rules\n\nSchema, link, and navigation checks. CLI and library API.\n\nLint rules\n\nDefault schemas\n\nChangelog frontmatter\n\nField Required Type -- -- -- title Yes non-empty string version Yes SemVer string date Yes ISO-8601 or parseable date description No string icon No string type No release , improvement , retired , or deprecation tags No string array canary No boolean authors No string or string array draft No boolean","Lint rules\n\nSchema, link, and navigation checks. CLI and library API.\n\nLint rules\n\nDefault schemas\n\nmeta.json\n\nField Required Type -- -- -- pages Yes string array title No non-empty string root No boolean icon No string defaultOpen No boolean nav.sidebar No section or combined nav.label No string nav.mode No string","Lint rules\n\nSchema, link, and navigation checks. CLI and library API.\n\nLint rules\n\nCustom schemas\n\nPass a Valibot schema to extend or replace the defaults Once you provide a custom schema, unknown-field warnings apply to that schema. Add --error-unknown in CI to keep your contract strict.\n\n```ts import * as v from \"valibot\"; import { lintDocs } from \"leadtype/lint\"; const customFrontmatter = v.object({ title: v.pipe(v.string(), v.minLength(1)), audience: v.picklist([\"beginner\", \"advanced\"]), }); await lintDocs({ srcDir: \"docs\", schemas: { frontmatter: customFrontmatter }, }); ```","Lint rules\n\nSchema, link, and navigation checks. CLI and library API.\n\nLint rules\n\nPractical guidance\n\nRun lint before leadtype generate so content errors fail fast. Use --format github in GitHub Actions and --format json in any other CI. Treat unresolved-placeholder as a content bug first — usually a missing entry in availableIn or a stale URL template. After a docs move, lint and run meta.json updates together; they drift at the same time. For wiring lint into pipelines, see Validate in CI.","LLM files\n\nGenerate llms.txt for hosted websites and AGENTS.md for npm-bundled offline reading.\n\nLLM files\n\nThe leadtype/llm entry point produces four flavors of agent-facing output, all derived from the same docs source generateLlmsTxt — for hosted websites. Emits the /llms.txt convention with root-relative markdown mirror links. generateLLMFullContextFiles — root /llms-full.txt fallback containing all generated markdown docs. Pairs with generateLlmsTxt . generateAgentReadabilityArtifacts — docs-scoped sitemap.xml , sitemap.md , robots.txt , and JSON manifest data that a host app can merge into site-level files. generateAgentsMd — for npm-bundled docs. Emits an AGENTS.md index with relative ./docs/ A library that does one thing well. - Helper that handles the boring parts. - Type-safe by default. - Works in any runtime. ## Best Starting Points - [Documentation](/docs/index.md) - [Quickstart](/docs/quickstart.md) ## Get Started Five-minute happy path and the mental model. - [Quickstart](/docs/quickstart.md): Install and run the pipeline. - [How it works](/docs/how-it-works.md): The mental model. ## Reference CLI flags and conversion APIs. - [CLI](/docs/reference/cli.md): Every flag. ```","LLM files\n\nGenerate llms.txt for hosted websites and AGENTS.md for npm-bundled offline reading.\n\nLLM files\n\nTypical sequence\n\ngenerateLLMFullContextFiles and generateAgentReadabilityArtifacts read from A library that does one thing well. These docs ship inside the package so coding agents can read them offline. Open the topic file you need from the list below — paths are relative to this file. ## Get Started - [Quickstart](./docs/quickstart.md): Install and run the pipeline. - [How it works](./docs/how-it-works.md): The mental model. ## Reference - [CLI](./docs/reference/cli.md): Every flag. ```","LLM files\n\nGenerate llms.txt for hosted websites and AGENTS.md for npm-bundled offline reading.\n\nLLM files\n\nresolveDocsNavigation\n\nSame group-resolution logic the LLM files use, but returns the navigation manifest as a plain object — useful for driving a sidebar UI Write the result to src/generated/docs-nav.json and import it from your sidebar component Now your sidebar can import a static manifest with the same group tree the LLM files use.\n\n```ts const navigation = await resolveDocsNavigation({ srcDir: \".\", baseUrl: \"https://leadtype.dev\", groups: docsConfig.groups, }); if (navigation.unknown.length > 0) { for (const { urlPath, slug } of navigation.unknown) { process.stderr.write(`error: ${urlPath} declares unknown group \"${slug}\".\\n`); } process.exit(1); } ``` ```ts import { mkdir, writeFile } from \"node:fs/promises\"; await mkdir(\"src/generated\", { recursive: true }); await writeFile( \"src/generated/docs-nav.json\", `${JSON.stringify(navigation, null, 2)}\\n` ); ```","LLM files\n\nGenerate llms.txt for hosted websites and AGENTS.md for npm-bundled offline reading.\n\nLLM files\n\nTables of contents\n\nFor the heading slug contract and renderer wiring, see Render MDX and TOC. This section covers the build-time APIs only. resolveDocsNavigation includes a toc array on every page by default. The default range is h2 – h3 , which keeps page-level h1 titles out of sidebars. If you only need TOC data and not the full group tree, call resolveDocsTableOfContents For custom pipelines, extractDocsTableOfContents accepts a markdown or MDX string plus page URL metadata and returns plain JSON. It ignores frontmatter and fenced code blocks, and it uses the same slugifyDocsHeading helper that rendered headings must use to keep id attributes in sync.\n\n```ts const navigation = await resolveDocsNavigation({ srcDir: \".\", baseUrl: \"https://leadtype.dev\", groups: docsConfig.groups, toc: { minLevel: 2, maxLevel: 4 }, }); ``` ```ts const pages = await resolveDocsTableOfContents({ srcDir: \".\", baseUrl: \"https://leadtype.dev\", }); ```","LLM files\n\nGenerate llms.txt for hosted websites and AGENTS.md for npm-bundled offline reading.\n\nLLM files\n\nGroup design\n\nThe groups you pass to these APIs come from docs.config.ts . Two principles Use groups for routing, not sharding. Groups organize llms.txt , navigation, search metadata, and AGENTS.md . The root llms-full.txt remains the broad fallback. Write group descriptions for routing, not flavor text. Agents read those descriptions to decide which pages to load. \"How to install and run\" beats \"Welcome to our guides!\"","LLM files\n\nGenerate llms.txt for hosted websites and AGENTS.md for npm-bundled offline reading.\n\nLLM files\n\nBase URL precedence\n\nPass baseUrl explicitly, or use environment variables for layered fallback The package-specific LEADTYPE AGENT BASE URL lets each package override an org-wide default. BASE URL covers most CI/deployment platforms, and a final hardcoded fallback keeps local builds working without env setup.\n\n```ts const baseUrl = process.env.LEADTYPE_AGENT_BASE_URL || process.env.BASE_URL || process.env.PORTLESS_URL || \"https://leadtype.dev\"; ```","Remark plugins\n\nThe default plugin stack that flattens MDX components into markdown.\n\nRemark plugins\n\nThe remark stack is what turns interactive MDX into agent-readable markdown. Imports get stripped first, placeholders get resolved, then each named component is flattened into a markdown equivalent. Order matters.\n\n```ts import { defaultRemarkPlugins, remarkInclude, remarkTypeTableToMarkdown, } from \"leadtype/remark\"; ```","Remark plugins\n\nThe default plugin stack that flattens MDX components into markdown.\n\nRemark plugins\n\nThe default stack\n\ndefaultRemarkPlugins runs the stack in this order 1. remarkRemoveImports — strip MDX import and export statements. 2. remarkRemoveJsxComments — strip / ... / JSX comments. 3. remarkResolveDocPlaceholders — replace framework and similar placeholders in URLs. 4. remarkAudienceToMarkdown — include ri --> rj --> rd --> rau --> rs --> rc --> rcd --> rdt --> rm --> rct --> rst --> rt --> rtt --> ra --> rts --> rft --> rp --> re --> out` ```","Remark plugins\n\nThe default plugin stack that flattens MDX components into markdown.\n\nRemark plugins\n\nThe default stack\n\n p !== remarkTypeTableToMarkdown), [remarkTypeTableToMarkdown, { basePath: process.cwd() }], ]; ``` ```mdx ```","Remark plugins\n\nThe default plugin stack that flattens MDX components into markdown.\n\nRemark plugins\n\nPlugin selection rules\n\nUse defaultRemarkPlugins for any agent-facing or LLM output. Add remarkInclude when docs are composed from shared fragments. Use individual plugins only when you intentionally want to omit a flattener e.g. you don't use /docs/search-index.json /docs/search-content.json ```","Search\n\nStatic search index, runtime helpers, and source-grounded answer streaming.\n\nSearch\n\nRuntime search\n\nThe runtime is edge-safe — no Node APIs, works on Vercel, Cloudflare, and anywhere else Results include heading paths, hash URLs, and snippets ready for a search UI. Search is still local and dependency-free, but it is not exact-token only. Query terms expand through lightweight stemming, prefix matches, typo-tolerant fallbacks, and a small built-in synonym map. Exact matches keep the highest weight so API names and config keys stay precise. Pass synonyms when your docs use product-specific vocabulary\n\n```ts import { searchDocs, type DocsSearchIndex, type DocsSearchContentStore, } from \"leadtype/search\"; import indexJson from \"../public/docs/search-index.json\"; import contentJson from \"../public/docs/search-content.json\"; const results = searchDocs( indexJson as DocsSearchIndex, \"tabs install\", { content: contentJson as DocsSearchContentStore } ); ``` ```ts const results = searchDocs(index, \"starter\", { content, synonyms: { starter: [\"quickstart\", \"getting started\"], }, }); ```","Search\n\nStatic search index, runtime helpers, and source-grounded answer streaming.\n\nSearch\n\nReading docs at runtime\n\nThe same index doubles as a virtual filesystem. Three readers, picked by what you have Use readDocsContentFile when you need the entire page for context links . Use readDocsContentChunk when a search result already named the right heading.\n\n```ts import { listDocsContentFiles, readDocsContentFile, readDocsContentChunk, } from \"leadtype/search\"; const allFiles = listDocsContentFiles(index); const wholePage = readDocsContentFile(index, \"guides/quickstart\", content); const oneChunk = readDocsContentChunk(index, \"chunk-0\", content); ```","Search\n\nStatic search index, runtime helpers, and source-grounded answer streaming.\n\nSearch\n\nSource-grounded answers\n\ncreateAnswerContext turns a query plus retrieved chunks into a system and prompt you pass to any model The system message instructs the model to answer only from the retrieved context, cite sources with 1 -style references, and say so when the context is insufficient.\n\n```ts import { createAnswerContext } from \"leadtype/search\"; const context = createAnswerContext(index, \"how do I run lint?\", { content, productName: \"My Library\", }); // → { system, prompt, sources } ```","Search\n\nStatic search index, runtime helpers, and source-grounded answer streaming.\n\nSearch\n\nStreaming via provider entry points\n\nThree thin wrappers around createAnswerContext that stream a Response and surface sources separately. Use one matching your runtime response is a plain text Response. sources is metadata for citation links — display it separately, don't embed it in the streamed answer. For TanStack, pass an explicit adapter . For Cloudflare, build one with createCloudflareDocsAdapter provider, model, options binding env.AI.gateway \"docs\" .\n\n```ts import { streamDocsAnswer } from \"leadtype/search/vercel\"; // Vercel AI SDK / AI Gateway import { streamDocsAnswer } from \"leadtype/search/tanstack\"; // TanStack AI import { streamDocsAnswer } from \"leadtype/search/cloudflare\"; // Cloudflare AI Gateway / Workers AI ``` ```ts const { response, sources } = streamDocsAnswer({ index, content, query, model: \"openai/gpt-5.5\", productName: \"My Library\", }); ```","Search\n\nStatic search index, runtime helpers, and source-grounded answer streaming.\n\nSearch\n\nBash tool adapters\n\nWhen you want an agent to explore docs with shell commands instead of receiving pre-selected chunks The adapter exposes a read-only virtual /docs filesystem with ls , cat , find , grep , and rg . Network commands, code execution, and writes are disabled. Use createDocsBashTool for Vercel AI SDK tool sets and createDocsBashTools for TanStack-compatible tools over the same filesystem.\n\n```ts import { createDocsBashTool, createDocsBashTools } from \"leadtype/search/bash\"; const { tools, instructions } = await createDocsBashTool(index, content); ```","Search\n\nStatic search index, runtime helpers, and source-grounded answer streaming.\n\nSearch\n\nAbuse guards\n\nReusable utilities for the request path Helper Purpose -- -- validateDocsQuery Trim and cap query text. readJsonWithLimit Reject oversized JSON bodies before parse. getClientIdentifier Read common proxy IP headers. createMemoryRateLimiter Implements RateLimiter for demos. The in-memory limiter is fine for demos. Production apps should adapt the RateLimiter interface to a shared store — Redis, Vercel KV, Cloudflare KV, or Durable Objects.","Search\n\nStatic search index, runtime helpers, and source-grounded answer streaming.\n\nSearch\n\nWhen to add embeddings\n\nStart with the local index. It is static, cheap, edge-safe, and fast for exact API names, config keys, error messages, and paths. Add embeddings only when Users search with vocabulary that doesn't match the docs e.g. \"make it faster\" matching a \"performance optimization\" page . Your docs grow past tens of thousands of chunks and the cold-start memory hit becomes noticeable. Even then, keep the lexical index for exact matches and layer embeddings on top — they're complementary, not replacements."]} +{"version":2,"generatedAt":"2026-05-13T16:50:59.893Z","chunks":["Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nLeadtype does not ship UI components. Your docs app owns runtime rendering, styling, and accessibility — it only has to honor a small naming contract so the remark pipeline can flatten each component into markdown for agents, search, and llms-full.txt .","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nWhy flatten at all?\n\nInteractive MDX components like ./shared/session-flow.mdx ``` ```mdx ``` ```mdx ``` ```mdx
This content can be reused in multiple pages.
```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nEach section below shows the authored MDX and a live render.","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nCallout\n\nWraps supporting context, warnings, or tips. Variants change styling but flatten identically into a blockquote. ℹ️ Info Heads up Callouts wrap supporting context, warnings, or tips. The remark pipeline flattens them into blockquotes so agents still see the content. ⚠️ Warning Don't do this Variants like warning, success, error, and tip change the styling but flatten identically.\n\n```tsx Body content goes here. ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nCards\n\nA grid of short, linked entry points. Flattens to a bullet list of links. Convert Remark Search\n\n```tsx ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nSteps\n\nNumbered walkthroughs. Flattens to an ordered list with bold step titles. 1. Author docs in MDX Use the components in this list as authoring affordances. 2. Run the conversion leadtype generate writes flattened markdown to public/docs/ . 3. Serve both formats HTML for humans, .md for agents — same URL, content negotiated by the Accept header.\n\n```tsx Use the components in this list. `leadtype generate` writes flattened markdown. ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nTabs\n\nGroup equivalent content. Flattens to bold headings followed by content so agents do not need a JSX-aware renderer to read every variant. tanstack-start Use a Vite middleware dev/preview and a Nitro middleware prod to negotiate the Accept header. next-js Wire content negotiation in middleware.ts and serve .md from a route handler. vite A configureServer middleware is enough for static deployments where .md files live in public/ .\n\n```tsx ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nCommandTabs\n\nPackage-manager-aware install or run commands. Flattens to a markdown table with one row per manager. Use mode=\"install\" when command is a package name, mode=\"run\" when command is a CLI name, and mode=\"create\" for starter commands. Use the commands prop for exact per-manager overrides. Package manager Command -- -- npm npm install leadtype pnpm pnpm add leadtype yarn yarn add leadtype bun bun add leadtype Package manager Command -- -- npm npx leadtype lint pnpm pnpm dlx leadtype lint yarn yarn dlx leadtype lint bun bunx leadtype lint\n\n```tsx ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nPrompt\n\nDisplays an agent-ready prompt with a copy action. Flattening preserves the prompt body as a fenced prompt block so copied instructions also survive in .md , root llms-full.txt , and bundled AGENTS.md output. Copy prompt for your coding agent Use this after generating artifacts.\n\n```tsx Inspect `public/docs/agent-readability.json`, then wire markdown responses before the HTML docs route. ``` ```prompt Inspect `public/docs/agent-readability.json`, then wire markdown responses before the HTML docs route. ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nAudience\n\nSplits browser-only and agent-only guidance without forking the page. Human content renders in the docs UI and is omitted from markdown output; agent content is hidden in the browser and included in generated markdown. This sentence appears only in generated markdown for agents.\n\n```tsx Click the robot icon in the header. Read generated markdown before editing runtime routes. ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nFileTree\n\nShows project structure in guides and release instructions. Flattening emits a fenced text tree so agents can read the same hierarchy without JSX.\n\n```tsx ``` ```text public/ ├── llms.txt └── docs/ └── index.md ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nAccordion\n\nCollapsible details for secondary content. Flattening ignores open/closed state and emits every item — accordions are not a place to hide content from agents. When should I use accordions? Use them for supporting details, troubleshooting notes, and optional reference material. Closed content is still flattened by the remark pipeline. Are accordions a good place to hide content from LLMs? No. Conversion ignores the open/closed state and emits everything inside.\n\n```tsx Use them for supporting details and optional reference material. ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nTopicSwitcher\n\nNavigation across equivalent docs topics — frameworks, SDKs, runtimes, deployment targets, product areas. Reader-facing only; it does not automatically read LLM topic config. Framework React — React integration Vue — Vue integration Svelte — Svelte integration\n\n```tsx ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nTypeTable and ExtractedTypeTable\n\nTypeTable is for explicit prop or type rows you already know. ExtractedTypeTable reads a TypeScript file at conversion time and extracts the table from a named type — keep its path stable. Property Type Description Default Required -- -- -- -- -- title string Heading rendered above the callout body. - ✅ Required variant CalloutVariant Visual treatment for the callout. info Optional deprecated \\ \\ boolean\\ \\ deprecated Marks the row as deprecated. false Optional\n\n```tsx ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nExample\n\nData-driven preview and source examples. The host component receives code as data; add file loaders or dynamic imports outside leadtype when an example needs app-specific behavior.\n\n```tsx The host app owns styling and runtime components while leadtype owns conversion. ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nComponent reference\n\nMermaid\n\nDiagrams authored as plain text. Renders client-side as interactive SVG. Flattening preserves the source as a fenced mermaid block so other tools can render the diagram from the markdown copy.\n\n```tsx |remark| Markdown Markdown -->|llms.txt| Agents`} /> ``` ```mermaid graph LR MDX -->|remark| Markdown Markdown -->|search index| API Markdown -->|llms.txt| Agents ```","Components\n\nMDX components the pipeline knows how to flatten into agent-readable markdown.\n\nComponents\n\nGuidelines\n\nKeep runtime components in your docs app. Leadtype stays out of UI. Keep component names stable. Renaming groups: [authoring, docs-site, reference]\"] page1[\"page A
group: authoring\"] page2[\"page B
group: docs-site\"] page3[\"page C
group: docs-site\"] resolver[\"group resolver\"] nav[\"navigation tree\"] llms[\"llms.txt sections\"] agents[\"AGENTS.md sections\"] cfg --> resolver page1 --> resolver page2 --> resolver page3 --> resolver resolver --> nav resolver --> llms resolver --> agents ```","Frontmatter\n\nRequired fields, group semantics, and how authored MDX becomes a navigation tree.\n\nFrontmatter\n\nHow groups become a nav tree\n\nNested groups\n\nDeclare children in the config to build deeper trees A page sets group remote-source and lands in that nested slot. Only leaf groups no children directly contain pages; non-leaf groups are headings only.\n\n```ts { slug: \"docs-site\", title: \"Build a Docs Site\", children: [ { slug: \"remote-source\", title: \"Remote source\" }, { slug: \"build-a-docs-site\", title: \"Build a docs site\" }, ], } ```","Frontmatter\n\nRequired fields, group semantics, and how authored MDX becomes a navigation tree.\n\nFrontmatter\n\nOptional fields\n\nThe default lint schema also accepts Property Type Description Default Required -- -- -- -- -- icon string Icon name resolved by your sidebar component. - Optional deprecated boolean Marks the page as deprecated in nav and search. - Optional deprecatedReason string Short message paired with deprecated. - Optional experimental boolean Marks the page as experimental. - Optional canary boolean Hides from stable channels. - Optional new boolean Highlights the page as recently added. - Optional draft boolean Excludes from generation entirely. - Optional tags string\\ Free-form tags for search facets. - Optional availableIn Array\\<\\ framework, url?, title? Cross-framework availability map for TopicSwitcher pages. - Optional full boolean Layout hint for docs UIs that support full-width pages. - Optional lastModified and lastAuthor are filled in automatically when you pass --enrich-git to the CLI. Don't author them by hand.","Frontmatter\n\nRequired fields, group semantics, and how authored MDX becomes a navigation tree.\n\nFrontmatter\n\nLint rules\n\nleadtype lint enforces the schema, so violations surface in CI before they reach a build. The relevant rules schema — a required field is missing or has the wrong type. unknown-field — a top-level field isn't in the schema warn by default; --error-unknown to fail . parse-error — frontmatter or meta.json doesn't parse. invalid-link — a /docs/... link points to a route that doesn't exist. unresolved-placeholder — a doc URL still contains an unresolved framework placeholder. cross-framework-link — a framework-scoped page links to another framework's docs. See Lint rules for the full reference and how to extend the schema.","Frontmatter\n\nRequired fields, group semantics, and how authored MDX becomes a navigation tree.\n\nFrontmatter\n\nWhat this gives you\n\nOnce frontmatter is consistent, the rest of the pipeline works without per-page configuration. The same group value drives The sidebar position The llms.txt section Search metadata and AGENTS.md grouping The search filtering UI if you build one Cross-framework link checks in lint One field, one source of truth.","Add search\n\nGenerate a static docs search index, query it at runtime, and optionally stream source-grounded answers.\n\nAdd search\n\nLeadtype search is static by default. Build time produces two JSON files; runtime code imports or fetches those files and queries them without a database.","Add search\n\nGenerate a static docs search index, query it at runtime, and optionally stream source-grounded answers.\n\nAdd search\n\nGenerate the files\n\nleadtype generate writes search files in site mode If you run the pipeline from scripts, call the search generator after conversion This writes The index contains compact ranking data. The content store contains the text used for excerpts and answer context.\n\n```bash npx leadtype generate --src . --out public --base-url https://example.com ``` ```ts import { generateDocsSearchFiles } from \"leadtype/search/node\"; await generateDocsSearchFiles({ outDir: \"public\", baseUrl: \"https://example.com\", }); ``` ```txt public/docs/search-index.json public/docs/search-content.json ```","Add search\n\nGenerate a static docs search index, query it at runtime, and optionally stream source-grounded answers.\n\nAdd search\n\nQuery at runtime\n\nResults include page URLs, heading paths, hash URLs, and snippets. Render them in your own search UI.\n\n```ts import { searchDocs, type DocsSearchContentStore, type DocsSearchIndex, } from \"leadtype/search\"; import contentJson from \"../public/docs/search-content.json\"; import indexJson from \"../public/docs/search-index.json\"; const index = indexJson as DocsSearchIndex; const content = contentJson as DocsSearchContentStore; const results = searchDocs(index, \"run lint\", { content }); ```","Add search\n\nGenerate a static docs search index, query it at runtime, and optionally stream source-grounded answers.\n\nAdd search\n\nAdd vocabulary aliases\n\nSearch starts with lexical matching, stemming, prefix matches, typo-tolerant fallbacks, and a small built-in synonym map. Add product-specific synonyms only when users search with words your docs do not use\n\n```ts const results = searchDocs(index, \"starter\", { content, synonyms: { starter: [\"quickstart\", \"getting started\"], }, }); ```","Add search\n\nGenerate a static docs search index, query it at runtime, and optionally stream source-grounded answers.\n\nAdd search\n\nOptional AI answers\n\nUse source-grounded answers only after basic search works. Leadtype retrieves chunks from the static index, builds a constrained prompt, and leaves model choice to the provider entry point you import Display sources next to the streamed response. Do not ask the model to answer from memory; the answer context is built from retrieved docs chunks.\n\n```ts import { streamDocsAnswer } from \"leadtype/search/vercel\"; const { response, sources } = streamDocsAnswer({ index, content, query: \"How do I run docs lint in CI?\", model: \"openai/gpt-5.5\", productName: \"My Library\", }); ```","Add search\n\nGenerate a static docs search index, query it at runtime, and optionally stream source-grounded answers.\n\nAdd search\n\nGuard the endpoint\n\nFor API routes that accept user queries, use the request helpers from leadtype/search validateDocsQuery to trim and cap query text. readJsonWithLimit to reject oversized JSON bodies. getClientIdentifier to read common proxy IP headers. createMemoryRateLimiter for demos. Production apps should adapt the rate limiter interface to a shared store such as Redis, Vercel KV, Cloudflare KV, or Durable Objects.","Add search\n\nGenerate a static docs search index, query it at runtime, and optionally stream source-grounded answers.\n\nAdd search\n\nVerify\n\npublic/docs/search-index.json and public/docs/search-content.json exist and are non-empty. Searching for an exact API name returns the expected reference page. Searching for a guide phrase returns a result with a section hash. AI answers cite sources from the returned sources metadata.","Build a docs site\n\nPick the right leadtype integration shape for your docs app, and what each path gives you.\n\nBuild a docs site\n\nLeadtype offers two integration shapes for a docs site. Pick the one that fits how your app is built — both produce the same content, just at different layers of your stack.","Build a docs site\n\nPick the right leadtype integration shape for your docs app, and what each path gives you.\n\nBuild a docs site\n\nPick your path\n\nPath When to choose What you wire -- -- -- Source primitive Most cases. You're building a Next, Vite, Astro, or Fumadocs app and compile MDX with your bundler. createDocsSource or leadtype/fumadocs + mdxSourcePlugins Static artifacts Your runtime needs flat files on disk CDN-only deploy, static export, agent-only consumption, multi-app sharing . leadtype generate CLI in your build script The two paths are not mutually exclusive — you can use the source primitive for your UI and still run leadtype generate for the llms.txt and agent-readability artifacts.\n\n```mermaid flowchart LR src[\"source MDX
docs/*.mdx + meta.json\"] primitive[\"createDocsSource()
(leadtype/mdx + leadtype/fumadocs)\"] cli[\"leadtype generate
(CLI)\"] app[\"docs app
(Next, Astro, Vite, Fumadocs)\"] artifacts[\"public/
llms.txt · docs/*.md
search-index · sitemap\"] src --> primitive --> app src --> cli --> artifacts --> app ```","Build a docs site\n\nPick the right leadtype integration shape for your docs app, and what each path gives you.\n\nBuild a docs site\n\nSource primitive the common path\n\nIf your docs app compiles MDX through a bundler, use the source primitive. It gives your runtime loadPage slug returning frontmatter + AST + serialized markdown + TOC getNavigation returning the resolved group tree buildSearchIndex returning a static search index resolveInclude for programmatic partial loading → Use the source primitive — generic recipe → Integrate with Fumadocs — first-party adapter","Build a docs site\n\nPick the right leadtype integration shape for your docs app, and what each path gives you.\n\nBuild a docs site\n\nStatic artifacts the CLI path\n\nIf your runtime needs files on disk — for a CDN-only deploy, a static export, a separate agent-only service, or to share artifacts across multiple sibling apps — run the CLI. It writes public/llms.txt and public/llms-full.txt public/docs/ .md markdown mirrors public/docs/search-index.json + search-content.json public/docs/sitemap.xml , sitemap.md , robots.txt , agent-readability.json → Generate static artifacts — CLI workflow","Build a docs site\n\nPick the right leadtype integration shape for your docs app, and what each path gives you.\n\nBuild a docs site\n\nAdd the cross-cutting features\n\nWhichever path you pick, the same follow-on guides apply Add search — local search index + optional source-grounded answers Optimize docs for agents — markdown responses, llms.txt, discovery files Validate in CI — lint frontmatter, meta.json, internal links","Build a docs site\n\nPick the right leadtype integration shape for your docs app, and what each path gives you.\n\nBuild a docs site\n\nConfigure product and groups\n\nBoth paths read the same docs/docs.config.ts . Source repos own this file so groups and product metadata version with the docs Pages declare group in frontmatter; the config declares titles, order, and descriptions. See Frontmatter for the page-level schema.\n\n```ts import { defineDocsConfig } from \"leadtype\"; export default defineDocsConfig({ product: { name: \"c15t\", summary: \"Consent infrastructure for modern web apps.\", bullets: [\"Framework integrations.\", \"Consent primitives.\", \"Audit-friendly docs.\"], bestStartingPoints: [{ urlPath: \"/docs\" }, { urlPath: \"/docs/quickstart\" }], }, groups: [ { slug: \"get-started\", title: \"Get Started\" }, { slug: \"guides\", title: \"Guides\" }, { slug: \"reference\", title: \"Reference\" }, ], }); ```","Generate static artifacts\n\nRun leadtype generate from your build pipeline to write llms.txt, markdown mirrors, search index, sitemap, and agent-readability files to disk.\n\nGenerate static artifacts\n\nUse this path when your runtime needs files on disk — a CDN-only deploy, a static export, a separate agent-only service, or multiple sibling apps sharing one corpus. The leadtype generate CLI walks your MDX source and writes a complete set of files into public/ . For app runtimes that compile MDX through a bundler Next, Astro, Vite, Fumadocs , prefer the source primitive — it skips the disk round-trip.","Generate static artifacts\n\nRun leadtype generate from your build pipeline to write llms.txt, markdown mirrors, search index, sitemap, and agent-readability files to disk.\n\nGenerate static artifacts\n\nThe flow\n\n```mermaid flowchart LR repo[\"source repo
docs/*.mdx\"] clone[\"checkout or clone
.docs-src/c15t\"] lint[\"leadtype lint\"] generate[\"leadtype generate\"] public[\"public/
llms.txt · llms-full.txt
docs/*.md
search · agent metadata\"] app[\"any consumer
(docs app, agent, CDN)\"] repo --> clone --> lint --> generate --> public --> app ```","Generate static artifacts\n\nRun leadtype generate from your build pipeline to write llms.txt, markdown mirrors, search index, sitemap, and agent-readability files to disk.\n\nGenerate static artifacts\n\nFetch the source repo\n\nIn CI, check out the docs source before the build. For a public repo, a shallow clone is enough For private repos, use your CI platform's checkout action or a read-only deploy key. The important part is that leadtype receives a normal filesystem path whose root contains docs/ .\n\n```bash rm -rf .docs-src/c15t git clone --depth 1 https://github.com/c15t/c15t .docs-src/c15t ```","Generate static artifacts\n\nRun leadtype generate from your build pipeline to write llms.txt, markdown mirrors, search index, sitemap, and agent-readability files to disk.\n\nGenerate static artifacts\n\nLint before generate\n\nRun lint against the fetched docs before writing generated output. Lint fails with file and line context, which is easier to debug than a later app build using stale artifacts\n\n```bash npx leadtype lint .docs-src/c15t/docs \\ --format github \\ --error-unknown \\ --max-warnings 0 ```","Generate static artifacts\n\nRun leadtype generate from your build pipeline to write llms.txt, markdown mirrors, search index, sitemap, and agent-readability files to disk.\n\nGenerate static artifacts\n\nGenerate\n\nPoint --src at the fetched repo root and --out at the directory you'll serve That command writes public/llms.txt and public/llms-full.txt public/docs/ .md public/docs/search-index.json and public/docs/search-content.json public/docs/sitemap.xml , sitemap.md , robots.txt , and agent-readability.json Use --json in CI so automation can record resolved groups, output files, filters, and search index stats.\n\n```bash npx leadtype generate \\ --src .docs-src/c15t \\ --out public \\ --base-url https://docs.example.com \\ --json ```","Generate static artifacts\n\nRun leadtype generate from your build pipeline to write llms.txt, markdown mirrors, search index, sitemap, and agent-readability files to disk.\n\nGenerate static artifacts\n\nMulti-source mounting\n\nSome projects keep docs and release notes side by side instead of putting everything under docs/ Repeat --docs-dir to include those folders in the same generated corpus By default, extra sources are mounted under /docs/ {title ?

{title}

: null}
{children}
); } ```","Integrate with Fumadocs\n\nWire leadtype's content layer into a fumadocs app for nav, search, and includes.\n\nIntegrate with Fumadocs\n\nLoad a page from a server component\n\nIf you prefer fumadocs's built-in page resolution, call source.getPage slug and import the source .mdx directly through fumadocs-mdx as you normally would — the mdxSourcePlugins preset will resolve includes during MDX compilation.\n\n```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 (

{page.title}

); } ```","Integrate with Fumadocs\n\nWire leadtype's content layer into a fumadocs app for nav, search, and includes.\n\nIntegrate with Fumadocs\n\nAdd search\n\nBuild a search index from the same source For provider-specific search Vercel AI, TanStack, Cloudflare , wire the bundle into a leadtype/search/ adapter. See Search.\n\n```ts title=\"app/api/search/route.ts\" import { source } from \"@/content/source\"; const bundle = await source.leadtype.buildSearchIndex(); export async function GET() { return Response.json(bundle.index); } ```","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\nUse this guide when you already have a docs site and want agents to find, fetch, attribute, and cite the same content humans read in the browser. Leadtype handles the generated files. Your app wires those files into routing and HTML. The default output shape is based on the repo's agent evals. See Evals for the benchmark summary and the open question around larger-corpus llms-full.txt scaling.","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\nWhat good looks like\n\nAn agent-readable docs site has four layers 1. Discovery files /llms.txt , /sitemap.xml , /sitemap.md , and /robots.txt tell agents what exists and where to start. 2. Markdown retrieval Each docs page has a markdown mirror at /docs/page.md , and agent requests to /docs/page can receive markdown instead of HTML. 3. Structured HTML metadata Human HTML pages include JSON-LD, canonical links, and markdown alternate links so agents can extract page identity without guessing from the DOM. 4. Attribution metadata Markdown responses include canonical url and last updated frontmatter so copied content keeps its source and freshness.","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\n1. Generate the artifacts\n\nRun the site-mode pipeline before your app build This writes the docs-scoped files under public/docs/ and the top-level public/llms.txt The generated agent-readability.json manifest is the bridge between build-time content and runtime requests. It contains page URLs, markdown mirror paths, titles, descriptions, group navigation, and freshness dates.\n\n```bash npx leadtype generate \\ --src . \\ --out public \\ --base-url https://leadtype.dev \\ --name \"My product\" \\ --summary \"One sentence about the product.\" ``` ```txt public/ ├── llms.txt ├── llms-full.txt └── docs/ ├── index.md ├── quickstart.md ├── llms.txt ├── sitemap.xml ├── sitemap.md ├── robots.txt └── agent-readability.json ```","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\n2. Add one middleware\n\nPut the agent-readable routes before your HTML docs route. This Node/Bun example handles root discovery files, docs-scoped discovery files, direct .md URLs, and Accept text/markdown requests in one place If you also have marketing, blog, changelog, or product pages, pass them through the optional pages field — the regenerator merges them into the rebased output The other generated artifacts — /llms.txt , /docs/llms.txt , /llms-full.txt , /docs/agent-readability.json — use root-relative links and serve fine as static files straight from public/ . Keep the docs-scoped versions too /docs/sitemap.xml etc. . Audits and agents may request both /sitemap.xml and /docs/sitemap.xml , especially when the audited URL is /docs .\n\n```ts import { readFile } from \"node:fs/promises\"; import { join } from \"node:path\"; import manifestJson from \"../public/docs/agent-readability.json\" with { type: \"json\", }; import { createAgentMarkdownResponse, createRobotsTxtResponse, createSitemapMarkdownResponse, createSitemapXmlResponse, type AgentReadabilityManifest, type MarkdownMirrorTarget, } from \"leadtype/llm/readability\"; const manifest = { ...manifestJson, version: 1, } as AgentReadabilityManifest; async function readMarkdownFile( target: MarkdownMirrorTarget ): Promise { try { return await readFile(join(process.cwd(), \"public\", target.filePath), \"utf8\"); } catch (error) { if ( typeof error === \"object\" && error !== null && \"code\" in error && (error.code === \"ENOENT\" || error.code === \"ENOTDIR\") ) { return null; } throw error; } } export async function handleDocsRequest( request: Request ): Promise { if (request.method !== \"GET\" && request.method !== \"HEAD\") { return null; } const url = new URL(request.url); const requestOrigin = url.origin; switch (url.pathname) { case \"/sitemap.xml\": case \"/docs/sitemap.xml\": return createSitemapXmlResponse({ manifest, requestOrigin }); case","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\n2. Add one middleware\n\nstOrigin = url.origin; switch (url.pathname) { case \"/sitemap.xml\": case \"/docs/sitemap.xml\": return createSitemapXmlResponse({ manifest, requestOrigin }); case \"/sitemap.md\": case \"/docs/sitemap.md\": return createSitemapMarkdownResponse({ manifest, requestOrigin }); case \"/robots.txt\": return createRobotsTxtResponse({ manifest, requestOrigin }); case \"/docs/robots.txt\": return createRobotsTxtResponse({ manifest, requestOrigin, sitemapUrlPath: \"/docs/sitemap.xml\", }); default: return createAgentMarkdownResponse({ urlPath: url.pathname, method: request.method, headers: Object.fromEntries(request.headers), manifest, readMarkdownFile, requestOrigin, }); } } ``` ```ts return createSitemapXmlResponse({ manifest, requestOrigin, pages: [...manifest.pages, ...marketingPages, ...blogPages], }); ```","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\n3. Add JSON-LD to docs pages\n\nUse the manifest entry for the current page and render Schema.org JSON-LD into the HTML head Use renderJsonLd page, manifest if your framework has a typed metadata API. Use renderJsonLdScript page, manifest if your framework expects an HTML string. Also add canonical and markdown alternate links The JSON-LD gives agents the page title, description, canonical URL, last modified date, and breadcrumbs without scraping your rendered layout.\n\n```ts import agentManifest from \"../public/docs/agent-readability.json\"; import { renderJsonLd, renderJsonLdScript } from \"leadtype/llm/readability\"; const page = agentManifest.pages.find( (entry) => entry.urlPath === \"/docs/quickstart\" ); if (page) { const jsonLd = renderJsonLd(page, agentManifest); const script = renderJsonLdScript(page, agentManifest); } ``` ```html ```","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\n4. Return markdown to agents\n\nThe middleware above uses createAgentMarkdownResponse . It returns a Web Response or null when the path is not an agent-oriented markdown request and handles Accept text/markdown and Accept text/plain content negotiation q-values respected . Known AI user-agent headers GPTBot, ClaudeBot, Bingbot, AmazonBot, MetaExternalAgent, PerplexityBot, MistralBot, AppleBot, ByteSpider, YouBot, … . Direct .md URLs such as /docs/quickstart.md . canonical url and last updated frontmatter aliases injected automatically. 200 markdown responses for missing docs pages, so agents do not discard the body. Content-Type text/markdown; charset=utf-8 , Vary Accept , User-Agent , Link <… ; rel=\"canonical\" , Cache-Control public, max-age=300, must-revalidate . readMarkdownFile may be sync or async. In Node/Bun, read from disk. In Cloudflare, fetch from KV/R2 or an asset binding. In Vercel Edge, fetch from the deployment's static asset URL. Put that logic wherever your framework can intercept docs requests before its HTML route Framework/runtime Where it usually goes -- -- TanStack Start / nitro server/middleware/agent-readability.ts h3 .","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\n4. Return markdown to agents\n\nn intercept docs requests before its HTML route Framework/runtime Where it usually goes -- -- TanStack Start / nitro server/middleware/agent-readability.ts h3 . One middleware handles both the markdown response and the sitemap/robots regenerators — runs in dev, preview, and prod. Nuxt server/middleware/agent-readability.ts h3 — same shape as the TanStack Start example. Next.js middleware.ts Edge or a catch-all route handler before the docs page. Astro An endpoint at pages/docs/ ...slug .md.ts or astro middleware . Cloudflare Workers/Pages Worker fetch handler with KV/R2 asset binding for the markdown reader. Express/Hono/Fastify Middleware before the docs HTML route. Tip if you keep static sitemap.xml / sitemap.md / robots.txt files in your build output, your framework's static handler may serve them before your middleware can rebase URLs to the live origin. Either delete the static copies after the build so the middleware always runs or make sure your middleware is registered ahead of static-asset serving. Do not rewrite llms.txt , sitemap.xml , sitemap.md , robots.txt , llms-full.txt , or agent-readability.json to page markdown. The helper leaves those artifact paths alone.","Optimize docs for agents\n\nSet up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site.\n\nOptimize docs for agents\n\n4. Return markdown to agents\n\nWhy the sitemap and robots responses are regenerated, not static\n\nsitemap.xml 's }) { const { slug = [] } = await params; const page = await source.loadPage(slug); if (!page) notFound(); return (

{page.title}

{page.description ?

{page.description}

: null}
); } export async function generateStaticParams() { const pages = await source.listPages(); return pages.map((page) => ({ slug: page.slug })); } ```","Use the source primitive\n\nWire createDocsSource into Next, Astro, Vite, Nuxt, SvelteKit, or any MDX-aware bundler. Same primitive, multiple host shapes.\n\nUse the source primitive\n\nWire into your framework\n\nAstro Content Collections\n\nUse Astro's native content collection schema for typed frontmatter. Call source.loadPage from leadtype only when you need programmatic include resolution, search, or navigation. See Astro's content collections docs for the routing pattern.\n\n```ts title=\"astro.config.mjs\" import { defineConfig } from \"astro/config\"; import mdx from \"@astrojs/mdx\"; import { mdxSourcePlugins } from \"leadtype/mdx\"; export default defineConfig({ integrations: [mdx({ remarkPlugins: [...mdxSourcePlugins] })], }); ```","Use the source primitive\n\nWire createDocsSource into Next, Astro, Vite, Nuxt, SvelteKit, or any MDX-aware bundler. Same primitive, multiple host shapes.\n\nUse the source primitive\n\nWire into your framework\n\nTanStack Start\n\nGenerate docs-pages.json at build time by calling createDocsSource .listPages from a build script and writing each page's slug , urlPath , and globKey path relative to the catch-all route, POSIX separators .\n\n```ts title=\"vite.config.ts\" import mdx from \"@mdx-js/rollup\"; import { tanstackStart } from \"@tanstack/react-start/plugin/vite\"; import viteReact from \"@vitejs/plugin-react\"; import { mdxSourcePlugins } from \"leadtype/mdx\"; import remarkFrontmatter from \"remark-frontmatter\"; import { defineConfig } from \"vite\"; export default defineConfig({ plugins: [ { ...mdx({ providerImportSource: \"@mdx-js/react\", remarkPlugins: [remarkFrontmatter, ...mdxSourcePlugins], }), enforce: \"pre\", }, tanstackStart(), viteReact({ include: /\\.(mdx|[jt]sx?)$/ }), ], }); ``` ```tsx title=\"src/routes/docs/$.tsx\" import { createFileRoute, notFound } from \"@tanstack/react-router\"; import { type ComponentType, lazy, Suspense } from \"react\"; import docsPages from \"@/generated/docs-pages.json\"; const pagesBySlug = new Map( (docsPages as { slug: string[]; globKey: string }[]).map((p) => [ p.slug.join(\"/\"), p, ]) ); // import.meta.glob keys are POSIX paths; the build-time manifest writes // matching keys so each slug maps to its lazy-loaded MDX module.","Use the source primitive\n\nWire createDocsSource into Next, Astro, Vite, Nuxt, SvelteKit, or any MDX-aware bundler. Same primitive, multiple host shapes.\n\nUse the source primitive\n\nWire into your framework\n\nTanStack Start\n\ng.join(\"/\"), p, ]) ); // import.meta.glob keys are POSIX paths; the build-time manifest writes // matching keys so each slug maps to its lazy-loaded MDX module. const mdxModules = import.meta.glob<{ default: ComponentType }>( \"../../../content/docs/**/*.mdx\" ); export const Route = createFileRoute(\"/docs/$\")({ beforeLoad: ({ params }) => { if (!pagesBySlug.get((params._splat ?? \"\").replace(/\\/+$/, \"\"))) { throw notFound(); } }, component: DocsCatchAllRoute, }); function DocsCatchAllRoute() { const { _splat } = Route.useParams(); const page = pagesBySlug.get((_splat ?? \"\").replace(/\\/+$/, \"\")); if (!page) { return null; } const Mdx = lazy(mdxModules[page.globKey]); return ( ); } ```","Use the source primitive\n\nWire createDocsSource into Next, Astro, Vite, Nuxt, SvelteKit, or any MDX-aware bundler. Same primitive, multiple host shapes.\n\nUse the source primitive\n\nWire into your framework\n\nVite + @mdx-js/rollup works for Vue, Solid, Svelte starters\n\n```ts title=\"vite.config.ts\" import mdx from \"@mdx-js/rollup\"; import { mdxSourcePlugins } from \"leadtype/mdx\"; export default { plugins: [ mdx({ remarkPlugins: [...mdxSourcePlugins] }), // ...your framework plugin: viteReact / vue / solid / svelte ], }; ```","Use the source primitive\n\nWire createDocsSource into Next, Astro, Vite, Nuxt, SvelteKit, or any MDX-aware bundler. Same primitive, multiple host shapes.\n\nUse the source primitive\n\nWire into your framework\n\nNuxt\n\n```ts title=\"nuxt.config.ts\" import { mdxSourcePlugins } from \"leadtype/mdx\"; export default defineNuxtConfig({ modules: [\"@nuxtjs/mdc\"], mdc: { remarkPlugins: [...mdxSourcePlugins] }, }); ```","Use the source primitive\n\nWire createDocsSource into Next, Astro, Vite, Nuxt, SvelteKit, or any MDX-aware bundler. Same primitive, multiple host shapes.\n\nUse the source primitive\n\nWire into your framework\n\nSvelteKit + mdsvex\n\n```ts title=\"svelte.config.js\" import { mdsvex } from \"mdsvex\"; import { mdxSourcePlugins } from \"leadtype/mdx\"; export default { extensions: [\".svelte\", \".svx\", \".mdx\"], preprocess: mdsvex({ remarkPlugins: [...mdxSourcePlugins] }), }; ```","Use the source primitive\n\nWire createDocsSource into Next, Astro, Vite, Nuxt, SvelteKit, or any MDX-aware bundler. Same primitive, multiple host shapes.\n\nUse the source primitive\n\nWire into your framework\n\nPattern for any other host\n\nIf your framework's MDX integration accepts a remark plugin list, leadtype works. Three pieces every time 1. Add mdxSourcePlugins to the remark list so ( ), // ... Tabs, Tab, Steps, Step, Cards, Card, TypeTable, etc. }; ```","Use the source primitive\n\nWire createDocsSource into Next, Astro, Vite, Nuxt, SvelteKit, or any MDX-aware bundler. Same primitive, multiple host shapes.\n\nUse the source primitive\n\nBuild the sidebar from navigation\n\nEach page carries a toc field DocsTableOfContentsItem you can render as an \"On this page\" rail.\n\n```tsx title=\"components/sidebar.tsx\" import { source } from \"@/lib/source\"; export async function Sidebar({ currentUrlPath }: { currentUrlPath: string }) { const navigation = await source.getNavigation(); return ( ); } ```","Use the source primitive\n\nWire createDocsSource into Next, Astro, Vite, Nuxt, SvelteKit, or any MDX-aware bundler. Same primitive, multiple host shapes.\n\nUse the source primitive\n\nMatch heading slugs\n\nsource.loadPage .toc uses slugifyDocsHeading to derive anchor IDs. Your rendered heading components need matching id attributes Wire Heading2 and h3 , etc. into your mdxComponents map. Authors can still pin an explicit id on a heading.\n\n```tsx import { slugifyDocsHeading } from \"leadtype/llm/readability\"; function textFromChildren(children: unknown): string { if (typeof children === \"string\" || typeof children === \"number\") { return String(children); } if (Array.isArray(children)) return children.map(textFromChildren).join(\"\"); // (recurse into React elements as needed) return \"\"; } const Heading2 = ({ children, id, ...props }: React.HTMLAttributes) => { const headingId = id ?? slugifyDocsHeading(textFromChildren(children)); return

{children}

; }; ```","Use the source primitive\n\nWire createDocsSource into Next, Astro, Vite, Nuxt, SvelteKit, or any MDX-aware bundler. Same primitive, multiple host shapes.\n\nUse the source primitive\n\nBuild a search index\n\nFor provider-specific search Vercel AI, TanStack, Cloudflare , feed the bundle into a leadtype/search/ adapter — see Add search.\n\n```tsx title=\"app/api/search/route.ts\" import { source } from \"@/lib/source\"; const bundle = await source.buildSearchIndex(); export async function GET() { return Response.json(bundle.index); } ```","Use the source primitive\n\nWire createDocsSource into Next, Astro, Vite, Nuxt, SvelteKit, or any MDX-aware bundler. Same primitive, multiple host shapes.\n\nUse the source primitive\n\nTroubleshooting\n\n lint-report.json ```","Validate in CI\n\nRun leadtype lint in CI so frontmatter, navigation, and link issues fail PRs before publish.\n\nValidate in CI\n\nLocal pre-push hook\n\nCatch issues before they reach CI by running lint in a husky pre-push hook Keep it under a second by limiting the scan to changed files when you have many pages. The CLI accepts repeated --ignore globs to skip stale or generated paths.\n\n```bash #!/usr/bin/env sh npx leadtype lint docs --max-warnings 0 ```","Validate in CI\n\nRun leadtype lint in CI so frontmatter, navigation, and link issues fail PRs before publish.\n\nValidate in CI\n\nRun before generate\n\nWhen leadtype lint and leadtype generate both run in the same job, lint first Generate fails noisily on unknown groups or broken includes. Lint fails specifically on content schema problems with file/line context — much easier to debug.\n\n```bash npx leadtype lint docs --error-unknown npx leadtype generate --src . --out public --json ```","Validate in CI\n\nRun leadtype lint in CI so frontmatter, navigation, and link issues fail PRs before publish.\n\nValidate in CI\n\nWhat to fix first\n\nWhen CI fails on a lot of violations, fix them in this order 1. parse-error — frontmatter is broken; nothing else can validate. 2. schema — missing or wrong-typed required fields. 3. unresolved-placeholder — content bug, not a config bug. 4. invalid-link and cross-framework-link — usually a stale link after a docs move. 5. unknown-field — last; either delete the field or extend the schema.","How it works\n\nThe mental model: one MDX source, a remark pipeline, two output modes, three audiences.\n\nHow it works\n\nLeadtype takes one input — a folder of MDX — and produces every shape your docs need to take. This page names every piece so the rest of the docs make sense.","How it works\n\nThe mental model: one MDX source, a remark pipeline, two output modes, three audiences.\n\nHow it works\n\nThe pipeline\n\nThe remark stack is what turns interactive MDX components into agent-readable markdown. JSX gets flattened — a title · description · group · body\"] fm[\"frontmatter parser\"] remark[\"remark plugin stack
(strip imports → flatten components)\"] groups[\"group resolver
(nav tree from frontmatter)\"] md[\"docs/*.md\"] site_idx[\"llms.txt · llms-full.txt
sitemap · robots · manifest
(website mode only)\"] search[\"search-index.json · search-content.json
(website mode only)\"] agents_md[\"AGENTS.md
(--bundle mode only)\"] nav[\"navigation manifest
(group → page)\"] src --> fm fm --> remark fm --> groups remark --> md md --> site_idx md --> search groups --> site_idx groups --> agents_md groups --> nav ```","How it works\n\nThe mental model: one MDX source, a remark pipeline, two output modes, three audiences.\n\nHow it works\n\nTwo output modes\n\nleadtype generate has two modes that read the same source and emit different shapes Property Type Description Default Required -- -- -- -- -- Site mode default leadtype generate --out public Writes llms.txt, llms-full.txt, docs/search-index.json, docs/sitemap.xml, docs/sitemap.md, docs/robots.txt, docs/agent-readability.json, and docs/\\ .md to a public/ directory your docs website serves. This is what you wire into a Vite, Next.js, Astro, or TanStack Start build. - Optional Bundle mode leadtype generate --bundle --out packages/foo Writes AGENTS.md at the package root and docs/\\ .md beneath it, both with relative paths. Skips llms.txt, llms-full.txt, search, sitemap, robots, and Agent Readability files — those are website-only. Designed for npm tarballs that ship docs alongside the published code. - Optional","How it works\n\nThe mental model: one MDX source, a remark pipeline, two output modes, three audiences.\n\nHow it works\n\nThe artifacts\n\nProperty Type Description Default Required -- -- -- -- -- Markdown .md docs/\\public/*\"] bundle_out[\"bundle mode
node_modules/<pkg>/*\"] human[\"Humans
(browser)\"] http_agent[\"HTTP agents
(fetch /llms.txt or
Accept: text/markdown)\"] search_ui[\"Search UI
AI answers\"] offline_agent[\"Coding agents
(Claude Code, Codex,
Cursor, Copilot, …)\"] site_out -- \"HTML render of MDX\" --> human site_out -- \"absolute URLs\" --> http_agent site_out -- \"search-index.json\" --> search_ui bundle_out -- \"version-matched AGENTS.md\" --> offline_agent ```","How it works\n\nThe mental model: one MDX source, a remark pipeline, two output modes, three audiences.\n\nHow it works\n\nVocabulary\n\nA few terms you will see throughout the docs. Property Type Description Default Required -- -- -- -- -- flatten verb Convert an interactive MDX component into a portable markdown equivalent. A \\(your source)\"] site_run[\"leadtype generate\"] bundle_run[\"leadtype generate --bundle\"] site_out[\"public/
llms.txt · llms-full.txt
docs/*.md · sitemap
agent-readability.json\"] bundle_out[\"packages/<name>/
AGENTS.md · docs/*.md\"] humans[\"Humans
(browser)\"] http_agents[\"HTTP agents
(via /llms.txt or
Accept: text/markdown)\"] search[\"Search UI
AI answers\"] offline_agents[\"Coding agents
can read version-matched
node_modules/<pkg>/AGENTS.md\"] src --> site_run src --> bundle_run site_run --> site_out bundle_run --> bundle_out site_out --> humans site_out --> http_agents site_out --> search bundle_out --> offline_agents ```","Leadtype\n\nOne MDX source. Hosted docs artifacts, package-bundled AGENTS.md, and search output from the same pipeline.\n\nLeadtype\n\nStart here\n\nQuickstart → — three steps to a working source primitive, then plug it into your framework. Or skip ahead Build a docs site compares every integration path side-by-side.","Leadtype\n\nOne MDX source. Hosted docs artifacts, package-bundled AGENTS.md, and search output from the same pipeline.\n\nLeadtype\n\nWhat leadtype produces\n\nA source primitive for your app llms.txt, sitemaps, agent metadata AGENTS.md for npm packages","Leadtype\n\nOne MDX source. Hosted docs artifacts, package-bundled AGENTS.md, and search output from the same pipeline.\n\nLeadtype\n\nNext\n\nNew here? Read the Quickstart — three steps to a working source. Want the mental model first? Read How it works for the pipeline diagram and the names of every artifact it produces. Comparing tools? See Methodology for how leadtype differs from Fumadocs, Starlight, and Mintlify.","Methodology\n\nHow leadtype differs from Fumadocs, Starlight, and Mintlify.\n\nMethodology\n\nLeadtype is a docs pipeline, not a docs website framework . It produces the artifacts a docs site needs markdown, llms.txt, search index, navigation and stays out of routing, layout, and theming.","Methodology\n\nHow leadtype differs from Fumadocs, Starlight, and Mintlify.\n\nMethodology\n\nThe short version\n\nTool What it gives you -- -- Fumadocs A React docs framework Next.js, TanStack Start, React Router, Waku . Supports llms.txt and content negotiation via framework route handlers. Starlight An Astro docs site framework. llms.txt via the starlight-llms-txt community plugin not built-in ; static client-side search via Pagefind. Mintlify A hosted docs SaaS with an optional headless Astro mode that still calls Mintlify's search and assistant APIs . Leadtype The portable content layer behind any of the above. Choose a website framework when the main job is publishing a polished docs UI quickly. Consider a hosted platform if you want managed publishing, search, analytics, and AI features and accept that service dependency. Opt for leadtype when you need to own the generated docs artifacts framework-agnostic markdown, navigation, search, llms.txt , a root llms-full.txt fallback, and optional AGENTS.md package bundles that ship inside npm tarballs.","Methodology\n\nHow leadtype differs from Fumadocs, Starlight, and Mintlify.\n\nMethodology\n\nWhat leadtype owns\n\nThe MDX tag contract — typed prop shapes for (your repo)\"] cli[\"leadtype generate --bundle
--out packages/<name>\"] bundle[\"packages/<name>/
AGENTS.md
docs/*.md\"] publish[\"npm publish\"] install[\"npm install <name>\"] consume[\"node_modules/<name>/
AGENTS.md → docs/*.md
version-matched offline docs\"] src --> cli cli --> bundle bundle --> publish publish --> install install --> consume ```","Bundle docs into a package\n\nShip agent-readable docs inside an npm tarball — AGENTS.md at the package root plus per-topic .md files.\n\nBundle docs into a package\n\nWhy AGENTS.md, not llms.txt?\n\nllms.txt is a website convention — a file at /llms.txt with absolute URLs that an agent fetches over HTTP. Inside an npm tarball it's the wrong shape every link points at a hosted URL the agent may not be able to reach, and no major coding agent looks for node modules/ 0) { for (const { urlPath, slug } of navigation.unknown) { process.stderr.write(`error: ${urlPath} declares unknown group \"${slug}\".\\n`); } process.exit(1); } await generateAgentsMd({ srcDir: REPO_ROOT, outDir: PACKAGE_ROOT, product: docsConfig.product, groups: docsConfig.groups, }); ```","Bundle docs into a package\n\nShip agent-readable docs inside an npm tarball — AGENTS.md at the package root plus per-topic .md files.\n\nBundle docs into a package\n\nVerify before publishing\n\nnpm pack --dry-run should list AGENTS.md docs/ / .md If AGENTS.md is missing, check files in package.json . If .md files are missing, check the --include / --exclude filters.\n\n```bash npx leadtype generate --bundle --src . --out packages/my-package cd packages/my-package && npm pack --dry-run ```","Bundle docs into a package\n\nShip agent-readable docs inside an npm tarball — AGENTS.md at the package root plus per-topic .md files.\n\nBundle docs into a package\n\nTell consuming projects to use the bundle\n\nAGENTS.md inside your tarball is available at node modules/ When working with the `` library, read the bundled docs in `node_modules//AGENTS.md` first — they're version-matched to the installed package and stay accurate as the library updates. ```","Bundle docs into a package\n\nShip agent-readable docs inside an npm tarball — AGENTS.md at the package root plus per-topic .md files.\n\nBundle docs into a package\n\nWhen to use this\n\nUse this when agents should understand the package from the installed dependency itself — coding agents that don't have web access, IDE assistants, CLI tools, or air-gapped environments. Don't use this if your only goal is a public docs website. For that, see Build a docs site. You can use both — they read the same source MDX, just emit different shapes.","Bundle docs into a package\n\nShip agent-readable docs inside an npm tarball — AGENTS.md at the package root plus per-topic .md files.\n\nBundle docs into a package\n\nWhat's next\n\nCLI reference LLM files Lint in CI","Quickstart\n\nInstall leadtype, author your first MDX page, and create a source. Then plug it into whatever framework you're using.\n\nQuickstart\n\nThree steps to a working source primitive. Then plug it into your framework — Next, Fumadocs, Astro, Vite + Vue/Solid/Svelte, Nuxt, SvelteKit — using one of the recipes below.","Quickstart\n\nInstall leadtype, author your first MDX page, and create a source. Then plug it into whatever framework you're using.\n\nQuickstart\n\nInstall\n\nPackage manager Command -- -- npm npm install leadtype pnpm pnpm add leadtype yarn yarn add leadtype bun bun add leadtype","Quickstart\n\nInstall leadtype, author your first MDX page, and create a source. Then plug it into whatever framework you're using.\n\nQuickstart\n\nAuthor one page\n\nEvery page needs a title . Add group when the page should appear in generated navigation and llms.txt sections. See Frontmatter for the full content contract.\n\n```mdx title=\"content/docs/index.mdx\" --- title: \"My library\" description: \"What it does in one sentence.\" --- # My library Welcome. ```","Quickstart\n\nInstall leadtype, author your first MDX page, and create a source. Then plug it into whatever framework you're using.\n\nQuickstart\n\nCreate the source\n\nThat's the framework-neutral primitive. You can now call source.loadPage slug — resolved markdown + AST + frontmatter + TOC source.listPages — every page's slug, urlPath, and metadata source.getNavigation — grouped sidebar tree source.buildSearchIndex — static search bundle source.resolveInclude specifier — standalone include resolver\n\n```ts title=\"lib/source.ts\" import { createDocsSource } from \"leadtype\"; export const source = await createDocsSource({ contentDir: \"./content/docs\", baseUrl: \"https://example.com\", }); ```","Quickstart\n\nInstall leadtype, author your first MDX page, and create a source. Then plug it into whatever framework you're using.\n\nQuickstart\n\nPlug it into your framework\n\nPick the recipe that matches your stack. Each one shows the wiring on top of the same createDocsSource you just set up. Next App Router Fumadocs TanStack Start Astro Content Collections Vite + Vue / Solid / Svelte Nuxt SvelteKit","Quickstart\n\nInstall leadtype, author your first MDX page, and create a source. Then plug it into whatever framework you're using.\n\nQuickstart\n\nOr write static artifacts to disk\n\nSome pipelines need flat files — CDN-only deploys, static exports, agent-only services, multiple sibling apps sharing one corpus. Use the CLI That writes llms.txt , llms-full.txt , docs/ .md , search index, sitemap, and agent-readability.json to disk. See Generate static artifacts for the full flow.\n\n```sh npx leadtype generate --src . --out public --base-url https://example.com ```","Quickstart\n\nInstall leadtype, author your first MDX page, and create a source. Then plug it into whatever framework you're using.\n\nQuickstart\n\nNext steps\n\nGoal Page -- -- Compare every integration shape side-by-side Build a docs site Implement custom MDX tags Callout, Tabs, Steps, … leadtype/mdx Add search Add search Validate frontmatter and links in CI Validate in CI Serve markdown, sitemaps, robots, and JSON-LD for agents Optimize docs for agents Publish AGENTS.md inside an npm package Bundle docs into a package","CLI\n\nleadtype generate and leadtype lint — flags, exit codes, and JSON output.\n\nCLI\n\nTwo commands generate runs the full pipeline, lint validates content. Run leadtype help [options] ```","CLI\n\nleadtype generate and leadtype lint — flags, exit codes, and JSON output.\n\nCLI\n\ngenerate\n\nConvert MDX, then either produce website artifacts default or a package bundle --bundle . Flag Default Description -- -- -- --src ; summary: { filesScanned: number; errors: number; warnings: number; }; }; ```","Lint rules\n\nSchema, link, and navigation checks. CLI and library API.\n\nLint rules\n\nDefault schemas\n\nDocs frontmatter\n\nField Required Type -- -- -- title Yes non-empty string description No string icon No string deprecated No boolean deprecatedReason No string experimental No boolean canary No boolean new No boolean draft No boolean tags No string array group No string or string array availableIn No array of framework, url?, title? full No boolean lastModified and lastAuthor are produced by the converter when --enrich-git is set. Don't author them.","Lint rules\n\nSchema, link, and navigation checks. CLI and library API.\n\nLint rules\n\nDefault schemas\n\nChangelog frontmatter\n\nField Required Type -- -- -- title Yes non-empty string version Yes SemVer string date Yes ISO-8601 or parseable date description No string icon No string type No release , improvement , retired , or deprecation tags No string array canary No boolean authors No string or string array draft No boolean","Lint rules\n\nSchema, link, and navigation checks. CLI and library API.\n\nLint rules\n\nDefault schemas\n\nmeta.json\n\nField Required Type -- -- -- pages Yes string array title No non-empty string root No boolean icon No string defaultOpen No boolean nav.sidebar No section or combined nav.label No string nav.mode No string","Lint rules\n\nSchema, link, and navigation checks. CLI and library API.\n\nLint rules\n\nCustom schemas\n\nPass a Valibot schema to extend or replace the defaults Once you provide a custom schema, unknown-field warnings apply to that schema. Add --error-unknown in CI to keep your contract strict.\n\n```ts import * as v from \"valibot\"; import { lintDocs } from \"leadtype/lint\"; const customFrontmatter = v.object({ title: v.pipe(v.string(), v.minLength(1)), audience: v.picklist([\"beginner\", \"advanced\"]), }); await lintDocs({ srcDir: \"docs\", schemas: { frontmatter: customFrontmatter }, }); ```","Lint rules\n\nSchema, link, and navigation checks. CLI and library API.\n\nLint rules\n\nPractical guidance\n\nRun lint before leadtype generate so content errors fail fast. Use --format github in GitHub Actions and --format json in any other CI. Treat unresolved-placeholder as a content bug first — usually a missing entry in availableIn or a stale URL template. After a docs move, lint and run meta.json updates together; they drift at the same time. For wiring lint into pipelines, see Validate in CI.","LLM files\n\nGenerate llms.txt for hosted websites and AGENTS.md for npm-bundled offline reading.\n\nLLM files\n\nThe leadtype/llm entry point produces four flavors of agent-facing output, all derived from the same docs source generateLlmsTxt — for hosted websites. Emits the /llms.txt convention with root-relative markdown mirror links. generateLLMFullContextFiles — root /llms-full.txt fallback containing all generated markdown docs. Pairs with generateLlmsTxt . generateAgentReadabilityArtifacts — docs-scoped sitemap.xml , sitemap.md , robots.txt , and JSON manifest data that a host app can merge into site-level files. generateAgentsMd — for npm-bundled docs. Emits an AGENTS.md index with relative ./docs/ A library that does one thing well. - Helper that handles the boring parts. - Type-safe by default. - Works in any runtime. ## Best Starting Points - [Documentation](/docs/index.md) - [Quickstart](/docs/quickstart.md) ## Get Started Five-minute happy path and the mental model. - [Quickstart](/docs/quickstart.md): Install and run the pipeline. - [How it works](/docs/how-it-works.md): The mental model. ## Reference CLI flags and conversion APIs. - [CLI](/docs/reference/cli.md): Every flag. ```","LLM files\n\nGenerate llms.txt for hosted websites and AGENTS.md for npm-bundled offline reading.\n\nLLM files\n\nTypical sequence\n\ngenerateLLMFullContextFiles and generateAgentReadabilityArtifacts read from A library that does one thing well. These docs ship inside the package so coding agents can read them offline. Open the topic file you need from the list below — paths are relative to this file. ## Get Started - [Quickstart](./docs/quickstart.md): Install and run the pipeline. - [How it works](./docs/how-it-works.md): The mental model. ## Reference - [CLI](./docs/reference/cli.md): Every flag. ```","LLM files\n\nGenerate llms.txt for hosted websites and AGENTS.md for npm-bundled offline reading.\n\nLLM files\n\nresolveDocsNavigation\n\nSame group-resolution logic the LLM files use, but returns the navigation manifest as a plain object — useful for driving a sidebar UI Write the result to src/generated/docs-nav.json and import it from your sidebar component Now your sidebar can import a static manifest with the same group tree the LLM files use.\n\n```ts const navigation = await resolveDocsNavigation({ srcDir: \".\", baseUrl: \"https://leadtype.dev\", groups: docsConfig.groups, }); if (navigation.unknown.length > 0) { for (const { urlPath, slug } of navigation.unknown) { process.stderr.write(`error: ${urlPath} declares unknown group \"${slug}\".\\n`); } process.exit(1); } ``` ```ts import { mkdir, writeFile } from \"node:fs/promises\"; await mkdir(\"src/generated\", { recursive: true }); await writeFile( \"src/generated/docs-nav.json\", `${JSON.stringify(navigation, null, 2)}\\n` ); ```","LLM files\n\nGenerate llms.txt for hosted websites and AGENTS.md for npm-bundled offline reading.\n\nLLM files\n\nTables of contents\n\nFor the heading slug contract and renderer wiring, see Use the source primitive. This section covers the build-time APIs only. resolveDocsNavigation includes a toc array on every page by default. The default range is h2 – h3 , which keeps page-level h1 titles out of sidebars. If you only need TOC data and not the full group tree, call resolveDocsTableOfContents For custom pipelines, extractDocsTableOfContents accepts a markdown or MDX string plus page URL metadata and returns plain JSON. It ignores frontmatter and fenced code blocks, and it uses the same slugifyDocsHeading helper that rendered headings must use to keep id attributes in sync.\n\n```ts const navigation = await resolveDocsNavigation({ srcDir: \".\", baseUrl: \"https://leadtype.dev\", groups: docsConfig.groups, toc: { minLevel: 2, maxLevel: 4 }, }); ``` ```ts const pages = await resolveDocsTableOfContents({ srcDir: \".\", baseUrl: \"https://leadtype.dev\", }); ```","LLM files\n\nGenerate llms.txt for hosted websites and AGENTS.md for npm-bundled offline reading.\n\nLLM files\n\nGroup design\n\nThe groups you pass to these APIs come from docs.config.ts . Two principles Use groups for routing, not sharding. Groups organize llms.txt , navigation, search metadata, and AGENTS.md . The root llms-full.txt remains the broad fallback. Write group descriptions for routing, not flavor text. Agents read those descriptions to decide which pages to load. \"How to install and run\" beats \"Welcome to our guides!\"","LLM files\n\nGenerate llms.txt for hosted websites and AGENTS.md for npm-bundled offline reading.\n\nLLM files\n\nBase URL precedence\n\nPass baseUrl explicitly, or use environment variables for layered fallback The package-specific LEADTYPE AGENT BASE URL lets each package override an org-wide default. BASE URL covers most CI/deployment platforms, and a final hardcoded fallback keeps local builds working without env setup.\n\n```ts const baseUrl = process.env.LEADTYPE_AGENT_BASE_URL || process.env.BASE_URL || process.env.PORTLESS_URL || \"https://leadtype.dev\"; ```","leadtype/mdx\n\nTag type contracts and the build-time source preset for consumers rendering MDX themselves.\n\nleadtype/mdx\n\nThe leadtype/mdx subpath is the consumer-facing MDX surface — everything you need to compile leadtype-authored MDX in your own renderer fumadocs, Next App Router, Vite + @mdx-js, Astro content collections . It exports three things 1. Tag type contracts — typed prop shapes for every custom MDX tag. 2. mdxSourcePlugins — a remark preset that performs build-time resolution only expand includes, resolve & HTMLAttributes & { children?: ReactNode }; export function Callout({ variant, title, children, ...rest }: ReactCalloutProps) { return ( ); } ```","leadtype/mdx\n\nTag type contracts and the build-time source preset for consumers rendering MDX themselves.\n\nleadtype/mdx\n\nFramework-neutral by design\n\nVue\n\n```vue ```","leadtype/mdx\n\nTag type contracts and the build-time source preset for consumers rendering MDX themselves.\n\nleadtype/mdx\n\nFramework-neutral by design\n\nSvelte\n\n```svelte ```","leadtype/mdx\n\nTag type contracts and the build-time source preset for consumers rendering MDX themselves.\n\nleadtype/mdx\n\nFramework-neutral by design\n\nSolid\n\n```tsx import type { CalloutProps } from \"leadtype/mdx\"; import type { JSX } from \"solid-js\"; type SolidCalloutProps = Omit & { children?: JSX.Element; }; export function Callout(props: SolidCalloutProps) { return ( ); } ```","leadtype/mdx\n\nTag type contracts and the build-time source preset for consumers rendering MDX themselves.\n\nleadtype/mdx\n\nFramework-neutral by design\n\nAstro\n\n```astro --- import type { CalloutProps } from \"leadtype/mdx\"; type Props = Omit; const { variant = \"info\", title } = Astro.props as Props; --- ```","leadtype/mdx\n\nTag type contracts and the build-time source preset for consumers rendering MDX themselves.\n\nleadtype/mdx\n\nFramework-neutral by design\n\nFull type inventory\n\nEvery prop name is part of the 1.0 contract — bumping a shape is a breaking change. New optional props are minor.\n\n```ts import type { // Layout AudienceProps, AudienceTarget, SectionProps, DetailsProps, // Callouts CalloutProps, CalloutVariant, CalloutTypeAlias, // Navigation / structure TabsProps, TabProps, StepsProps, StepProps, AccordionProps, AccordionItemProps, // Cards / topics CardsProps, CardProps, CardVariant, TopicSwitcherProps, TopicSwitcherItem, // File tree FileTreeProps, FolderProps, FileProps, // Code / commands CommandTabsProps, CommandMode, PackageManager, ExampleProps, ExampleSourceFile, PromptProps, // Type tables TypeTableProps, TypeTableProperty, ExtractedTypeTableProps, // Diagrams MermaidProps, } from \"leadtype/mdx\"; ```","leadtype/mdx\n\nTag type contracts and the build-time source preset for consumers rendering MDX themselves.\n\nleadtype/mdx\n\nFramework-neutral by design\n\nBuild-time only {title ?

{title}

: null} {description ?

{description}

: null} {Object.entries(properties).map(([name, prop]) => ( ))}
{name} {prop.type} {prop.description}
); } ```","leadtype/mdx\n\nTag type contracts and the build-time source preset for consumers rendering MDX themselves.\n\nleadtype/mdx\n\nInclude resolution\n\nresolveInclude reads + classifies an include target without going through remark — useful when loading a partial outside of the bundler pipeline parseIncludeSpecifier specifier and extractMdxSection root, id are exposed for callers that want to compose their own pipeline.\n\n```ts import { resolveInclude } from \"leadtype/mdx\"; const result = await resolveInclude(\"./shared/install.mdx#bun\", { fromDir: process.cwd(), }); if (result.kind === \"markdown\") { console.log(result.content); // frontmatter-stripped body console.log(result.section); // \"bun\" } else { console.log(result.lang, result.content); // code-block form } ```","leadtype/mdx\n\nTag type contracts and the build-time source preset for consumers rendering MDX themselves.\n\nleadtype/mdx\n\nRe-exported path helpers\n\nRouting primitives that previously lived under leadtype/internal are re-exported for consumer use\n\n```ts import { type DocsPathMount, normalizeBaseUrl, normalizeDocsPath, normalizeUrlPrefix, stripDocsExtension, toDocsUrlPath, } from \"leadtype/mdx\"; ```","Remark plugins\n\nThe default plugin stack that flattens MDX components into markdown.\n\nRemark plugins\n\nThe remark stack is what turns interactive MDX into agent-readable markdown. Imports get stripped first, placeholders get resolved, then each named component is flattened into a markdown equivalent. Order matters.\n\n```ts import { defaultRemarkPlugins, remarkInclude, remarkTypeTableToMarkdown, } from \"leadtype/remark\"; ```","Remark plugins\n\nThe default plugin stack that flattens MDX components into markdown.\n\nRemark plugins\n\nThe default stack\n\ndefaultRemarkPlugins runs the stack in this order 1. remarkRemoveImports — strip MDX import and export statements. 2. remarkRemoveJsxComments — strip / ... / JSX comments. 3. remarkResolveDocPlaceholders — replace framework and similar placeholders in URLs. 4. remarkAudienceToMarkdown — include ri --> rj --> rd --> rau --> rs --> rc --> rcd --> rdt --> rm --> rct --> rst --> rt --> rtt --> ra --> rts --> rft --> rp --> re --> out ```","Remark plugins\n\nThe default plugin stack that flattens MDX components into markdown.\n\nRemark plugins\n\nThe default stack\n\n ./shared/auth.mdx ``` ```mdx ``` ```mdx ``` ```mdx
This content can be reused in multiple pages.
``` ```ts remarkPlugins: [ [remarkInclude, [\"docs\", \"content\"]], ...defaultRemarkPlugins, ]; ```","Remark plugins\n\nThe default plugin stack that flattens MDX components into markdown.\n\nRemark plugins\n\nOptional plugins\n\nremarkTypeTableToMarkdown with basePath\n\n p !== remarkTypeTableToMarkdown), [remarkTypeTableToMarkdown, { basePath: process.cwd() }], ]; ``` ```mdx ```","Remark plugins\n\nThe default plugin stack that flattens MDX components into markdown.\n\nRemark plugins\n\nPlugin selection rules\n\nUse defaultRemarkPlugins for any agent-facing or LLM output. Add remarkInclude when docs are composed from shared fragments. Use individual plugins only when you intentionally want to omit a flattener e.g. you don't use /docs/search-index.json /docs/search-content.json ``` ```ts await generateDocsSearchFiles({ outDir: \"public\", baseUrl: \"https://leadtype.dev\", mounts: [ { pathPrefix: \"\", urlPrefix: \"/docs\" }, { pathPrefix: \"changelog\", urlPrefix: \"/changelog\" }, ], }); ```","Search\n\nStatic search index, runtime helpers, and source-grounded answer streaming.\n\nSearch\n\nRuntime search\n\nThe runtime is edge-safe — no Node APIs, works on Vercel, Cloudflare, and anywhere else Results include heading paths, hash URLs, and snippets ready for a search UI. Search is still local and dependency-free, but it is not exact-token only. Query terms expand through lightweight stemming, prefix matches, typo-tolerant fallbacks, and a small built-in synonym map. Exact matches keep the highest weight so API names and config keys stay precise. Pass synonyms when your docs use product-specific vocabulary\n\n```ts import { searchDocs, type DocsSearchIndex, type DocsSearchContentStore, } from \"leadtype/search\"; import indexJson from \"../public/docs/search-index.json\"; import contentJson from \"../public/docs/search-content.json\"; const results = searchDocs( indexJson as DocsSearchIndex, \"tabs install\", { content: contentJson as DocsSearchContentStore } ); ``` ```ts const results = searchDocs(index, \"starter\", { content, synonyms: { starter: [\"quickstart\", \"getting started\"], }, }); ```","Search\n\nStatic search index, runtime helpers, and source-grounded answer streaming.\n\nSearch\n\nReading docs at runtime\n\nThe same index doubles as a virtual filesystem. Three readers, picked by what you have Use readDocsContentFile when you need the entire page for context links . Use readDocsContentChunk when a search result already named the right heading.\n\n```ts import { listDocsContentFiles, readDocsContentFile, readDocsContentChunk, } from \"leadtype/search\"; const allFiles = listDocsContentFiles(index); const wholePage = readDocsContentFile(index, \"guides/quickstart\", content); const oneChunk = readDocsContentChunk(index, \"chunk-0\", content); ```","Search\n\nStatic search index, runtime helpers, and source-grounded answer streaming.\n\nSearch\n\nSource-grounded answers\n\ncreateAnswerContext turns a query plus retrieved chunks into a system and prompt you pass to any model The system message instructs the model to answer only from the retrieved context, cite sources with 1 -style references, and say so when the context is insufficient.\n\n```ts import { createAnswerContext } from \"leadtype/search\"; const context = createAnswerContext(index, \"how do I run lint?\", { content, productName: \"My Library\", }); // → { system, prompt, sources } ```","Search\n\nStatic search index, runtime helpers, and source-grounded answer streaming.\n\nSearch\n\nStreaming via provider entry points\n\nThree thin wrappers around createAnswerContext that stream a Response and surface sources separately. Use one matching your runtime response is a plain text Response. sources is metadata for citation links — display it separately, don't embed it in the streamed answer. For TanStack, pass an explicit adapter . For Cloudflare, build one with createCloudflareDocsAdapter provider, model, options binding env.AI.gateway \"docs\" .\n\n```ts import { streamDocsAnswer } from \"leadtype/search/vercel\"; // Vercel AI SDK / AI Gateway import { streamDocsAnswer } from \"leadtype/search/tanstack\"; // TanStack AI import { streamDocsAnswer } from \"leadtype/search/cloudflare\"; // Cloudflare AI Gateway / Workers AI ``` ```ts const { response, sources } = streamDocsAnswer({ index, content, query, model: \"openai/gpt-5.5\", productName: \"My Library\", }); ```","Search\n\nStatic search index, runtime helpers, and source-grounded answer streaming.\n\nSearch\n\nBash tool adapters\n\nWhen you want an agent to explore docs with shell commands instead of receiving pre-selected chunks The adapter exposes a read-only virtual /docs filesystem with ls , cat , find , grep , and rg . Network commands, code execution, and writes are disabled. Use createDocsBashTool for Vercel AI SDK tool sets and createDocsBashTools for TanStack-compatible tools over the same filesystem.\n\n```ts import { createDocsBashTool, createDocsBashTools } from \"leadtype/search/bash\"; const { tools, instructions } = await createDocsBashTool(index, content); ```","Search\n\nStatic search index, runtime helpers, and source-grounded answer streaming.\n\nSearch\n\nAbuse guards\n\nReusable utilities for the request path Helper Purpose -- -- validateDocsQuery Trim and cap query text. readJsonWithLimit Reject oversized JSON bodies before parse. getClientIdentifier Read common proxy IP headers. createMemoryRateLimiter Implements RateLimiter for demos. The in-memory limiter is fine for demos. Production apps should adapt the RateLimiter interface to a shared store — Redis, Vercel KV, Cloudflare KV, or Durable Objects.","Search\n\nStatic search index, runtime helpers, and source-grounded answer streaming.\n\nSearch\n\nWhen to add embeddings\n\nStart with the local index. It is static, cheap, edge-safe, and fast for exact API names, config keys, error messages, and paths. Add embeddings only when Users search with vocabulary that doesn't match the docs e.g. \"make it faster\" matching a \"performance optimization\" page . Your docs grow past tens of thousands of chunks and the cold-start memory hit becomes noticeable. Even then, keep the lexical index for exact matches and layer embeddings on top — they're complementary, not replacements.","createDocsSource\n\nFramework-neutral docs source primitive — navigation, page loader, search index, and include resolver.\n\ncreateDocsSource\n\ncreateDocsSource is the framework-neutral entry point for any consumer that wants to render leadtype-authored MDX in their own renderer. It composes leadtype's primitives resolveDocsNavigation , convertMdxFile , createDocsSearchIndex , resolveInclude into a single source object. For fumadocs specifically, use the thin leadtype/fumadocs adapter that wraps this primitive.\n\n```ts import { createDocsSource } from \"leadtype\"; const source = await createDocsSource({ contentDir: \"./content/docs\", baseUrl: \"https://example.com\", groups: [ { slug: \"get-started\", title: \"Get Started\" }, { slug: \"guides\", title: \"Guides\" }, ], }); const page = await source.loadPage(\"quickstart\"); const navigation = await source.getNavigation(); const search = await source.buildSearchIndex(); ```","createDocsSource\n\nFramework-neutral docs source primitive — navigation, page loader, search index, and include resolver.\n\ncreateDocsSource\n\nConfiguration\n\nOption Type Notes -- -- -- contentDir string Required. Directory containing source .md / .mdx files. baseUrl string Used for absolute URLs in TOC and search index. groups DocsGroup Doc groups for navigation. Empty groups = all pages ungrouped. mounts DocsPathMount Multi-mount routing advanced . remarkPlugins PluggableList Defaults to mdxSourcePlugins . Pass to skip transforms. toc DocsTableOfContentsOptions \\ false TOC tuning; false skips TOC entirely. searchIndex CreateDocsSearchIndexOptions Search-index tuning. createDocsSource does no I/O on construction beyond a directory scan for listPages caching. Page bodies are loaded lazily.","createDocsSource\n\nFramework-neutral docs source primitive — navigation, page loader, search index, and include resolver.\n\ncreateDocsSource\n\nSource methods\n\ngetNavigation Promise; markdown: string; // resolved, plugin-transformed markdown ast: Root; // mdast Root after the source preset — render this for live MDX toc: DocsTableOfContentsItem[]; } ```","createDocsSource\n\nFramework-neutral docs source primitive — navigation, page loader, search index, and include resolver.\n\ncreateDocsSource\n\nSource methods\n\nbuildSearchIndex Promise SearchRoute, } as any) -const DocsQuickstartRoute = DocsQuickstartRouteImport.update({ - id: '/quickstart', - path: '/quickstart', - getParentRoute: () => DocsRouteRoute, -} as any) -const DocsMethodologyRoute = DocsMethodologyRouteImport.update({ - id: '/methodology', - path: '/methodology', - getParentRoute: () => DocsRouteRoute, -} as any) -const DocsHowItWorksRoute = DocsHowItWorksRouteImport.update({ - id: '/how-it-works', - path: '/how-it-works', - getParentRoute: () => DocsRouteRoute, -} as any) -const DocsReferenceSearchRoute = DocsReferenceSearchRouteImport.update({ - id: '/reference/search', - path: '/reference/search', - getParentRoute: () => DocsRouteRoute, -} as any) -const DocsReferenceRemarkRoute = DocsReferenceRemarkRouteImport.update({ - id: '/reference/remark', - path: '/reference/remark', - getParentRoute: () => DocsRouteRoute, -} as any) -const DocsReferenceLlmRoute = DocsReferenceLlmRouteImport.update({ - id: '/reference/llm', - path: '/reference/llm', - getParentRoute: () => DocsRouteRoute, -} as any) -const DocsReferenceLintRoute = DocsReferenceLintRouteImport.update({ - id: '/reference/lint', - path: '/reference/lint', - getParentRoute: () => DocsRouteRoute, -} as any) -const DocsReferenceEvalsRoute = DocsReferenceEvalsRouteImport.update({ - id: '/reference/evals', - path: '/reference/evals', - getParentRoute: () => DocsRouteRoute, -} as any) -const DocsReferenceConvertRoute = DocsReferenceConvertRouteImport.update({ - id: '/reference/convert', - path: '/reference/convert', - getParentRoute: () => DocsRouteRoute, -} as any) -const DocsReferenceCliRoute = DocsReferenceCliRouteImport.update({ - id: '/reference/cli', - path: '/reference/cli', - getParentRoute: () => DocsRouteRoute, -} as any) -const DocsPackageDocsBundleRoute = DocsPackageDocsBundleRouteImport.update({ - id: '/package-docs/bundle', - path: '/package-docs/bundle', - getParentRoute: () => DocsRouteRoute, -} as any) -const DocsBuildValidateInCiRoute = DocsBuildValidateInCiRouteImport.update({ - id: '/build/validate-in-ci', - path: '/build/validate-in-ci', - getParentRoute: () => DocsRouteRoute, -} as any) -const DocsBuildRenderMdxAndTocRoute = - DocsBuildRenderMdxAndTocRouteImport.update({ - id: '/build/render-mdx-and-toc', - path: '/build/render-mdx-and-toc', - getParentRoute: () => DocsRouteRoute, - } as any) -const DocsBuildOptimizeDocsForAgentsRoute = - DocsBuildOptimizeDocsForAgentsRouteImport.update({ - id: '/build/optimize-docs-for-agents', - path: '/build/optimize-docs-for-agents', - getParentRoute: () => DocsRouteRoute, - } as any) -const DocsBuildConnectDocsSiteRoute = - DocsBuildConnectDocsSiteRouteImport.update({ - id: '/build/connect-docs-site', - path: '/build/connect-docs-site', - getParentRoute: () => DocsRouteRoute, - } as any) -const DocsBuildAddSearchRoute = DocsBuildAddSearchRouteImport.update({ - id: '/build/add-search', - path: '/build/add-search', - getParentRoute: () => DocsRouteRoute, -} as any) -const DocsAuthoringFrontmatterRoute = - DocsAuthoringFrontmatterRouteImport.update({ - id: '/authoring/frontmatter', - path: '/authoring/frontmatter', - getParentRoute: () => DocsRouteRoute, - } as any) -const DocsAuthoringComponentsRoute = DocsAuthoringComponentsRouteImport.update({ - id: '/authoring/components', - path: '/authoring/components', +const DocsSplatRoute = DocsSplatRouteImport.update({ + id: '/$', + path: '/$', getParentRoute: () => DocsRouteRoute, } as any) const ApiDocsSearchRoute = ApiDocsSearchRouteImport.update({ @@ -212,9 +106,7 @@ export interface FileRoutesByFullPath { '/docs': typeof DocsRouteRouteWithChildren '/playground': typeof PlaygroundRoute '/search': typeof SearchRouteWithChildren - '/docs/how-it-works': typeof DocsHowItWorksRoute - '/docs/methodology': typeof DocsMethodologyRoute - '/docs/quickstart': typeof DocsQuickstartRoute + '/docs/$': typeof DocsSplatRoute '/search/cloudflare': typeof SearchCloudflareRoute '/search/tanstack': typeof SearchTanstackRoute '/search/vercel': typeof SearchVercelRoute @@ -222,21 +114,6 @@ export interface FileRoutesByFullPath { '/search/': typeof SearchIndexRoute '/api/docs/ask': typeof ApiDocsAskRouteWithChildren '/api/docs/search': typeof ApiDocsSearchRoute - '/docs/authoring/components': typeof DocsAuthoringComponentsRoute - '/docs/authoring/frontmatter': typeof DocsAuthoringFrontmatterRoute - '/docs/build/add-search': typeof DocsBuildAddSearchRoute - '/docs/build/connect-docs-site': typeof DocsBuildConnectDocsSiteRoute - '/docs/build/optimize-docs-for-agents': typeof DocsBuildOptimizeDocsForAgentsRoute - '/docs/build/render-mdx-and-toc': typeof DocsBuildRenderMdxAndTocRoute - '/docs/build/validate-in-ci': typeof DocsBuildValidateInCiRoute - '/docs/package-docs/bundle': typeof DocsPackageDocsBundleRoute - '/docs/reference/cli': typeof DocsReferenceCliRoute - '/docs/reference/convert': typeof DocsReferenceConvertRoute - '/docs/reference/evals': typeof DocsReferenceEvalsRoute - '/docs/reference/lint': typeof DocsReferenceLintRoute - '/docs/reference/llm': typeof DocsReferenceLlmRoute - '/docs/reference/remark': typeof DocsReferenceRemarkRoute - '/docs/reference/search': typeof DocsReferenceSearchRoute '/api/docs/ask/cloudflare': typeof ApiDocsAskCloudflareRoute '/api/docs/ask/tanstack': typeof ApiDocsAskTanstackRoute '/api/docs/ask/vercel': typeof ApiDocsAskVercelRoute @@ -244,9 +121,7 @@ export interface FileRoutesByFullPath { export interface FileRoutesByTo { '/': typeof IndexRoute '/playground': typeof PlaygroundRoute - '/docs/how-it-works': typeof DocsHowItWorksRoute - '/docs/methodology': typeof DocsMethodologyRoute - '/docs/quickstart': typeof DocsQuickstartRoute + '/docs/$': typeof DocsSplatRoute '/search/cloudflare': typeof SearchCloudflareRoute '/search/tanstack': typeof SearchTanstackRoute '/search/vercel': typeof SearchVercelRoute @@ -254,21 +129,6 @@ export interface FileRoutesByTo { '/search': typeof SearchIndexRoute '/api/docs/ask': typeof ApiDocsAskRouteWithChildren '/api/docs/search': typeof ApiDocsSearchRoute - '/docs/authoring/components': typeof DocsAuthoringComponentsRoute - '/docs/authoring/frontmatter': typeof DocsAuthoringFrontmatterRoute - '/docs/build/add-search': typeof DocsBuildAddSearchRoute - '/docs/build/connect-docs-site': typeof DocsBuildConnectDocsSiteRoute - '/docs/build/optimize-docs-for-agents': typeof DocsBuildOptimizeDocsForAgentsRoute - '/docs/build/render-mdx-and-toc': typeof DocsBuildRenderMdxAndTocRoute - '/docs/build/validate-in-ci': typeof DocsBuildValidateInCiRoute - '/docs/package-docs/bundle': typeof DocsPackageDocsBundleRoute - '/docs/reference/cli': typeof DocsReferenceCliRoute - '/docs/reference/convert': typeof DocsReferenceConvertRoute - '/docs/reference/evals': typeof DocsReferenceEvalsRoute - '/docs/reference/lint': typeof DocsReferenceLintRoute - '/docs/reference/llm': typeof DocsReferenceLlmRoute - '/docs/reference/remark': typeof DocsReferenceRemarkRoute - '/docs/reference/search': typeof DocsReferenceSearchRoute '/api/docs/ask/cloudflare': typeof ApiDocsAskCloudflareRoute '/api/docs/ask/tanstack': typeof ApiDocsAskTanstackRoute '/api/docs/ask/vercel': typeof ApiDocsAskVercelRoute @@ -279,9 +139,7 @@ export interface FileRoutesById { '/docs': typeof DocsRouteRouteWithChildren '/playground': typeof PlaygroundRoute '/search': typeof SearchRouteWithChildren - '/docs/how-it-works': typeof DocsHowItWorksRoute - '/docs/methodology': typeof DocsMethodologyRoute - '/docs/quickstart': typeof DocsQuickstartRoute + '/docs/$': typeof DocsSplatRoute '/search/cloudflare': typeof SearchCloudflareRoute '/search/tanstack': typeof SearchTanstackRoute '/search/vercel': typeof SearchVercelRoute @@ -289,21 +147,6 @@ export interface FileRoutesById { '/search/': typeof SearchIndexRoute '/api/docs/ask': typeof ApiDocsAskRouteWithChildren '/api/docs/search': typeof ApiDocsSearchRoute - '/docs/authoring/components': typeof DocsAuthoringComponentsRoute - '/docs/authoring/frontmatter': typeof DocsAuthoringFrontmatterRoute - '/docs/build/add-search': typeof DocsBuildAddSearchRoute - '/docs/build/connect-docs-site': typeof DocsBuildConnectDocsSiteRoute - '/docs/build/optimize-docs-for-agents': typeof DocsBuildOptimizeDocsForAgentsRoute - '/docs/build/render-mdx-and-toc': typeof DocsBuildRenderMdxAndTocRoute - '/docs/build/validate-in-ci': typeof DocsBuildValidateInCiRoute - '/docs/package-docs/bundle': typeof DocsPackageDocsBundleRoute - '/docs/reference/cli': typeof DocsReferenceCliRoute - '/docs/reference/convert': typeof DocsReferenceConvertRoute - '/docs/reference/evals': typeof DocsReferenceEvalsRoute - '/docs/reference/lint': typeof DocsReferenceLintRoute - '/docs/reference/llm': typeof DocsReferenceLlmRoute - '/docs/reference/remark': typeof DocsReferenceRemarkRoute - '/docs/reference/search': typeof DocsReferenceSearchRoute '/api/docs/ask/cloudflare': typeof ApiDocsAskCloudflareRoute '/api/docs/ask/tanstack': typeof ApiDocsAskTanstackRoute '/api/docs/ask/vercel': typeof ApiDocsAskVercelRoute @@ -315,9 +158,7 @@ export interface FileRouteTypes { | '/docs' | '/playground' | '/search' - | '/docs/how-it-works' - | '/docs/methodology' - | '/docs/quickstart' + | '/docs/$' | '/search/cloudflare' | '/search/tanstack' | '/search/vercel' @@ -325,21 +166,6 @@ export interface FileRouteTypes { | '/search/' | '/api/docs/ask' | '/api/docs/search' - | '/docs/authoring/components' - | '/docs/authoring/frontmatter' - | '/docs/build/add-search' - | '/docs/build/connect-docs-site' - | '/docs/build/optimize-docs-for-agents' - | '/docs/build/render-mdx-and-toc' - | '/docs/build/validate-in-ci' - | '/docs/package-docs/bundle' - | '/docs/reference/cli' - | '/docs/reference/convert' - | '/docs/reference/evals' - | '/docs/reference/lint' - | '/docs/reference/llm' - | '/docs/reference/remark' - | '/docs/reference/search' | '/api/docs/ask/cloudflare' | '/api/docs/ask/tanstack' | '/api/docs/ask/vercel' @@ -347,9 +173,7 @@ export interface FileRouteTypes { to: | '/' | '/playground' - | '/docs/how-it-works' - | '/docs/methodology' - | '/docs/quickstart' + | '/docs/$' | '/search/cloudflare' | '/search/tanstack' | '/search/vercel' @@ -357,21 +181,6 @@ export interface FileRouteTypes { | '/search' | '/api/docs/ask' | '/api/docs/search' - | '/docs/authoring/components' - | '/docs/authoring/frontmatter' - | '/docs/build/add-search' - | '/docs/build/connect-docs-site' - | '/docs/build/optimize-docs-for-agents' - | '/docs/build/render-mdx-and-toc' - | '/docs/build/validate-in-ci' - | '/docs/package-docs/bundle' - | '/docs/reference/cli' - | '/docs/reference/convert' - | '/docs/reference/evals' - | '/docs/reference/lint' - | '/docs/reference/llm' - | '/docs/reference/remark' - | '/docs/reference/search' | '/api/docs/ask/cloudflare' | '/api/docs/ask/tanstack' | '/api/docs/ask/vercel' @@ -381,9 +190,7 @@ export interface FileRouteTypes { | '/docs' | '/playground' | '/search' - | '/docs/how-it-works' - | '/docs/methodology' - | '/docs/quickstart' + | '/docs/$' | '/search/cloudflare' | '/search/tanstack' | '/search/vercel' @@ -391,21 +198,6 @@ export interface FileRouteTypes { | '/search/' | '/api/docs/ask' | '/api/docs/search' - | '/docs/authoring/components' - | '/docs/authoring/frontmatter' - | '/docs/build/add-search' - | '/docs/build/connect-docs-site' - | '/docs/build/optimize-docs-for-agents' - | '/docs/build/render-mdx-and-toc' - | '/docs/build/validate-in-ci' - | '/docs/package-docs/bundle' - | '/docs/reference/cli' - | '/docs/reference/convert' - | '/docs/reference/evals' - | '/docs/reference/lint' - | '/docs/reference/llm' - | '/docs/reference/remark' - | '/docs/reference/search' | '/api/docs/ask/cloudflare' | '/api/docs/ask/tanstack' | '/api/docs/ask/vercel' @@ -485,130 +277,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SearchCloudflareRouteImport parentRoute: typeof SearchRoute } - '/docs/quickstart': { - id: '/docs/quickstart' - path: '/quickstart' - fullPath: '/docs/quickstart' - preLoaderRoute: typeof DocsQuickstartRouteImport - parentRoute: typeof DocsRouteRoute - } - '/docs/methodology': { - id: '/docs/methodology' - path: '/methodology' - fullPath: '/docs/methodology' - preLoaderRoute: typeof DocsMethodologyRouteImport - parentRoute: typeof DocsRouteRoute - } - '/docs/how-it-works': { - id: '/docs/how-it-works' - path: '/how-it-works' - fullPath: '/docs/how-it-works' - preLoaderRoute: typeof DocsHowItWorksRouteImport - parentRoute: typeof DocsRouteRoute - } - '/docs/reference/search': { - id: '/docs/reference/search' - path: '/reference/search' - fullPath: '/docs/reference/search' - preLoaderRoute: typeof DocsReferenceSearchRouteImport - parentRoute: typeof DocsRouteRoute - } - '/docs/reference/remark': { - id: '/docs/reference/remark' - path: '/reference/remark' - fullPath: '/docs/reference/remark' - preLoaderRoute: typeof DocsReferenceRemarkRouteImport - parentRoute: typeof DocsRouteRoute - } - '/docs/reference/llm': { - id: '/docs/reference/llm' - path: '/reference/llm' - fullPath: '/docs/reference/llm' - preLoaderRoute: typeof DocsReferenceLlmRouteImport - parentRoute: typeof DocsRouteRoute - } - '/docs/reference/lint': { - id: '/docs/reference/lint' - path: '/reference/lint' - fullPath: '/docs/reference/lint' - preLoaderRoute: typeof DocsReferenceLintRouteImport - parentRoute: typeof DocsRouteRoute - } - '/docs/reference/evals': { - id: '/docs/reference/evals' - path: '/reference/evals' - fullPath: '/docs/reference/evals' - preLoaderRoute: typeof DocsReferenceEvalsRouteImport - parentRoute: typeof DocsRouteRoute - } - '/docs/reference/convert': { - id: '/docs/reference/convert' - path: '/reference/convert' - fullPath: '/docs/reference/convert' - preLoaderRoute: typeof DocsReferenceConvertRouteImport - parentRoute: typeof DocsRouteRoute - } - '/docs/reference/cli': { - id: '/docs/reference/cli' - path: '/reference/cli' - fullPath: '/docs/reference/cli' - preLoaderRoute: typeof DocsReferenceCliRouteImport - parentRoute: typeof DocsRouteRoute - } - '/docs/package-docs/bundle': { - id: '/docs/package-docs/bundle' - path: '/package-docs/bundle' - fullPath: '/docs/package-docs/bundle' - preLoaderRoute: typeof DocsPackageDocsBundleRouteImport - parentRoute: typeof DocsRouteRoute - } - '/docs/build/validate-in-ci': { - id: '/docs/build/validate-in-ci' - path: '/build/validate-in-ci' - fullPath: '/docs/build/validate-in-ci' - preLoaderRoute: typeof DocsBuildValidateInCiRouteImport - parentRoute: typeof DocsRouteRoute - } - '/docs/build/render-mdx-and-toc': { - id: '/docs/build/render-mdx-and-toc' - path: '/build/render-mdx-and-toc' - fullPath: '/docs/build/render-mdx-and-toc' - preLoaderRoute: typeof DocsBuildRenderMdxAndTocRouteImport - parentRoute: typeof DocsRouteRoute - } - '/docs/build/optimize-docs-for-agents': { - id: '/docs/build/optimize-docs-for-agents' - path: '/build/optimize-docs-for-agents' - fullPath: '/docs/build/optimize-docs-for-agents' - preLoaderRoute: typeof DocsBuildOptimizeDocsForAgentsRouteImport - parentRoute: typeof DocsRouteRoute - } - '/docs/build/connect-docs-site': { - id: '/docs/build/connect-docs-site' - path: '/build/connect-docs-site' - fullPath: '/docs/build/connect-docs-site' - preLoaderRoute: typeof DocsBuildConnectDocsSiteRouteImport - parentRoute: typeof DocsRouteRoute - } - '/docs/build/add-search': { - id: '/docs/build/add-search' - path: '/build/add-search' - fullPath: '/docs/build/add-search' - preLoaderRoute: typeof DocsBuildAddSearchRouteImport - parentRoute: typeof DocsRouteRoute - } - '/docs/authoring/frontmatter': { - id: '/docs/authoring/frontmatter' - path: '/authoring/frontmatter' - fullPath: '/docs/authoring/frontmatter' - preLoaderRoute: typeof DocsAuthoringFrontmatterRouteImport - parentRoute: typeof DocsRouteRoute - } - '/docs/authoring/components': { - id: '/docs/authoring/components' - path: '/authoring/components' - fullPath: '/docs/authoring/components' - preLoaderRoute: typeof DocsAuthoringComponentsRouteImport + '/docs/$': { + id: '/docs/$' + path: '/$' + fullPath: '/docs/$' + preLoaderRoute: typeof DocsSplatRouteImport parentRoute: typeof DocsRouteRoute } '/api/docs/search': { @@ -650,47 +323,13 @@ declare module '@tanstack/react-router' { } interface DocsRouteRouteChildren { - DocsHowItWorksRoute: typeof DocsHowItWorksRoute - DocsMethodologyRoute: typeof DocsMethodologyRoute - DocsQuickstartRoute: typeof DocsQuickstartRoute + DocsSplatRoute: typeof DocsSplatRoute DocsIndexRoute: typeof DocsIndexRoute - DocsAuthoringComponentsRoute: typeof DocsAuthoringComponentsRoute - DocsAuthoringFrontmatterRoute: typeof DocsAuthoringFrontmatterRoute - DocsBuildAddSearchRoute: typeof DocsBuildAddSearchRoute - DocsBuildConnectDocsSiteRoute: typeof DocsBuildConnectDocsSiteRoute - DocsBuildOptimizeDocsForAgentsRoute: typeof DocsBuildOptimizeDocsForAgentsRoute - DocsBuildRenderMdxAndTocRoute: typeof DocsBuildRenderMdxAndTocRoute - DocsBuildValidateInCiRoute: typeof DocsBuildValidateInCiRoute - DocsPackageDocsBundleRoute: typeof DocsPackageDocsBundleRoute - DocsReferenceCliRoute: typeof DocsReferenceCliRoute - DocsReferenceConvertRoute: typeof DocsReferenceConvertRoute - DocsReferenceEvalsRoute: typeof DocsReferenceEvalsRoute - DocsReferenceLintRoute: typeof DocsReferenceLintRoute - DocsReferenceLlmRoute: typeof DocsReferenceLlmRoute - DocsReferenceRemarkRoute: typeof DocsReferenceRemarkRoute - DocsReferenceSearchRoute: typeof DocsReferenceSearchRoute } const DocsRouteRouteChildren: DocsRouteRouteChildren = { - DocsHowItWorksRoute: DocsHowItWorksRoute, - DocsMethodologyRoute: DocsMethodologyRoute, - DocsQuickstartRoute: DocsQuickstartRoute, + DocsSplatRoute: DocsSplatRoute, DocsIndexRoute: DocsIndexRoute, - DocsAuthoringComponentsRoute: DocsAuthoringComponentsRoute, - DocsAuthoringFrontmatterRoute: DocsAuthoringFrontmatterRoute, - DocsBuildAddSearchRoute: DocsBuildAddSearchRoute, - DocsBuildConnectDocsSiteRoute: DocsBuildConnectDocsSiteRoute, - DocsBuildOptimizeDocsForAgentsRoute: DocsBuildOptimizeDocsForAgentsRoute, - DocsBuildRenderMdxAndTocRoute: DocsBuildRenderMdxAndTocRoute, - DocsBuildValidateInCiRoute: DocsBuildValidateInCiRoute, - DocsPackageDocsBundleRoute: DocsPackageDocsBundleRoute, - DocsReferenceCliRoute: DocsReferenceCliRoute, - DocsReferenceConvertRoute: DocsReferenceConvertRoute, - DocsReferenceEvalsRoute: DocsReferenceEvalsRoute, - DocsReferenceLintRoute: DocsReferenceLintRoute, - DocsReferenceLlmRoute: DocsReferenceLlmRoute, - DocsReferenceRemarkRoute: DocsReferenceRemarkRoute, - DocsReferenceSearchRoute: DocsReferenceSearchRoute, } const DocsRouteRouteWithChildren = DocsRouteRoute._addFileChildren( diff --git a/apps/example/src/routes/docs/$.tsx b/apps/example/src/routes/docs/$.tsx new file mode 100644 index 0000000..b9483b2 --- /dev/null +++ b/apps/example/src/routes/docs/$.tsx @@ -0,0 +1,94 @@ +/** biome-ignore-all lint/style/useFilenamingConvention: TanStack Router catch-all route convention */ +"use client"; + +import { createFileRoute, notFound } from "@tanstack/react-router"; +import { type ComponentType, lazy, Suspense, useMemo } from "react"; +import docsPages from "@/generated/docs-pages.json"; +import { createDocsHead } from "@/lib/docs-head"; + +interface DocsPage { + description: string; + extension: ".md" | ".mdx"; + /** Relative to this file — matches an `import.meta.glob` key exactly. */ + globKey: string; + groups: string[]; + relativePath: string; + slug: string[]; + title: string; + urlPath: string; +} + +const pages = docsPages as DocsPage[]; +const pagesByJoinedSlug = new Map( + pages.map((page) => [page.slug.join("/"), page]) +); + +const TRAILING_SLASH_RE = /\/+$/; + +/** + * `import.meta.glob` enumerates every doc MDX at build time and returns + * lazy loaders. Vite resolves the literal glob pattern statically, so the + * keys are file paths relative to this file. Each `globKey` field in the + * manifest matches one of these keys exactly. + */ +const mdxModules = import.meta.glob<{ default: ComponentType }>( + "../../../../../docs/**/*.mdx" +); + +function resolvePage(splat: string | undefined): DocsPage | null { + if (!splat) { + return null; + } + const normalized = splat.replace(TRAILING_SLASH_RE, ""); + return pagesByJoinedSlug.get(normalized) ?? null; +} + +function MissingMdxModule({ urlPath }: { urlPath: string }) { + return ( +
+ MDX module not found for {urlPath}. Re-run{" "} + bun run pipeline:source-manifest after adding docs files. +
+ ); +} + +export const Route = createFileRoute("/docs/$")({ + beforeLoad: ({ params }) => { + if (!resolvePage(params._splat)) { + throw notFound(); + } + }, + component: DocsCatchAllRoute, + head: ({ params }) => { + const page = resolvePage(params._splat); + return page ? createDocsHead(page.urlPath) : {}; + }, +}); + +function DocsCatchAllRoute() { + const { _splat } = Route.useParams(); + // beforeLoad throws notFound() for missing pages, so this should always + // resolve by the time the component renders. The explicit check both + // narrows the type and surfaces a clear error if the invariant ever breaks. + const pageCandidate = resolvePage(_splat); + if (!pageCandidate) { + throw new Error( + `DocsCatchAllRoute rendered with no resolvable page for splat "${_splat}". beforeLoad should have thrown notFound() — file a bug if you see this.` + ); + } + const page: DocsPage = pageCandidate; + + const MdxComponent = useMemo(() => { + const loader = mdxModules[page.globKey]; + if (!loader) { + return () => ; + } + return lazy(loader); + }, [page.globKey, page.urlPath]); + + return ( + + + + ); +} diff --git a/apps/example/src/routes/docs/authoring/components.tsx b/apps/example/src/routes/docs/authoring/components.tsx deleted file mode 100644 index 9289b80..0000000 --- a/apps/example/src/routes/docs/authoring/components.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createFileRoute } from "@tanstack/react-router"; -import { createDocsHead } from "@/lib/docs-head"; -import ComponentsDoc from "../../../../../../docs/authoring/components.mdx"; - -export const Route = createFileRoute("/docs/authoring/components")({ - component: ComponentsRoute, - head: () => createDocsHead("/docs/authoring/components"), -}); - -function ComponentsRoute() { - return ; -} diff --git a/apps/example/src/routes/docs/authoring/frontmatter.tsx b/apps/example/src/routes/docs/authoring/frontmatter.tsx deleted file mode 100644 index 55bfa42..0000000 --- a/apps/example/src/routes/docs/authoring/frontmatter.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createFileRoute } from "@tanstack/react-router"; -import { createDocsHead } from "@/lib/docs-head"; -import FrontmatterDoc from "../../../../../../docs/authoring/frontmatter.mdx"; - -export const Route = createFileRoute("/docs/authoring/frontmatter")({ - component: FrontmatterRoute, - head: () => createDocsHead("/docs/authoring/frontmatter"), -}); - -function FrontmatterRoute() { - return ; -} diff --git a/apps/example/src/routes/docs/build/add-search.tsx b/apps/example/src/routes/docs/build/add-search.tsx deleted file mode 100644 index 0be2c6c..0000000 --- a/apps/example/src/routes/docs/build/add-search.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createFileRoute } from "@tanstack/react-router"; -import { createDocsHead } from "@/lib/docs-head"; -import AddSearchDoc from "../../../../../../docs/build/add-search.mdx"; - -export const Route = createFileRoute("/docs/build/add-search")({ - component: AddSearchRoute, - head: () => createDocsHead("/docs/build/add-search"), -}); - -function AddSearchRoute() { - return ; -} diff --git a/apps/example/src/routes/docs/build/connect-docs-site.tsx b/apps/example/src/routes/docs/build/connect-docs-site.tsx deleted file mode 100644 index 47990e3..0000000 --- a/apps/example/src/routes/docs/build/connect-docs-site.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createFileRoute } from "@tanstack/react-router"; -import { createDocsHead } from "@/lib/docs-head"; -import ConnectDocsSiteDoc from "../../../../../../docs/build/connect-docs-site.mdx"; - -export const Route = createFileRoute("/docs/build/connect-docs-site")({ - component: ConnectDocsSiteRoute, - head: () => createDocsHead("/docs/build/connect-docs-site"), -}); - -function ConnectDocsSiteRoute() { - return ; -} diff --git a/apps/example/src/routes/docs/build/optimize-docs-for-agents.tsx b/apps/example/src/routes/docs/build/optimize-docs-for-agents.tsx deleted file mode 100644 index baaef25..0000000 --- a/apps/example/src/routes/docs/build/optimize-docs-for-agents.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createFileRoute } from "@tanstack/react-router"; -import { createDocsHead } from "@/lib/docs-head"; -import OptimizeDocsForAgentsDoc from "../../../../../../docs/build/optimize-docs-for-agents.mdx"; - -export const Route = createFileRoute("/docs/build/optimize-docs-for-agents")({ - component: OptimizeDocsForAgentsRoute, - head: () => createDocsHead("/docs/build/optimize-docs-for-agents"), -}); - -function OptimizeDocsForAgentsRoute() { - return ; -} diff --git a/apps/example/src/routes/docs/build/render-mdx-and-toc.tsx b/apps/example/src/routes/docs/build/render-mdx-and-toc.tsx deleted file mode 100644 index 31a0eb5..0000000 --- a/apps/example/src/routes/docs/build/render-mdx-and-toc.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createFileRoute } from "@tanstack/react-router"; -import { createDocsHead } from "@/lib/docs-head"; -import RenderMdxAndTocDoc from "../../../../../../docs/build/render-mdx-and-toc.mdx"; - -export const Route = createFileRoute("/docs/build/render-mdx-and-toc")({ - component: RenderMdxAndTocRoute, - head: () => createDocsHead("/docs/build/render-mdx-and-toc"), -}); - -function RenderMdxAndTocRoute() { - return ; -} diff --git a/apps/example/src/routes/docs/build/validate-in-ci.tsx b/apps/example/src/routes/docs/build/validate-in-ci.tsx deleted file mode 100644 index 0d6d843..0000000 --- a/apps/example/src/routes/docs/build/validate-in-ci.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createFileRoute } from "@tanstack/react-router"; -import { createDocsHead } from "@/lib/docs-head"; -import ValidateInCiDoc from "../../../../../../docs/build/validate-in-ci.mdx"; - -export const Route = createFileRoute("/docs/build/validate-in-ci")({ - component: ValidateInCiRoute, - head: () => createDocsHead("/docs/build/validate-in-ci"), -}); - -function ValidateInCiRoute() { - return ; -} diff --git a/apps/example/src/routes/docs/how-it-works.tsx b/apps/example/src/routes/docs/how-it-works.tsx deleted file mode 100644 index babeb32..0000000 --- a/apps/example/src/routes/docs/how-it-works.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createFileRoute } from "@tanstack/react-router"; -import { createDocsHead } from "@/lib/docs-head"; -import HowItWorksDoc from "../../../../../docs/how-it-works.mdx"; - -export const Route = createFileRoute("/docs/how-it-works")({ - component: HowItWorksRoute, - head: () => createDocsHead("/docs/how-it-works"), -}); - -function HowItWorksRoute() { - return ; -} diff --git a/apps/example/src/routes/docs/methodology.tsx b/apps/example/src/routes/docs/methodology.tsx deleted file mode 100644 index b91c59a..0000000 --- a/apps/example/src/routes/docs/methodology.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createFileRoute } from "@tanstack/react-router"; -import { createDocsHead } from "@/lib/docs-head"; -import MethodologyDoc from "../../../../../docs/methodology.mdx"; - -export const Route = createFileRoute("/docs/methodology")({ - component: MethodologyRoute, - head: () => createDocsHead("/docs/methodology"), -}); - -function MethodologyRoute() { - return ; -} diff --git a/apps/example/src/routes/docs/package-docs/bundle.tsx b/apps/example/src/routes/docs/package-docs/bundle.tsx deleted file mode 100644 index 5329f78..0000000 --- a/apps/example/src/routes/docs/package-docs/bundle.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createFileRoute } from "@tanstack/react-router"; -import { createDocsHead } from "@/lib/docs-head"; -import BundleDoc from "../../../../../../docs/package-docs/bundle.mdx"; - -export const Route = createFileRoute("/docs/package-docs/bundle")({ - component: BundleRoute, - head: () => createDocsHead("/docs/package-docs/bundle"), -}); - -function BundleRoute() { - return ; -} diff --git a/apps/example/src/routes/docs/quickstart.tsx b/apps/example/src/routes/docs/quickstart.tsx deleted file mode 100644 index a05a226..0000000 --- a/apps/example/src/routes/docs/quickstart.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createFileRoute } from "@tanstack/react-router"; -import { createDocsHead } from "@/lib/docs-head"; -import QuickstartDoc from "../../../../../docs/quickstart.mdx"; - -export const Route = createFileRoute("/docs/quickstart")({ - component: QuickstartRoute, - head: () => createDocsHead("/docs/quickstart"), -}); - -function QuickstartRoute() { - return ; -} diff --git a/apps/example/src/routes/docs/reference/cli.tsx b/apps/example/src/routes/docs/reference/cli.tsx deleted file mode 100644 index 5c23d8e..0000000 --- a/apps/example/src/routes/docs/reference/cli.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createFileRoute } from "@tanstack/react-router"; -import { createDocsHead } from "@/lib/docs-head"; -import CliDoc from "../../../../../../docs/reference/cli.mdx"; - -export const Route = createFileRoute("/docs/reference/cli")({ - component: CliRoute, - head: () => createDocsHead("/docs/reference/cli"), -}); - -function CliRoute() { - return ; -} diff --git a/apps/example/src/routes/docs/reference/convert.tsx b/apps/example/src/routes/docs/reference/convert.tsx deleted file mode 100644 index ebef307..0000000 --- a/apps/example/src/routes/docs/reference/convert.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createFileRoute } from "@tanstack/react-router"; -import { createDocsHead } from "@/lib/docs-head"; -import ConvertDoc from "../../../../../../docs/reference/convert.mdx"; - -export const Route = createFileRoute("/docs/reference/convert")({ - component: ConvertRoute, - head: () => createDocsHead("/docs/reference/convert"), -}); - -function ConvertRoute() { - return ; -} diff --git a/apps/example/src/routes/docs/reference/evals.tsx b/apps/example/src/routes/docs/reference/evals.tsx deleted file mode 100644 index 003c8bb..0000000 --- a/apps/example/src/routes/docs/reference/evals.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createFileRoute } from "@tanstack/react-router"; -import { createDocsHead } from "@/lib/docs-head"; -import EvalsDoc from "../../../../../../docs/reference/evals.mdx"; - -export const Route = createFileRoute("/docs/reference/evals")({ - component: EvalsRoute, - head: () => createDocsHead("/docs/reference/evals"), -}); - -function EvalsRoute() { - return ; -} diff --git a/apps/example/src/routes/docs/reference/lint.tsx b/apps/example/src/routes/docs/reference/lint.tsx deleted file mode 100644 index 3086024..0000000 --- a/apps/example/src/routes/docs/reference/lint.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createFileRoute } from "@tanstack/react-router"; -import { createDocsHead } from "@/lib/docs-head"; -import LintDoc from "../../../../../../docs/reference/lint.mdx"; - -export const Route = createFileRoute("/docs/reference/lint")({ - component: LintRoute, - head: () => createDocsHead("/docs/reference/lint"), -}); - -function LintRoute() { - return ; -} diff --git a/apps/example/src/routes/docs/reference/llm.tsx b/apps/example/src/routes/docs/reference/llm.tsx deleted file mode 100644 index 84a968b..0000000 --- a/apps/example/src/routes/docs/reference/llm.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createFileRoute } from "@tanstack/react-router"; -import { createDocsHead } from "@/lib/docs-head"; -import LlmDoc from "../../../../../../docs/reference/llm.mdx"; - -export const Route = createFileRoute("/docs/reference/llm")({ - component: LlmRoute, - head: () => createDocsHead("/docs/reference/llm"), -}); - -function LlmRoute() { - return ; -} diff --git a/apps/example/src/routes/docs/reference/remark.tsx b/apps/example/src/routes/docs/reference/remark.tsx deleted file mode 100644 index 58cebd9..0000000 --- a/apps/example/src/routes/docs/reference/remark.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createFileRoute } from "@tanstack/react-router"; -import { createDocsHead } from "@/lib/docs-head"; -import RemarkDoc from "../../../../../../docs/reference/remark.mdx"; - -export const Route = createFileRoute("/docs/reference/remark")({ - component: RemarkRoute, - head: () => createDocsHead("/docs/reference/remark"), -}); - -function RemarkRoute() { - return ; -} diff --git a/apps/example/src/routes/docs/reference/search.tsx b/apps/example/src/routes/docs/reference/search.tsx deleted file mode 100644 index d860c33..0000000 --- a/apps/example/src/routes/docs/reference/search.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { createFileRoute } from "@tanstack/react-router"; -import { createDocsHead } from "@/lib/docs-head"; -import SearchDoc from "../../../../../../docs/reference/search.mdx"; - -export const Route = createFileRoute("/docs/reference/search")({ - component: SearchRoute, - head: () => createDocsHead("/docs/reference/search"), -}); - -function SearchRoute() { - return ; -} diff --git a/apps/example/vite.config.ts b/apps/example/vite.config.ts index bb67701..1998807 100644 --- a/apps/example/vite.config.ts +++ b/apps/example/vite.config.ts @@ -2,6 +2,7 @@ import mdx from "@mdx-js/rollup"; import tailwindcss from "@tailwindcss/vite"; import { tanstackStart } from "@tanstack/react-start/plugin/vite"; import viteReact from "@vitejs/plugin-react"; +import { mdxSourcePlugins } from "leadtype/mdx"; import type { Root } from "mdast"; import { nitro } from "nitro/vite"; import remarkFrontmatter from "remark-frontmatter"; @@ -35,7 +36,16 @@ export default defineConfig({ { ...mdx({ providerImportSource: "@mdx-js/react", - remarkPlugins: [remarkFrontmatter, remarkGfm, stripYamlFrontmatter], + remarkPlugins: [ + // Frontmatter parsing first (mdxSourcePlugins expects bodies only). + remarkFrontmatter, + stripYamlFrontmatter, + remarkGfm, + // Leadtype's MDX-source preset: expand , resolve + // , strip authoring `import`s. Keeps every + // other custom tag as live JSX for the React components below. + ...mdxSourcePlugins, + ], }), enforce: "pre", }, diff --git a/apps/fumadocs-example/app/docs/[[...slug]]/page.tsx b/apps/fumadocs-example/app/docs/[[...slug]]/page.tsx new file mode 100644 index 0000000..1ca8fca --- /dev/null +++ b/apps/fumadocs-example/app/docs/[[...slug]]/page.tsx @@ -0,0 +1,70 @@ +import { rehypeCode } from "fumadocs-core/mdx-plugins"; +import { + PageArticle, + PageRoot, + PageTOC, + PageTOCItems, +} from "fumadocs-ui/layouts/docs/page"; +import { notFound } from "next/navigation"; +import { MDXRemote } from "next-mdx-remote-client/rsc"; +import { mdxComponents } from "@/lib/mdx-components"; +import { leadtypeSource } from "@/lib/source"; + +const mdxOptions = { + // Syntax highlight code blocks. Tokens use the same shiki theme + classes + // that fumadocs-ui's codeblock CSS is designed for. + rehypePlugins: [rehypeCode], +}; + +type PageParams = Promise<{ slug?: string[] }>; + +export default async function DocsPage({ params }: { params: PageParams }) { + const { slug = [] } = await params; + const page = await leadtypeSource.loadPage(slug); + + if (!page) { + notFound(); + } + + return ( + ({ + title: item.title, + url: `#${item.id}`, + depth: item.level, + })), + }} + > + +

{page.title}

+ {page.description ?

{page.description}

: null} + +
+ + + +
+ ); +} + +export async function generateStaticParams() { + const pages = await leadtypeSource.listPages(); + return pages.map((page) => ({ slug: page.slug })); +} + +export async function generateMetadata({ params }: { params: PageParams }) { + const { slug = [] } = await params; + const page = await leadtypeSource.loadPage(slug); + if (!page) { + return {}; + } + return { + title: `${page.title} — c15t docs`, + description: page.description, + }; +} diff --git a/apps/fumadocs-example/app/docs/layout.tsx b/apps/fumadocs-example/app/docs/layout.tsx new file mode 100644 index 0000000..2fbab4c --- /dev/null +++ b/apps/fumadocs-example/app/docs/layout.tsx @@ -0,0 +1,57 @@ +import { DocsLayout } from "fumadocs-ui/layouts/docs"; +import type { ReactNode } from "react"; +import { FrameworkSwitcher } from "@/lib/framework-switcher"; +import { leadtypeSource, source } from "@/lib/source"; + +// Build at render time on the server: every page that exists, so the +// switcher can fall back to /quickstart when the sibling page is missing. +const allPages = await leadtypeSource.listPages(); +const knownRoutes = new Set(allPages.map((page) => page.urlPath)); + +/** + * Top "tabs" that group docs by audience. Each tab points at its section's + * landing route and matches every URL inside that section. fumadocs renders + * these as a switcher above the sidebar. + */ +const sidebarTabs = [ + { + title: "Frontend", + description: "SDKs, components, hooks, and styling", + url: "/docs/frameworks/next/quickstart", + urls: new Set([ + "/docs/frameworks/next", + "/docs/frameworks/react", + "/docs/frameworks/javascript", + ]), + }, + { + title: "Integrations", + description: "Tag managers, analytics, and ad platforms", + url: "/docs/integrations/overview", + urls: new Set(["/docs/integrations"]), + }, + { + title: "Self Host", + description: "Backend setup, deployment, and API reference", + url: "/docs/self-host/quickstart", + urls: new Set(["/docs/self-host"]), + }, +]; + +const navLinks = [{ text: "Changelog", url: "/changelog", external: false }]; + +export default function DocsRouteLayout({ children }: { children: ReactNode }) { + return ( + , + }} + tree={source.pageTree} + > + {children} + + ); +} diff --git a/apps/fumadocs-example/app/global.css b/apps/fumadocs-example/app/global.css new file mode 100644 index 0000000..8aa4042 --- /dev/null +++ b/apps/fumadocs-example/app/global.css @@ -0,0 +1,9 @@ +@import "tailwindcss"; +@import "fumadocs-ui/css/neutral.css"; +@import "fumadocs-ui/css/preset.css"; + +/* Tailwind v4 only scans the files you point it at. fumadocs-ui ships its + classes inside its compiled JS — without this @source line, Tailwind won't + emit any of fumadocs-ui's utility classes and the layout falls back to + unstyled text. */ +@source "../node_modules/fumadocs-ui/dist/**/*.js"; diff --git a/apps/fumadocs-example/app/layout.tsx b/apps/fumadocs-example/app/layout.tsx new file mode 100644 index 0000000..a2c7c12 --- /dev/null +++ b/apps/fumadocs-example/app/layout.tsx @@ -0,0 +1,19 @@ +import { RootProvider } from "fumadocs-ui/provider"; +import type { ReactNode } from "react"; +import "./global.css"; + +export default function RootLayout({ children }: { children: ReactNode }) { + return ( + + + {children} + + + ); +} + +export const metadata = { + title: "c15t — fumadocs + leadtype", + description: + "c15t consent-management docs rendered through fumadocs-ui with leadtype as the source layer.", +}; diff --git a/apps/fumadocs-example/app/page.tsx b/apps/fumadocs-example/app/page.tsx new file mode 100644 index 0000000..d085b75 --- /dev/null +++ b/apps/fumadocs-example/app/page.tsx @@ -0,0 +1,21 @@ +import Link from "next/link"; + +export default function HomePage() { + return ( +
+

c15t docs

+

+ Rendered through fumadocs-ui with leadtype as the source layer. + c15t's authored MDX (including <include>{" "} + partials and <ExtractedTypeTable> components) is + resolved at build time by mdxSourcePlugins. +

+ + Open the Next.js quickstart → + +
+ ); +} diff --git a/apps/fumadocs-example/css.d.ts b/apps/fumadocs-example/css.d.ts new file mode 100644 index 0000000..cbe652d --- /dev/null +++ b/apps/fumadocs-example/css.d.ts @@ -0,0 +1 @@ +declare module "*.css"; diff --git a/apps/fumadocs-example/lib/framework-switcher.tsx b/apps/fumadocs-example/lib/framework-switcher.tsx new file mode 100644 index 0000000..d5594a3 --- /dev/null +++ b/apps/fumadocs-example/lib/framework-switcher.tsx @@ -0,0 +1,75 @@ +"use client"; + +import { usePathname, useRouter } from "next/navigation"; + +const frameworks = [ + { id: "next", label: "Next.js" }, + { id: "react", label: "React" }, + { id: "javascript", label: "JavaScript" }, +] as const; + +type FrameworkId = (typeof frameworks)[number]["id"]; + +const frameworkRoutePattern = + /^\/docs\/frameworks\/(next|react|javascript)(\/(.*))?$/; + +function suffixFromPath(pathname: string): { + framework: FrameworkId; + suffix: string; +} | null { + const match = pathname.match(frameworkRoutePattern); + if (!match) { + return null; + } + return { + framework: match[1] as FrameworkId, + suffix: match[3] ?? "quickstart", + }; +} + +interface FrameworkSwitcherProps { + /** Routes that exist for each framework; passed in from a server component. */ + knownRoutes: Set; +} + +export function FrameworkSwitcher({ knownRoutes }: FrameworkSwitcherProps) { + const pathname = usePathname(); + const router = useRouter(); + const current = suffixFromPath(pathname); + + // Only show on /docs/frameworks/* routes. + if (!current) { + return null; + } + + return ( +
+ + Framework + +
+ {frameworks.map(({ id, label }) => { + const preferred = `/docs/frameworks/${id}/${current.suffix}`; + const fallback = `/docs/frameworks/${id}/quickstart`; + const target = knownRoutes.has(preferred) ? preferred : fallback; + const isActive = id === current.framework; + return ( + + ); + })} +
+
+ ); +} diff --git a/apps/fumadocs-example/lib/mdx-components.tsx b/apps/fumadocs-example/lib/mdx-components.tsx new file mode 100644 index 0000000..d53c345 --- /dev/null +++ b/apps/fumadocs-example/lib/mdx-components.tsx @@ -0,0 +1,243 @@ +import { Accordion, Accordions } from "fumadocs-ui/components/accordion"; +import { Callout } from "fumadocs-ui/components/callout"; +import { Card, Cards } from "fumadocs-ui/components/card"; +import { File, Files, Folder } from "fumadocs-ui/components/files"; +import { Step, Steps } from "fumadocs-ui/components/steps"; +import { Tab, Tabs } from "fumadocs-ui/components/tabs"; +import defaultMdxComponents from "fumadocs-ui/mdx"; +import type { + AccordionItemProps, + AudienceProps, + CalloutProps, + CommandTabsProps, + PromptProps, + TypeTableProps, +} from "leadtype/mdx"; +import type { ComponentType, ReactNode } from "react"; + +/** + * Map leadtype's custom MDX tag contract onto fumadocs-ui primitives. + * + * fumadocs-ui already covers most tags directly (Callout, Card, Steps, Tabs, + * Files); a few tags need light adapters (Accordion → fumadocs Accordions / + * Accordion, TypeTable, Audience). c15t-specific tags get pass-through stubs + * so the docs render without errors. + */ + +// Leadtype Accordion(container)/AccordionItem(item) → fumadocs Accordions/Accordion +function LeadtypeAccordion({ children }: { children?: ReactNode }) { + return {children}; +} + +function LeadtypeAccordionItem({ + title, + children, + defaultOpen: _defaultOpen, +}: Omit & { children?: ReactNode }) { + return {children}; +} + +// Leadtype Audience tag: hide content targeted at agents. +function Audience({ + target, + children, +}: Omit & { children?: ReactNode }) { + if (target === "agent") { + return null; + } + return <>{children}; +} + +// Leadtype TypeTable: render the extracted properties as a simple table. +// When the source preset couldn't extract from TS (empty `properties` + +// `name`/`path` present), render a placeholder instead of an empty table. +function TypeTable({ + properties, + title, + description, + name, + path, +}: TypeTableProps) { + const entries = Object.entries(properties ?? {}); + + if (entries.length === 0 && (name || path)) { + return ( +
+ {name ?? "Type"} + {path ? {path} : null} +

+ Extraction unavailable — install typescript in the docs + app and configure basePath on the source preset. +

+
+ ); + } + + return ( +
+ {title ?

{title}

: null} + {description ? ( +

{description}

+ ) : null} +
+ + + + + + + + + + + + {entries.map(([propName, property]) => ( + + + + + + + + ))} + +
PropertyTypeDescriptionDefaultRequired
{propName}{property.type}{property.description ?? "—"} + {property.default ?? "—"} + + {property.required ? "Required" : "Optional"} +
+
+
+ ); +} + +// Leadtype CommandTabs: render the command per package manager. +function CommandTabs(props: CommandTabsProps) { + const managers = ["npm", "pnpm", "yarn", "bun"] as const; + const commands: Record = {}; + if ("commands" in props && props.commands) { + for (const manager of managers) { + const value = props.commands[manager]; + if (value) { + commands[manager] = value; + } + } + } else if ("command" in props && props.command) { + const mode = props.mode ?? "run"; + for (const manager of managers) { + commands[manager] = formatCommand(manager, mode, props.command); + } + } + + const labels = Object.keys(commands); + if (labels.length === 0) { + return null; + } + + return ( + + {labels.map((label) => ( + +
+            {commands[label]}
+          
+
+ ))} +
+ ); +} + +function formatCommand( + manager: "npm" | "pnpm" | "yarn" | "bun", + mode: "run" | "install" | "create", + command: string +): string { + if (mode === "install") { + return manager === "npm" + ? `npm install ${command}` + : `${manager} add ${command}`; + } + if (mode === "create") { + if (manager === "npm") { + return `npm create ${command}`; + } + if (manager === "yarn") { + return `yarn create ${command}`; + } + if (manager === "pnpm") { + return `pnpm create ${command}`; + } + return `bun create ${command}`; + } + if (manager === "npm") { + return `npx ${command}`; + } + if (manager === "pnpm") { + return `pnpm dlx ${command}`; + } + if (manager === "yarn") { + return `yarn dlx ${command}`; + } + return `bunx ${command}`; +} + +function Prompt({ + title, + description, + children, +}: Omit & { children?: ReactNode }) { + return ( + + {description ?

{description}

: null} + {children} +
+ ); +} + +// c15t-specific components — pass through so MDX compiles without crashing. +const Passthrough: ComponentType<{ children?: ReactNode }> = ({ children }) => ( + <>{children} +); + +export const mdxComponents = { + ...defaultMdxComponents, + // Leadtype contract → fumadocs-ui + Callout: Callout as ComponentType, + Tabs, + Tab, + Steps, + Step, + Cards, + Card, + Accordion: LeadtypeAccordion, + AccordionItem: LeadtypeAccordionItem, + FileTree: Files, + File, + Folder, + TypeTable, + CommandTabs, + // c15t's alias. + PackageCommandTabs: CommandTabs, + Prompt, + Audience, + // c15t pass-throughs — these need real implementations for production. + ConsentBanner: Passthrough, + ConsentManager: Passthrough, + ConsentManagerDialog: Passthrough, + ConsentManagerProvider: Passthrough, + ConsentManagerWidget: Passthrough, + ConsentDialog: Passthrough, + ConsentDialogTrigger: Passthrough, + ConsentDialogLink: Passthrough, + ConsentButton: Passthrough, + ConsentWidget: Passthrough, + CookieBanner: Passthrough, + CustomConsentBanner: Passthrough, + IABConsentBanner: Passthrough, + IABConsentDialog: Passthrough, + DevTools: Passthrough, + Frame: Passthrough, + C15tPrefetch: Passthrough, + ContributorBlock: Passthrough, + Icon: ({ name }: { name?: string }) => {name}, +}; diff --git a/apps/fumadocs-example/lib/source.ts b/apps/fumadocs-example/lib/source.ts new file mode 100644 index 0000000..2231210 --- /dev/null +++ b/apps/fumadocs-example/lib/source.ts @@ -0,0 +1,29 @@ +import { resolve } from "node:path"; +import { loader } from "fumadocs-core/source"; +import { fumadocsSource } from "leadtype/fumadocs"; + +// process.cwd() is the app root when Next runs build/dev. +const contentDir = resolve( + process.cwd(), + "..", + "..", + ".docs-src", + "c15t", + "docs" +); + +/** + * fumadocs source backed by leadtype/fumadocs. Walks `.docs-src/c15t/docs`, + * picks up both `.mdx` pages and the c15t-authored `meta.json` files, and + * resolves `` / `` at build time via + * `mdxSourcePlugins` (wired in `next.config.mjs`). + */ +const fumadocsSourceResult = await fumadocsSource({ contentDir }); + +export const source = loader({ + baseUrl: "/docs", + source: fumadocsSourceResult, +}); + +/** Underlying leadtype DocsSource — call loadPage/buildSearchIndex/resolveInclude on this. */ +export const leadtypeSource = fumadocsSourceResult.leadtype; diff --git a/apps/fumadocs-example/mdx-components.tsx b/apps/fumadocs-example/mdx-components.tsx new file mode 100644 index 0000000..f7bba9c --- /dev/null +++ b/apps/fumadocs-example/mdx-components.tsx @@ -0,0 +1,13 @@ +// biome-ignore lint/suspicious/noExplicitAny: @next/mdx's MDXComponents type expects a permissive index signature +type MDXComponents = Record; + +import { mdxComponents } from "./lib/mdx-components"; + +/** + * `@next/mdx` reads this file to discover MDX components. Re-export the + * shared map from `lib/mdx-components.tsx` so the same components power + * both Server Component rendering and direct MDX imports. + */ +export function useMDXComponents(components: MDXComponents): MDXComponents { + return { ...components, ...mdxComponents }; +} diff --git a/apps/fumadocs-example/next-env.d.ts b/apps/fumadocs-example/next-env.d.ts new file mode 100644 index 0000000..830fb59 --- /dev/null +++ b/apps/fumadocs-example/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/fumadocs-example/next.config.mjs b/apps/fumadocs-example/next.config.mjs new file mode 100644 index 0000000..ef355e3 --- /dev/null +++ b/apps/fumadocs-example/next.config.mjs @@ -0,0 +1,27 @@ +import createMDX from "@next/mdx"; +import { rehypeCode } from "fumadocs-core/mdx-plugins"; +import { mdxSourcePlugins } from "leadtype/mdx"; +import remarkFrontmatter from "remark-frontmatter"; +import remarkGfm from "remark-gfm"; + +const withMDX = createMDX({ + options: { + // Frontmatter parsing must precede leadtype's stack (it expects bodies). + remarkPlugins: [remarkFrontmatter, remarkGfm, ...mdxSourcePlugins], + // Shiki-based highlighter from fumadocs-core. Pairs with fumadocs-ui's + // codeblock CSS so tokens, copy button, and frame styling all kick in. + rehypePlugins: [rehypeCode], + }, +}); + +/** @type {import("next").NextConfig} */ +const config = { + pageExtensions: ["ts", "tsx", "mdx"], + // c15t source lives outside the workspace package boundary at .docs-src/. + // Next + Turbopack would otherwise reject imports outside the app root. + experimental: { + externalDir: true, + }, +}; + +export default withMDX(config); diff --git a/apps/fumadocs-example/package.json b/apps/fumadocs-example/package.json new file mode 100644 index 0000000..49af657 --- /dev/null +++ b/apps/fumadocs-example/package.json @@ -0,0 +1,37 @@ +{ + "name": "fumadocs-example", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "setup:source": "bun run scripts/setup-source.ts", + "predev": "bun run setup:source", + "prebuild": "bun run setup:source", + "dev": "portless fumadocs -- next dev", + "build": "bun run --filter leadtype build && next build", + "start": "next start", + "check-types": "tsgo --noEmit" + }, + "dependencies": { + "@next/mdx": "^16.2.6", + "@tailwindcss/postcss": "^4.3.0", + "fumadocs-core": "^15.0.0", + "fumadocs-ui": "^15.0.0", + "leadtype": "workspace:*", + "next": "^15.0.0", + "next-mdx-remote-client": "^2.1.10", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.1", + "tailwindcss": "^4.3.0" + }, + "devDependencies": { + "@repo/typescript-config": "workspace:*", + "@types/node": "^25.6.2", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@typescript/native-preview": "7.0.0-dev.20260509.2", + "typescript": "6.0.3" + } +} diff --git a/apps/fumadocs-example/postcss.config.mjs b/apps/fumadocs-example/postcss.config.mjs new file mode 100644 index 0000000..c2ddf74 --- /dev/null +++ b/apps/fumadocs-example/postcss.config.mjs @@ -0,0 +1,5 @@ +export default { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; diff --git a/apps/fumadocs-example/scripts/setup-source.ts b/apps/fumadocs-example/scripts/setup-source.ts new file mode 100644 index 0000000..2f20305 --- /dev/null +++ b/apps/fumadocs-example/scripts/setup-source.ts @@ -0,0 +1,36 @@ +#!/usr/bin/env bun + +/** + * Shares the c15t-example's setup-source.ts so both apps point at the same + * .docs-src/c15t/ clone — keeps the comparison between hand-rolled UI and + * fumadocs-ui honest. Set C15T_REFRESH=1 to pull the latest c15t source. + */ + +import { spawnSync } from "node:child_process"; +import { existsSync } from "node:fs"; +import { mkdir } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const scriptsRoot = dirname(fileURLToPath(import.meta.url)); +const repoRoot = join(scriptsRoot, "..", "..", ".."); +const sourceRoot = join(repoRoot, ".docs-src", "c15t"); +const refresh = process.env.C15T_REFRESH === "1"; + +const run = (args: string[]) => { + const result = spawnSync("git", args, { stdio: "inherit" }); + if (result.status !== 0) { + throw new Error(`git ${args.join(" ")} failed`); + } +}; + +await mkdir(join(repoRoot, ".docs-src"), { recursive: true }); + +if (existsSync(join(sourceRoot, ".git"))) { + if (refresh) { + run(["-C", sourceRoot, "pull", "--ff-only"]); + } + process.stdout.write(`Using c15t source at ${sourceRoot}\n`); +} else { + run(["clone", "--depth", "1", "https://github.com/c15t/c15t", sourceRoot]); +} diff --git a/apps/fumadocs-example/tsconfig.json b/apps/fumadocs-example/tsconfig.json new file mode 100644 index 0000000..9a8844c --- /dev/null +++ b/apps/fumadocs-example/tsconfig.json @@ -0,0 +1,29 @@ +{ + "extends": "@repo/typescript-config/base.json", + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "preserve", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "incremental": true, + "isolatedModules": true, + "paths": { + "@/*": ["./*"] + }, + "plugins": [ + { + "name": "next" + } + ], + "allowJs": true + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules", ".next"] +} diff --git a/bun.lock b/bun.lock index fdc3270..8488e80 100644 --- a/bun.lock +++ b/bun.lock @@ -19,7 +19,7 @@ }, "apps/c15t-example": { "name": "c15t-example", - "version": "0.0.0", + "version": "0.0.1", "dependencies": { "@fontsource-variable/geist": "^5.2.8", "@fontsource-variable/geist-mono": "^5.2.7", @@ -94,6 +94,32 @@ "vite-tsconfig-paths": "^6.1.1", }, }, + "apps/fumadocs-example": { + "name": "fumadocs-example", + "version": "0.0.1", + "dependencies": { + "@next/mdx": "^16.2.6", + "@tailwindcss/postcss": "^4.3.0", + "fumadocs-core": "^15.0.0", + "fumadocs-ui": "^15.0.0", + "leadtype": "workspace:*", + "next": "^15.0.0", + "next-mdx-remote-client": "^2.1.10", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.1", + "tailwindcss": "^4.3.0", + }, + "devDependencies": { + "@repo/typescript-config": "workspace:*", + "@types/node": "^25.6.2", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@typescript/native-preview": "7.0.0-dev.20260509.2", + "typescript": "6.0.3", + }, + }, "evals": { "name": "leadtype-evals", "version": "0.0.0", @@ -106,7 +132,7 @@ }, "packages/leadtype": { "name": "leadtype", - "version": "0.1.0", + "version": "0.1.1", "bin": "./dist/cli.js", "dependencies": { "@types/mdast": "4.0.4", @@ -130,6 +156,7 @@ "@typescript/native-preview": "7.0.0-dev.20260509.2", "ai": "^6.0.177", "bash-tool": "1.3.16", + "fumadocs-core": "^15.0.0", "jiti": "^2.7.0", "just-bash": "2.14.5", "mdast-util-mdx": "3.0.0", @@ -146,6 +173,7 @@ "@tanstack/ai": ">=0.15.0", "ai": ">=6.0.0", "bash-tool": ">=1.3.16", + "fumadocs-core": ">=15.0.0", "jiti": ">=2.0.0", "just-bash": ">=2.14.5", "typescript": ">=5.0.0", @@ -155,6 +183,7 @@ "@tanstack/ai", "ai", "bash-tool", + "fumadocs-core", "jiti", "just-bash", "typescript", @@ -174,6 +203,8 @@ "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], + "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], + "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.82.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-xdHTjL1GlUlDugHq/I47qdOKp/ROPvuHl7ROJCgUQigbvPu7asf9KcAcU1EqdrP2LuVhEKaTs7Z+ShpZDRzHdQ=="], @@ -352,16 +383,76 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], + "@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="], + + "@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="], + + "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.8", "", { "dependencies": { "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A=="], + + "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], + "@fontsource-variable/geist": ["@fontsource-variable/geist@5.2.8", "", {}, "sha512-cJ6m9e+8MQ5dCYJsLylfZrgBh6KkG4bOLckB35Tr9J/EqdkEM6QllH5PxqP1dhTvFup+HtMRPuz9xOjxXJggxw=="], "@fontsource-variable/geist-mono": ["@fontsource-variable/geist-mono@5.2.7", "", {}, "sha512-ZKlZ5sjtalb2TwXKs400mAGDlt/+2ENLNySPx0wTz3bP3mWARCsUW+rpxzZc7e05d2qGch70pItt3K4qttbIYA=="], + "@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.6.2", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA=="], + "@google/genai": ["@google/genai@1.51.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-vTZZF3CSimN7cn2zsLpW2p5WF0eZa5Gz69ITMPCNHpPrDlAstOfGifSfi0p/s9Z9400f7xJRkgvkQNrcM7pJ6w=="], "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], "@iconify/utils": ["@iconify/utils@3.1.1", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.2" } }, "sha512-MwzoDtw9rO1x+qfgLTV/IVXsHDBqeYZoMIQC8SfxfYSlaSUG+oWiAcoiB1yajAda6mqblm4/1/w2E8tRu7a7Tw=="], + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], + + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], + + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], + + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], + + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], + + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], + + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], + + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], + + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], + + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], + + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], + + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], + + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], + + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], + + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], + + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], + + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], + + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], + + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], + + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], + + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], + + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], + + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], + "@inquirer/external-editor": ["@inquirer/external-editor@1.0.3", "", { "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA=="], "@jitl/quickjs-ffi-types": ["@jitl/quickjs-ffi-types@0.32.0", "", {}, "sha512-v9T+GQpmk43VDJ7d72sf0Nexhk+ArvtUihW27dy7lqAl0zBObFKtSBBIm5RBjwIhE8VwsPPm9PNuvPvNqLWUEg=="], @@ -402,6 +493,26 @@ "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], + "@next/env": ["@next/env@15.5.18", "", {}, "sha512-hAV85Ckd9QR6RvH04MEKwsfLTksvFpO47j9xwtoIuvuPnlwecpSi+uZTtm8HirVbtlI2Fnz//xpcSTjFdyJk+g=="], + + "@next/mdx": ["@next/mdx@16.2.6", "", { "dependencies": { "source-map": "^0.7.0" }, "peerDependencies": { "@mdx-js/loader": ">=0.15.0", "@mdx-js/react": ">=0.15.0" }, "optionalPeers": ["@mdx-js/loader", "@mdx-js/react"] }, "sha512-0hdoSkzRbyud1dNRRDiyqD9FrxR2wwdiW+ffhYx+n+fXrFOJ7Nwpi8o7nUz2LiiM44BB9M0eIO1Evy3BBrS50A=="], + + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-w0WvQf1n+txiwns/9pwIQteCJpZTbxzO2SE0FLcwuD4v0WEh1JPOjdyxWL21XwJsdpx8cFRjyzxzCS/siP7HcQ=="], + + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-znn71QmDuxm+BOaglihMZfvyySMnNljkVIY5Z2TCssBmm+WqL6c19VhtH5ktFkHa8EZ2bnTUpcNcmNSQsg67og=="], + + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-yPPe5MNL+igZUa+OsqQJisqSfh6oarIuA1Q0BDxljGJhRQyZeP+WRHh7rs/jZUGMh5aY0YdIjXZG0VohkKkUdw=="], + + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-glaCczEWIrHsokFZ3pP08U4BpKxwIdnT+txdOM32OBgpL9Yw4aqx8NejmgtZQZOdstQ5f0L3CasIZudzCuD+nw=="], + + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.18", "", { "os": "linux", "cpu": "x64" }, "sha512-oUfg2EgJmU3R0OCOWiokGFUTvZiPfXtriXiuF3YNxRoROCdgvTedHIzYoeKH34gsZxS/V7mHbfq2hpAHwhH1/A=="], + + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.18", "", { "os": "linux", "cpu": "x64" }, "sha512-JLxSP3KTd9iu/bvUMQxH7RJo9xKSHf55/6RPE4a6FTSZygGn7uvZbCej0AHXydwkggQGSD9UddSjwv6Xz5ESfA=="], + + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-ir1v7enP52K2HNz3tQQvwF+x7VNxBk1ciiZ18WBPvxf4C59IqdfmHPJYK3vH7rSxpuCVw/8C712wTXNAtEp+NA=="], + + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.18", "", { "os": "win32", "cpu": "x64" }, "sha512-LIu5me6QTANCd25E7I5uIEfvgQ06RK7tvHAbYo3zCb3VpxQEPvMcSpd87NwUABDT6MbGPdEGR5VRiK4PPTJhQg=="], + "@nodable/entities": ["@nodable/entities@2.1.0", "", {}, "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], @@ -422,6 +533,8 @@ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], + "@orama/orama": ["@orama/orama@3.1.18", "", {}, "sha512-a61ljmRVVyG5MC/698C8/FfFDw5a8LOIvyOLW5fztgUXqUpc1jOfQzOitSCbge657OgXXThmY3Tk8fpiDb4UcA=="], + "@oxc-project/types": ["@oxc-project/types@0.128.0", "", {}, "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ=="], "@playwright/test": ["@playwright/test@1.59.1", "", { "dependencies": { "playwright": "1.59.1" }, "bin": { "playwright": "cli.js" } }, "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg=="], @@ -446,14 +559,76 @@ "@protobufjs/utf8": ["@protobufjs/utf8@1.1.1", "", {}, "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg=="], + "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], + + "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], + + "@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA=="], + + "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], + + "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="], + + "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], + "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], + "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], + + "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="], + + "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="], + + "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="], + + "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="], + + "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], + + "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], + + "@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w=="], + + "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA=="], + + "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="], + + "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], + + "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="], + "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], + "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], + + "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.10", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A=="], + "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g=="], "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], + "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="], + + "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], + + "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="], + + "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="], + + "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], + + "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], + + "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="], + + "@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="], + + "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="], + + "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug=="], + + "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], + "@repo/typescript-config": ["@repo/typescript-config@workspace:packages/typescript-config"], "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.18", "", { "os": "android", "cpu": "arm64" }, "sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ=="], @@ -540,10 +715,30 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.2", "", { "os": "win32", "cpu": "x64" }, "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA=="], + "@shikijs/core": ["@shikijs/core@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA=="], + + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA=="], + + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g=="], + + "@shikijs/langs": ["@shikijs/langs@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg=="], + + "@shikijs/rehype": ["@shikijs/rehype@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@types/hast": "^3.0.4", "hast-util-to-string": "^3.0.1", "shiki": "3.23.0", "unified": "^11.0.5", "unist-util-visit": "^5.1.0" } }, "sha512-GepKJxXHbXFfAkiZZZ+4V7x71Lw3s0ALYmydUxJRdvpKjSx9FOMSaunv6WRLFBXR6qjYerUq1YZQno+2gLEPwA=="], + + "@shikijs/themes": ["@shikijs/themes@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA=="], + + "@shikijs/transformers": ["@shikijs/transformers@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/types": "3.23.0" } }, "sha512-F9msZVxdF+krQNSdQ4V+Ja5QemeAoTQ2jxt7nJCwhDsdF1JWS3KxIQXA3lQbyKwS3J61oHRUSv4jYWv3CkaKTQ=="], + + "@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="], + + "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@streamdown/mermaid": ["@streamdown/mermaid@1.0.2", "", { "dependencies": { "mermaid": "^11.12.2" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-Fr/4sBWnAeSnxM3PcrV/+DiZe5oPMq9gOkUIAH7ZauJeuwrZ/DVzD4g0zlav6AH0axh2m/sOfrfLtY5aLT7niw=="], + "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], + "@tailwindcss/node": ["@tailwindcss/node@4.3.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.21.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.0" } }, "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g=="], "@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.0", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.0", "@tailwindcss/oxide-darwin-arm64": "4.3.0", "@tailwindcss/oxide-darwin-x64": "4.3.0", "@tailwindcss/oxide-freebsd-x64": "4.3.0", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", "@tailwindcss/oxide-linux-x64-musl": "4.3.0", "@tailwindcss/oxide-wasm32-wasi": "4.3.0", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" } }, "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg=="], @@ -572,6 +767,8 @@ "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA=="], + "@tailwindcss/postcss": ["@tailwindcss/postcss@4.3.0", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.3.0", "@tailwindcss/oxide": "4.3.0", "postcss": "^8.5.10", "tailwindcss": "4.3.0" } }, "sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w=="], + "@tailwindcss/vite": ["@tailwindcss/vite@4.3.0", "", { "dependencies": { "@tailwindcss/node": "4.3.0", "@tailwindcss/oxide": "4.3.0", "tailwindcss": "4.3.0" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw=="], "@tanstack/ai": ["@tanstack/ai@0.15.0", "", { "dependencies": { "@ag-ui/core": "0.0.49", "@tanstack/ai-event-client": "0.2.9", "partial-json": "^0.1.7" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0" }, "optionalPeers": ["@opentelemetry/api"] }, "sha512-Ft1mZ1mwrtbmXjuarJ4I1f+HZwZ+doqg6EIaBtBTvZ7FHMtKzWIxqRngKOwBazv5Csv2lyOMM9E1nvH3/kzEKA=="], @@ -796,6 +993,8 @@ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], + "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="], "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], @@ -868,6 +1067,8 @@ "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], + "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], "collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="], @@ -876,6 +1077,8 @@ "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], + "compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="], + "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], @@ -894,6 +1097,8 @@ "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], "cytoscape": ["cytoscape@3.33.3", "", {}, "sha512-Gej7U+OKR+LZ8kvX7rb2HhCYJ0IhvEFsnkud4SB1PR+BUY/TsSO0dmOW59WEVLu51b1Rm+gQRKoz4bLYxGSZ2g=="], @@ -994,6 +1199,8 @@ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], @@ -1108,16 +1315,26 @@ "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "fumadocs-core": ["fumadocs-core@15.8.5", "", { "dependencies": { "@formatjs/intl-localematcher": "^0.6.2", "@orama/orama": "^3.1.14", "@shikijs/rehype": "^3.13.0", "@shikijs/transformers": "^3.13.0", "github-slugger": "^2.0.0", "hast-util-to-estree": "^3.1.3", "hast-util-to-jsx-runtime": "^2.3.6", "image-size": "^2.0.2", "negotiator": "^1.0.0", "npm-to-yarn": "^3.0.1", "path-to-regexp": "^8.3.0", "react-remove-scroll": "^2.7.1", "remark": "^15.0.1", "remark-gfm": "^4.0.1", "remark-rehype": "^11.1.2", "scroll-into-view-if-needed": "^3.1.0", "shiki": "^3.13.0", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "@mixedbread/sdk": "^0.19.0", "@oramacloud/client": "1.x.x || 2.x.x", "@tanstack/react-router": "1.x.x", "@types/react": "*", "algoliasearch": "5.x.x", "lucide-react": "*", "next": "14.x.x || 15.x.x", "react": "18.x.x || 19.x.x", "react-dom": "18.x.x || 19.x.x", "react-router": "7.x.x", "waku": "^0.26.0" }, "optionalPeers": ["@mixedbread/sdk", "@oramacloud/client", "@tanstack/react-router", "@types/react", "algoliasearch", "lucide-react", "next", "react", "react-dom", "react-router", "waku"] }, "sha512-hyJtKGuB2J/5y7tDfI1EnGMKlNbSXM5N5cpwvgCY0DcBJwFMDG/GpSpaVRzh3aWy67pAYDZFIwdtbKXBa/q5bg=="], + + "fumadocs-example": ["fumadocs-example@workspace:apps/fumadocs-example"], + + "fumadocs-ui": ["fumadocs-ui@15.8.5", "", { "dependencies": { "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-direction": "^1.1.1", "@radix-ui/react-navigation-menu": "^1.2.14", "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-presence": "^1.1.5", "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tabs": "^1.1.13", "class-variance-authority": "^0.7.1", "fumadocs-core": "15.8.5", "lodash.merge": "^4.6.2", "next-themes": "^0.4.6", "postcss-selector-parser": "^7.1.0", "react-medium-image-zoom": "^5.4.0", "scroll-into-view-if-needed": "^3.1.0", "tailwind-merge": "^3.3.1" }, "peerDependencies": { "@types/react": "*", "next": "14.x.x || 15.x.x", "react": "18.x.x || 19.x.x", "react-dom": "18.x.x || 19.x.x", "tailwindcss": "^3.4.14 || ^4.0.0" }, "optionalPeers": ["@types/react", "next", "tailwindcss"] }, "sha512-9pyB+9rOOsrFnmmZ9xREp/OgVhyaSq2ocEpqTNbeQ7tlJ6JWbdFWfW0C9lRXprQEB6DJWUDtDxqKS5QXLH0EGA=="], + "gaxios": ["gaxios@7.1.4", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA=="], "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], + "get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], + "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], @@ -1148,10 +1365,14 @@ "hast-util-to-estree": ["hast-util-to-estree@3.1.3", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-attach-comments": "^3.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w=="], + "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], + "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="], "hast-util-to-parse5": ["hast-util-to-parse5@8.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA=="], + "hast-util-to-string": ["hast-util-to-string@3.0.1", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A=="], + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], "hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], @@ -1178,6 +1399,8 @@ "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "image-size": ["image-size@2.0.2", "", { "bin": { "image-size": "bin/image-size.js" } }, "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w=="], + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="], @@ -1278,6 +1501,8 @@ "lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="], + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "lodash.startcase": ["lodash.startcase@4.4.0", "", {}, "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg=="], "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], @@ -1428,6 +1653,14 @@ "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], + "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + + "next": ["next@15.5.18", "", { "dependencies": { "@next/env": "15.5.18", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.18", "@next/swc-darwin-x64": "15.5.18", "@next/swc-linux-arm64-gnu": "15.5.18", "@next/swc-linux-arm64-musl": "15.5.18", "@next/swc-linux-x64-gnu": "15.5.18", "@next/swc-linux-x64-musl": "15.5.18", "@next/swc-win32-arm64-msvc": "15.5.18", "@next/swc-win32-x64-msvc": "15.5.18", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-eKL8zUJkX9Y5lE+RX/2YJoItVdGlIscyVyboeD9wSpp0PaGqjoA4tTpT2qPqz9ax+5IzGESyLSeZ/RCwbSZ2uQ=="], + + "next-mdx-remote-client": ["next-mdx-remote-client@2.1.10", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@mdx-js/mdx": "^3.1.1", "@mdx-js/react": "^3.1.1", "remark-mdx-remove-esm": "^1.3.1", "serialize-error": "^13.0.1", "vfile": "^6.0.3", "vfile-matter": "^5.0.1" }, "peerDependencies": { "react": ">= 19.1.0", "react-dom": ">= 19.1.0" } }, "sha512-wpLjRYyvOJRewb/PLwID0qbi7RGDiwg6QpTKcM36OnjJ2WIU9nOg0xRs3Q7WgfPteDlFx+zZkJHJ6WrvmIulEg=="], + + "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="], + "nf3": ["nf3@0.3.16", "", {}, "sha512-Gs0xRPpUm2nDkqbi40NJ9g7qDIcjcJzgExiydnq6LAyqhI2jfno8wG3NKTL+IiJsx799UHOb1CnSd4Wg4SG4Pw=="], "nitro": ["nitro@3.0.260429-beta", "", { "dependencies": { "consola": "^3.4.2", "crossws": "^0.4.5", "db0": "^0.3.4", "env-runner": "^0.1.7", "h3": "^2.0.1-rc.20", "hookable": "^6.1.1", "nf3": "^0.3.16", "ocache": "^0.1.4", "ofetch": "^2.0.0-alpha.3", "ohash": "^2.0.11", "rolldown": "^1.0.0-rc.17", "srvx": "^0.11.15", "unenv": "^2.0.0-rc.24", "unstorage": "^2.0.0-alpha.7" }, "peerDependencies": { "@vercel/queue": "^0.1.6", "dotenv": "*", "giget": "*", "jiti": "^2.6.1", "rollup": "^4.60.2", "vite": "^7 || ^8", "xml2js": "^0.6.2", "zephyr-agent": "^0.2.0" }, "optionalPeers": ["@vercel/queue", "dotenv", "giget", "jiti", "rollup", "vite", "xml2js", "zephyr-agent"], "bin": { "nitro": "dist/cli/index.mjs" } }, "sha512-KweLVCUN5X9v9g+4yxAyRcz3FcOlnjmt9FyrAIWDxJETJmNT7I0JV0clgsONjo2nI0U5gwedXYA3RaNtF5XWzg=="], @@ -1446,8 +1679,12 @@ "node-releases": ["node-releases@2.0.38", "", {}, "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw=="], + "non-error": ["non-error@0.1.0", "", {}, "sha512-TMB1uHiGsHRGv1uYclfhivcnf0/PdFp2pNqRxXjncaAsjYMoisaQJI+SSZCqRq+VliwRTC8tsMQfmrWjDMhkPQ=="], + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + "npm-to-yarn": ["npm-to-yarn@3.0.1", "", {}, "sha512-tt6PvKu4WyzPwWUzy/hvPFqn+uwXO0K1ZHka8az3NnrhWJDmSqI8ncWq0fkL0k/lmmi5tAC11FXwXuh0rFbt1A=="], + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], "nypm": ["nypm@0.6.6", "", { "dependencies": { "citty": "^0.2.2", "pathe": "^2.0.3", "tinyexec": "^1.1.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-vRyr0r4cbBapw07Xw8xrj9Teq3o7MUD35rSaTcanDbW+aK2XHDgJFiU6ZTj2GBw7Q12ysdsyFss+Vdz4hQ0Y6Q=="], @@ -1462,6 +1699,10 @@ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "oniguruma-parser": ["oniguruma-parser@0.12.2", "", {}, "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw=="], + + "oniguruma-to-es": ["oniguruma-to-es@4.3.6", "", { "dependencies": { "oniguruma-parser": "^0.12.2", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA=="], + "openai": ["openai@6.35.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-L/skwIGnt5xQZHb0UfTu9uAUKbis3ehKypOuJKi20QvG7UStV6C8IC3myGYHcdiF4kms/bAvOJ9UqqNWqi8x/Q=="], "outdent": ["outdent@0.5.0", "", {}, "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q=="], @@ -1502,6 +1743,8 @@ "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], + "path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], + "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], @@ -1524,6 +1767,8 @@ "postcss": ["postcss@8.5.14", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg=="], + "postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], "prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="], @@ -1550,6 +1795,14 @@ "react-dom": ["react-dom@19.2.5", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.5" } }, "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag=="], + "react-medium-image-zoom": ["react-medium-image-zoom@5.4.5", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-58QSIRK6X3uw2fSTejJRnH0JuKTZl7ZJYX+sAMaYx4YTEm33gsNdnP5RuQSCnBiAvisQeErqZWAT31bR89WB6g=="], + + "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], + + "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], + + "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], + "read-yaml-file": ["read-yaml-file@1.1.0", "", { "dependencies": { "graceful-fs": "^4.1.5", "js-yaml": "^3.6.1", "pify": "^4.0.1", "strip-bom": "^3.0.0" } }, "sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA=="], "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], @@ -1564,6 +1817,12 @@ "recma-stringify": ["recma-stringify@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-to-js": "^2.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g=="], + "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], + + "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], + + "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], + "rehype-harden": ["rehype-harden@1.1.8", "", { "dependencies": { "unist-util-visit": "^5.0.0" } }, "sha512-Qn7vR1xrf6fZCrkm9TDWi/AB4ylrHy+jqsNm1EHOAmbARYA6gsnVJBq/sdBh6kmT4NEZxH5vgIjrscefJAOXcw=="], "rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="], @@ -1580,6 +1839,8 @@ "remark-mdx": ["remark-mdx@3.1.1", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg=="], + "remark-mdx-remove-esm": ["remark-mdx-remove-esm@1.3.1", "", { "dependencies": { "@types/mdast": "^4.0.4", "mdast-util-mdxjs-esm": "^2.0.1", "unist-util-remove": "^4.0.0" }, "peerDependencies": { "unified": "^11" } }, "sha512-POa8abdiuicD2e+zQkclxzJa5JEGLtV8XIOFVvisnGuw4l4xd6dfQozedwqR8JTeXQmxLebvYhlbwHoQP9RWkw=="], + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], "remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], @@ -1620,18 +1881,26 @@ "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + "scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "", { "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="], + "seek-bzip": ["seek-bzip@2.0.0", "", { "dependencies": { "commander": "^6.0.0" }, "bin": { "seek-bunzip": "bin/seek-bunzip", "seek-table": "bin/seek-bzip-table" } }, "sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg=="], "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "serialize-error": ["serialize-error@13.0.1", "", { "dependencies": { "non-error": "^0.1.0", "type-fest": "^5.4.1" } }, "sha512-bBZaRwLH9PN5HbLCjPId4dP5bNGEtumcErgOX952IsvOhVPrm3/AeK1y0UHA/QaPG701eg0yEnOKsCOC6X/kaA=="], + "seroval": ["seroval@1.5.4", "", {}, "sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw=="], "seroval-plugins": ["seroval-plugins@1.5.4", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw=="], + "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "shiki": ["shiki@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/engine-javascript": "3.23.0", "@shikijs/engine-oniguruma": "3.23.0", "@shikijs/langs": "3.23.0", "@shikijs/themes": "3.23.0", "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA=="], + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], @@ -1684,8 +1953,12 @@ "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], + "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], + "stylis": ["stylis@4.4.0", "", {}, "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA=="], + "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], + "tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="], "tailwindcss": ["tailwindcss@4.3.0", "", {}, "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q=="], @@ -1732,6 +2005,8 @@ "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], + "type-fest": ["type-fest@5.6.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="], + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], @@ -1754,6 +2029,8 @@ "unist-util-position-from-estree": ["unist-util-position-from-estree@2.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ=="], + "unist-util-remove": ["unist-util-remove@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg=="], + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], @@ -1770,6 +2047,10 @@ "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], + + "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], + "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], @@ -1858,6 +2139,42 @@ "@openrouter/sdk/zod": ["zod@4.4.1", "", {}, "sha512-a6ENMBBGZBsnlSebQ/eKCguSBeGKSf4O7BPnqVPmYGtpBYI7VSqoVqw+QcB7kPRjbqPwhYTpFbVj/RqNz/CT0Q=="], + "@radix-ui/react-accordion/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-collapsible/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-collection/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-focus-scope/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-navigation-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-popover/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-popover/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-popper/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-portal/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-roving-focus/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-scroll-area/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-tabs/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], @@ -1932,6 +2249,8 @@ "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], @@ -1956,6 +2275,30 @@ "whatwg-encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "@radix-ui/react-accordion/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-arrow/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-collapsible/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-focus-scope/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-navigation-menu/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-popper/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-portal/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-roving-focus/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-scroll-area/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-tabs/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "@tanstack/ai-anthropic/@tanstack/ai/@tanstack/ai-event-client": ["@tanstack/ai-event-client@0.2.8", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.1" }, "peerDependencies": { "@tanstack/ai": "0.14.0" } }, "sha512-pdvT1hh7UorwBiDLrXnI5BFDfc7JSqnN06LaQHG4TGRl/621UnUrh16hQKOMn/msArKiCj+n+TG+cw4mDwMaLw=="], "@tanstack/ai-gemini/@tanstack/ai/@tanstack/ai-event-client": ["@tanstack/ai-event-client@0.2.8", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.1" }, "peerDependencies": { "@tanstack/ai": "0.14.0" } }, "sha512-pdvT1hh7UorwBiDLrXnI5BFDfc7JSqnN06LaQHG4TGRl/621UnUrh16hQKOMn/msArKiCj+n+TG+cw4mDwMaLw=="], diff --git a/docs/authoring/components.mdx b/docs/authoring/components.mdx index 9a4fedb..d50358d 100644 --- a/docs/authoring/components.mdx +++ b/docs/authoring/components.mdx @@ -27,7 +27,7 @@ The remark pipeline recognizes these names. If your components use the same name If your app uses different names, you have two options: rename to match, or add a custom remark plugin that maps your names back to the contract above. -Runtime registration, heading IDs, and "On this page" sidebars are docs-app concerns. See [Render MDX and TOC](/docs/build/render-mdx-and-toc) for that setup. +Runtime registration, heading IDs, and "On this page" sidebars are docs-app concerns. See [Use the source primitive](/docs/build/use-the-source-primitive) for runtime wiring and [`leadtype/mdx`](/docs/reference/mdx) for the full prop-type contract. ## Reuse shared content diff --git a/docs/authoring/frontmatter.mdx b/docs/authoring/frontmatter.mdx index c9ad82d..17bf1ef 100644 --- a/docs/authoring/frontmatter.mdx +++ b/docs/authoring/frontmatter.mdx @@ -12,8 +12,8 @@ Every page is a `.mdx` file with a YAML frontmatter block. Leadtype reads three ```mdx --- -title: "Connect a docs site" -description: "Wire leadtype into a docs app build." +title: "Build a docs site" +description: "Pick the right leadtype integration shape for your docs app." group: docs-site --- ``` @@ -57,7 +57,7 @@ Declare children in the config to build deeper trees: title: "Build a Docs Site", children: [ { slug: "remote-source", title: "Remote source" }, - { slug: "connect-docs-site", title: "Connect a docs site" }, + { slug: "build-a-docs-site", title: "Build a docs site" }, ], } ``` diff --git a/docs/build/add-search.mdx b/docs/build/add-search.mdx index e99ac88..d540b82 100644 --- a/docs/build/add-search.mdx +++ b/docs/build/add-search.mdx @@ -2,6 +2,7 @@ title: "Add search" description: "Generate a static docs search index, query it at runtime, and optionally stream source-grounded answers." group: docs-site +order: 60 --- # Add search diff --git a/docs/build/build-a-docs-site.mdx b/docs/build/build-a-docs-site.mdx new file mode 100644 index 0000000..14d57b7 --- /dev/null +++ b/docs/build/build-a-docs-site.mdx @@ -0,0 +1,83 @@ +--- +title: "Build a docs site" +description: "Pick the right leadtype integration shape for your docs app, and what each path gives you." +group: docs-site +order: 10 +--- + +# Build a docs site + +Leadtype offers two integration shapes for a docs site. Pick the one that fits how your app is built — both produce the same content, just at different layers of your stack. + +## Pick your path + +docs/*.mdx + meta.json"] + primitive["createDocsSource()
(leadtype/mdx + leadtype/fumadocs)"] + cli["leadtype generate
(CLI)"] + app["docs app
(Next, Astro, Vite, Fumadocs)"] + artifacts["public/
llms.txt · docs/*.md
search-index · sitemap"] + src --> primitive --> app + src --> cli --> artifacts --> app`} /> + +| Path | When to choose | What you wire | +| --- | --- | --- | +| **Source primitive** | Most cases. You're building a Next, Vite, Astro, or Fumadocs app and compile MDX with your bundler. | `createDocsSource()` (or `leadtype/fumadocs`) + `mdxSourcePlugins` | +| **Static artifacts** | Your runtime needs flat files on disk (CDN-only deploy, static export, agent-only consumption, multi-app sharing). | `leadtype generate` CLI in your build script | + +The two paths are not mutually exclusive — you can use the source primitive for your UI and still run `leadtype generate` for the `llms.txt` and agent-readability artifacts. + +## Source primitive: the common path + +If your docs app compiles MDX through a bundler, use the source primitive. It gives your runtime: + +- `loadPage(slug)` returning frontmatter + AST + serialized markdown + TOC +- `getNavigation()` returning the resolved group tree +- `buildSearchIndex()` returning a static search index +- `resolveInclude()` for programmatic partial loading + +→ [Use the source primitive](/docs/build/use-the-source-primitive) — generic recipe +→ [Integrate with Fumadocs](/docs/build/integrate-with-fumadocs) — first-party adapter + +## Static artifacts: the CLI path + +If your runtime needs files on disk — for a CDN-only deploy, a static export, a separate agent-only service, or to share artifacts across multiple sibling apps — run the CLI. It writes: + +- `public/llms.txt` and `public/llms-full.txt` +- `public/docs/*.md` (markdown mirrors) +- `public/docs/search-index.json` + `search-content.json` +- `public/docs/sitemap.xml`, `sitemap.md`, `robots.txt`, `agent-readability.json` + +→ [Generate static artifacts](/docs/build/generate-static-artifacts) — CLI workflow + +## Add the cross-cutting features + +Whichever path you pick, the same follow-on guides apply: + +- [Add search](/docs/build/add-search) — local search index + optional source-grounded answers +- [Optimize docs for agents](/docs/build/optimize-docs-for-agents) — markdown responses, llms.txt, discovery files +- [Validate in CI](/docs/build/validate-in-ci) — lint frontmatter, meta.json, internal links + +## Configure product and groups + +Both paths read the same `docs/docs.config.ts`. Source repos own this file so groups and product metadata version with the docs: + +```ts +import { defineDocsConfig } from "leadtype"; + +export default defineDocsConfig({ + product: { + name: "c15t", + summary: "Consent infrastructure for modern web apps.", + bullets: ["Framework integrations.", "Consent primitives.", "Audit-friendly docs."], + bestStartingPoints: [{ urlPath: "/docs" }, { urlPath: "/docs/quickstart" }], + }, + groups: [ + { slug: "get-started", title: "Get Started" }, + { slug: "guides", title: "Guides" }, + { slug: "reference", title: "Reference" }, + ], +}); +``` + +Pages declare `group:` in frontmatter; the config declares titles, order, and descriptions. See [Frontmatter](/docs/authoring/frontmatter) for the page-level schema. diff --git a/docs/build/connect-docs-site.mdx b/docs/build/generate-static-artifacts.mdx similarity index 58% rename from docs/build/connect-docs-site.mdx rename to docs/build/generate-static-artifacts.mdx index 30e05fc..1f664bb 100644 --- a/docs/build/connect-docs-site.mdx +++ b/docs/build/generate-static-artifacts.mdx @@ -1,14 +1,15 @@ --- -title: "Connect a docs site" -description: "Build a shared docs app from source docs that live with the code they document." +title: "Generate static artifacts" +description: "Run leadtype generate from your build pipeline to write llms.txt, markdown mirrors, search index, sitemap, and agent-readability files to disk." group: docs-site +order: 40 --- -# Connect a docs site +# Generate static artifacts -Use this path when you run a docs app that renders docs from one or more source repos. This is the main Leadtype docs-site model: content stays next to the code it documents, while the docs app clones or checks out that content during build and serves the generated artifacts. +Use this path when your runtime needs files on disk — a CDN-only deploy, a static export, a separate agent-only service, or multiple sibling apps sharing one corpus. The `leadtype generate` CLI walks your MDX source and writes a complete set of files into `public/`. -Your framework still owns routes, HTML, styling, and deployment. Leadtype owns the source-to-artifact pipeline. +For app runtimes that compile MDX through a bundler (Next, Astro, Vite, Fumadocs), prefer [the source primitive](/docs/build/use-the-source-primitive) — it skips the disk round-trip. ## The flow @@ -18,23 +19,23 @@ Your framework still owns routes, HTML, styling, and deployment. Leadtype owns t lint["leadtype lint"] generate["leadtype generate"] public["public/
llms.txt · llms-full.txt
docs/*.md
search · agent metadata"] - app["docs app
routing + HTML"] + app["any consumer
(docs app, agent, CDN)"] repo --> clone --> lint --> generate --> public --> app`} /> ## Fetch the source repo -In CI, check out the docs source before the docs app build. For a public repo, a shallow clone is enough: +In CI, check out the docs source before the build. For a public repo, a shallow clone is enough: ```bash rm -rf .docs-src/c15t git clone --depth 1 https://github.com/c15t/c15t .docs-src/c15t ``` -For private repos, use your CI platform's checkout action or a read-only deploy key. The important part is that Leadtype receives a normal filesystem path whose root contains `docs/`. +For private repos, use your CI platform's checkout action or a read-only deploy key. The important part is that leadtype receives a normal filesystem path whose root contains `docs/`. ## Lint before generate -Run lint against the fetched docs before writing generated output: +Run lint against the fetched docs before writing generated output. Lint fails with file and line context, which is easier to debug than a later app build using stale artifacts: ```bash npx leadtype lint .docs-src/c15t/docs \ @@ -43,11 +44,9 @@ npx leadtype lint .docs-src/c15t/docs \ --max-warnings 0 ``` -Lint fails with file and line context for frontmatter, internal links, placeholders, and schema issues. That is easier to debug than a later app build using stale or partial artifacts. +## Generate -## Generate hosted artifacts - -Point `--src` at the fetched repository root and `--out` at the docs app public directory: +Point `--src` at the fetched repo root and `--out` at the directory you'll serve: ```bash npx leadtype generate \ @@ -66,7 +65,7 @@ That command writes: Use `--json` in CI so automation can record resolved groups, output files, filters, and search index stats. -## Add sibling sources +## Multi-source mounting Some projects keep docs and release notes side by side instead of putting everything under `docs/`: @@ -106,9 +105,9 @@ npx leadtype generate \ That still keeps an internal generated copy at `public/docs/changelog/v1.md` for search and runtime helpers, but it also writes `public/changelog/v1.md` and emits canonical links such as `/changelog/v1` and `/changelog/v1.md` in `llms.txt`, search metadata, sitemap entries, and `agent-readability.json`. -## Wire it into the app build +## Wire it into the build -Make the source checkout, lint, and generation steps run before the framework build: +Make checkout, lint, and generation run before the framework build: ```json { @@ -128,33 +127,9 @@ npx leadtype lint docs --error-unknown npx leadtype generate --src . --out public --base-url https://docs.example.com ``` -## Configure product and groups +## Use library APIs for custom pipelines -The source repo should own `docs/docs.config.ts` so its groups and product metadata version with the docs. `leadtype generate` loads this file automatically from the docs folder: - -```ts -import { defineDocsConfig } from "leadtype"; - -export default defineDocsConfig({ - product: { - name: "c15t", - summary: "Consent infrastructure for modern web apps.", - bullets: ["Framework integrations.", "Consent primitives.", "Audit-friendly docs."], - bestStartingPoints: [{ urlPath: "/docs" }, { urlPath: "/docs/quickstart" }], - }, - groups: [ - { slug: "get-started", title: "Get Started" }, - { slug: "guides", title: "Guides" }, - { slug: "reference", title: "Reference" }, - ], -}); -``` - -Pages declare `group:` in frontmatter. The config declares titles, order, and descriptions. Together they drive navigation, `llms.txt`, search metadata, and package `AGENTS.md` sections. If no config exists, the CLI infers groups from frontmatter, but inferred groups have no descriptions or custom order. See [Frontmatter](/docs/authoring/frontmatter). - -## Use scripts for custom pipelines - -The CLI is the happy path. Use the library APIs directly when you need custom plugin order, filters, or generated JSON paths. Keep conversion first because LLM files, search, and Agent Readability artifacts read the generated markdown. +The CLI is the happy path. Use the library APIs directly when you need custom plugin order, filters, or generated JSON paths. Keep conversion first — LLM files, search, and Agent Readability artifacts read the generated markdown: ```ts import { convertAllMdx } from "leadtype/convert"; @@ -181,13 +156,6 @@ await convertAllMdx({ enrichFrontmatterFromGit: true, }); -await convertAllMdx({ - srcDir: `${sourceRoot}/changelog`, - outDir: "public/docs/changelog", - remarkPlugins: [remarkInclude, ...defaultRemarkPlugins], - enrichFrontmatterFromGit: true, -}); - await generateLlmsTxt({ srcDir: sourceRoot, outDir: "public", @@ -211,7 +179,7 @@ await generateDocsSearchFiles({ mounts, }); -const agentReadability = await generateAgentReadabilityArtifacts({ +await generateAgentReadabilityArtifacts({ outDir: "public", baseUrl: "https://docs.example.com", product: docsConfig.product, @@ -227,24 +195,12 @@ const navigation = await resolveDocsNavigation({ }); ``` -Write `navigation` to a generated JSON file if your sidebar should use the same group tree as `llms.txt`. Write `agentReadability.manifest` if your runtime needs to merge docs pages with marketing, blog, changelog, or product routes. Custom scripts that need plain static markdown at mounted paths should also copy `public/docs/changelog/*.md` to `public/changelog/*.md`; the CLI does that automatically. - -## Wire the app runtime - -After generation, choose the runtime pieces your site needs: - -- Render source MDX as HTML with your own components: [Render MDX and TOC](/docs/build/render-mdx-and-toc). -- Return markdown for agent requests and serve discovery files: [Optimize docs for agents](/docs/build/optimize-docs-for-agents). -- Add local search and optional source-grounded answers: [Add search](/docs/build/add-search). - ## Verify After a clean build, inspect these files: -- `public/docs/index.md` — converted markdown for the source repo home page. -- `public/llms.txt` — hosted routing index with page-level markdown links. -- `public/llms-full.txt` — all generated markdown docs in one fallback file. -- `public/docs/search-index.json` and `public/docs/search-content.json` — non-empty search files. -- `public/docs/agent-readability.json` — manifest for markdown responses, JSON-LD, sitemap, and robots helpers. - -Then start your app and check at least one browser HTML page and one markdown response path. +- `public/docs/index.md` — converted markdown for the source repo home page +- `public/llms.txt` — hosted routing index with page-level markdown links +- `public/llms-full.txt` — all generated markdown docs in one fallback file +- `public/docs/search-index.json` and `public/docs/search-content.json` — non-empty search files +- `public/docs/agent-readability.json` — manifest for markdown responses, JSON-LD, sitemap, and robots helpers diff --git a/docs/build/integrate-with-fumadocs.mdx b/docs/build/integrate-with-fumadocs.mdx new file mode 100644 index 0000000..cda892c --- /dev/null +++ b/docs/build/integrate-with-fumadocs.mdx @@ -0,0 +1,150 @@ +--- +title: "Integrate with Fumadocs" +description: "Wire leadtype's content layer into a fumadocs app for nav, search, and includes." +group: docs-site +order: 30 +--- + +# Integrate with Fumadocs + +Fumadocs owns the docs UI — sidebar, layout, theme, search input, route handlers. Leadtype owns the **content pipeline** — `` expansion, frontmatter resolution, the typed tag contract, and the agent/LLM artifacts. The two pair naturally: fumadocs reads source MDX, leadtype provides the source primitive and the build-time transforms that go through fumadocs's MDX compiler. + +This page covers the wiring. For everything fumadocs does after that, see fumadocs's own docs. + +## TL;DR + +```sh +bun add leadtype fumadocs-core fumadocs-ui +``` + +```ts title="lib/source.ts" +import { loader } from "fumadocs-core/source"; +import { fumadocsSource } from "leadtype/fumadocs"; + +const fumaSource = await fumadocsSource({ contentDir: "./content/docs" }); +export const source = loader({ baseUrl: "/docs", source: fumaSource }); +export const leadtypeSource = fumaSource.leadtype; +``` + +```ts title="next.config.mjs" +import createMDX from "@next/mdx"; +import { mdxSourcePlugins } from "leadtype/mdx"; + +export default createMDX({ + options: { remarkPlugins: [...mdxSourcePlugins] }, +})({ pageExtensions: ["ts", "tsx", "mdx"] }); +``` + +That's the minimum. The rest of this page covers `DocsLayout` wiring, page rendering, custom-tag mapping, and search. + +## Install + +```sh +bun add leadtype fumadocs-core fumadocs-ui +``` + +Leadtype declares `fumadocs-core >= 15` as an **optional peer dependency** — install it explicitly when you want the adapter. + +## Wire the source + +```ts title="content/source.ts" +import { loader } from "fumadocs-core/source"; +import { fumadocsSource } from "leadtype/fumadocs"; + +export const source = loader({ + baseUrl: "/docs", + source: await fumadocsSource({ + contentDir: "./content/docs", + groups: [ + { slug: "get-started", title: "Get Started" }, + { slug: "guides", title: "Guides" }, + ], + }), +}); +``` + +`fumadocsSource()` pre-walks `contentDir`, returns a synchronous `Source` for fumadocs, and exposes the underlying `DocsSource` on `source.leadtype` for `loadPage()` / `buildSearchIndex()` / `resolveInclude()`. + +## Add the source preset to your MDX compiler + +Leadtype's MDX-source preset expands ``, resolves ``, and strips authoring `import`s — at build time. Everything else stays JSX for your runtime components. + +```ts title="next.config.mjs" +import createMDX from "@next/mdx"; +import { mdxSourcePlugins } from "leadtype/mdx"; + +const withMDX = createMDX({ + options: { + remarkPlugins: [...mdxSourcePlugins], + }, +}); + +export default withMDX({ pageExtensions: ["ts", "tsx", "mdx"] }); +``` + +The same plugin list works with `@mdx-js/rollup`, fumadocs-mdx, and any other MDX compiler that accepts a remark plugin list. + +## Implement the tag components + +Use the prop types from `leadtype/mdx` so your components type-check against the same contract the source preset expects: + +```tsx +import type { CalloutProps, TabsProps, StepProps } from "leadtype/mdx"; + +export function Callout({ variant, title, children }: CalloutProps) { + return ( + + ); +} +``` + +See [MDX tag contract](/docs/reference/mdx) for the full prop list across all 14 tags. + +## Load a page from a server component + +```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 ( +
+

{page.title}

+ +
+ ); +} +``` + +If you prefer fumadocs's built-in page resolution, call `source.getPage(slug)` and import the source `.mdx` directly through fumadocs-mdx as you normally would — the `mdxSourcePlugins` preset will resolve includes during MDX compilation. + +## Add search + +Build a search index from the same source: + +```ts title="app/api/search/route.ts" +import { source } from "@/content/source"; + +const bundle = await source.leadtype.buildSearchIndex(); + +export async function GET() { + return Response.json(bundle.index); +} +``` + +For provider-specific search (Vercel AI, TanStack, Cloudflare), wire the bundle into a `leadtype/search/*` adapter. See [Search](/docs/build/add-search). diff --git a/docs/build/optimize-docs-for-agents.mdx b/docs/build/optimize-docs-for-agents.mdx index ee7d83f..d8540bb 100644 --- a/docs/build/optimize-docs-for-agents.mdx +++ b/docs/build/optimize-docs-for-agents.mdx @@ -2,6 +2,7 @@ title: "Optimize docs for agents" description: "Set up llms.txt, markdown mirrors, JSON-LD, sitemaps, robots.txt, and audit checks for an agent-readable docs site." group: docs-site +order: 50 --- # Optimize docs for agents @@ -206,7 +207,7 @@ Put that logic wherever your framework can intercept docs requests before its HT | Framework/runtime | Where it usually goes | | --- | --- | -| TanStack Start / nitro | `server/middleware/agent-readability.ts` (h3). One middleware handles both the markdown response and the sitemap/robots regenerators — runs in dev, preview, and prod. See [`apps/example/server/middleware/agent-readability.ts`](https://github.com/inthhq/leadtype/blob/main/apps/example/server/middleware/agent-readability.ts) for the canonical reference. | +| TanStack Start / nitro | `server/middleware/agent-readability.ts` (h3). One middleware handles both the markdown response and the sitemap/robots regenerators — runs in dev, preview, and prod. | | Nuxt | `server/middleware/agent-readability.ts` (h3) — same shape as the TanStack Start example. | | Next.js | `middleware.ts` (Edge) or a catch-all route handler before the docs page. | | Astro | An endpoint at `pages/docs/[...slug].md.ts` or `astro:middleware`. | diff --git a/docs/build/render-mdx-and-toc.mdx b/docs/build/render-mdx-and-toc.mdx deleted file mode 100644 index bb4102c..0000000 --- a/docs/build/render-mdx-and-toc.mdx +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: "Render MDX and TOC" -description: "Set up runtime MDX components, stable heading IDs, and a table of contents from the generated navigation manifest." -group: docs-site ---- - -# Render MDX and TOC - -Leadtype does not render your website. Your docs app owns MDX runtime components, layout, accessibility, and the "On this page" UI. Leadtype defines the contracts that keep the rendered HTML and generated markdown aligned. - -## Register MDX components - -Use the component names Leadtype's remark stack knows how to flatten: - -```tsx -import { mdxComponents } from "@/components/docs-mdx"; - -export const components = { - ...mdxComponents, -}; -``` - -The naming contract is documented in [Components](/docs/authoring/components). If your app uses different component names, add a custom remark plugin that maps them back before Leadtype flattens MDX to markdown. - -## Use the same heading slugs - -`resolveDocsNavigation` extracts a table of contents from page headings. Your rendered headings need matching `id` attributes or sidebar links will miss. - -Import `slugifyDocsHeading` from the fs-free readability entry point: - -```tsx -import { slugifyDocsHeading } from "leadtype/llm/readability"; -import { type ComponentPropsWithoutRef, isValidElement } from "react"; - -type HeadingProps = ComponentPropsWithoutRef<"h1">; - -function textFromChildren(children: unknown): string { - if (typeof children === "string" || typeof children === "number") { - return String(children); - } - if (Array.isArray(children)) { - return children.map(textFromChildren).join(""); - } - if (isValidElement(children)) { - const elementProps = children.props as { children?: unknown }; - return textFromChildren(elementProps.children); - } - return ""; -} - -function createHeading(level: 1 | 2 | 3 | 4 | 5 | 6) { - return ({ children, id, ...props }: HeadingProps) => { - const Component = `h${level}` as const; - const headingId = id ?? slugifyDocsHeading(textFromChildren(children)); - return ( - - {children} - - ); - }; -} -``` - -Authors can still pin an anchor by passing an explicit `id`. - -## Generate navigation with TOC data - -`resolveDocsNavigation` includes `toc` on every page. The default range is `h2` to `h3`. - -```ts -import { resolveDocsNavigation } from "leadtype/llm"; -import docsConfig from "../docs/docs.config"; - -const navigation = await resolveDocsNavigation({ - srcDir: ".", - baseUrl: "https://example.com", - groups: docsConfig.groups, - toc: { minLevel: 2, maxLevel: 4 }, -}); -``` - -Write the navigation object to a generated JSON file and import it from your sidebar. - -## Render the sidebar - -Look up the current page in the manifest and pass `currentPage.toc` to your sidebar component. The example app includes a complete React implementation with scroll-spy, hash sync, active highlighting, and sticky positioning: - -[`apps/example/src/components/table-of-contents.tsx`](https://github.com/inthhq/leadtype/blob/main/apps/example/src/components/table-of-contents.tsx) - -## Troubleshooting - -- **TOC links do not scroll anywhere.** Your heading IDs do not match the extracted slugs. Use `slugifyDocsHeading` in the rendered heading components. -- **A page TOC is empty.** All headings are outside the configured `minLevel` and `maxLevel` range. Defaults exclude `h1` and headings deeper than `h3`. -- **Sidebar order differs from llms.txt.** The app is not using the same `docs.config.ts` groups as the generation step. diff --git a/docs/build/use-the-source-primitive.mdx b/docs/build/use-the-source-primitive.mdx new file mode 100644 index 0000000..b3b8e76 --- /dev/null +++ b/docs/build/use-the-source-primitive.mdx @@ -0,0 +1,332 @@ +--- +title: "Use the source primitive" +description: "Wire createDocsSource into Next, Astro, Vite, Nuxt, SvelteKit, or any MDX-aware bundler. Same primitive, multiple host shapes." +group: docs-site +order: 20 +--- + +# Use the source primitive + +`createDocsSource()` is the framework-neutral way to render leadtype-authored MDX in your own docs app. This page shows the wiring on top of the most common hosts. + +If you're using Fumadocs specifically, use [`leadtype/fumadocs`](/docs/build/integrate-with-fumadocs) — it's a thinner wrapper around this same primitive. + +## TL;DR + +The primitive itself is identical across frameworks: + +```ts title="lib/source.ts" +import { createDocsSource } from "leadtype"; + +export const source = await createDocsSource({ + contentDir: "./content/docs", + baseUrl: "https://example.com", +}); +``` + +Wire `mdxSourcePlugins` into your bundler's remark stack, then call `source.loadPage(slug)` from your framework's page renderer. The "Wire into your framework" section below has minimal setups for each host. + +## Install + +```sh +bun add leadtype +``` + +Plus an MDX integration for your bundler (`@next/mdx`, `@astrojs/mdx`, `@mdx-js/rollup`, etc.). + +## Wire into your framework + +`mdxSourcePlugins` expands `` partials and resolves `` at build time, while leaving every custom tag (``, ``, ``, …) as JSX for your runtime components. + +### Next App Router + +```ts title="next.config.mjs" +import createMDX from "@next/mdx"; +import { mdxSourcePlugins } from "leadtype/mdx"; + +export default createMDX({ + options: { remarkPlugins: [...mdxSourcePlugins] }, +})({ pageExtensions: ["ts", "tsx", "mdx"] }); +``` + +```tsx title="app/docs/[[...slug]]/page.tsx" +import { notFound } from "next/navigation"; +import { MDXRemote } from "next-mdx-remote-client/rsc"; +import { source } from "@/lib/source"; +import { mdxComponents } from "@/lib/mdx-components"; + +export default async function DocsPage({ + params, +}: { params: Promise<{ slug?: string[] }> }) { + const { slug = [] } = await params; + const page = await source.loadPage(slug); + if (!page) notFound(); + + return ( +
+

{page.title}

+ {page.description ?

{page.description}

: null} + +
+ ); +} + +export async function generateStaticParams() { + const pages = await source.listPages(); + return pages.map((page) => ({ slug: page.slug })); +} +``` + +### Astro Content Collections + +```ts title="astro.config.mjs" +import { defineConfig } from "astro/config"; +import mdx from "@astrojs/mdx"; +import { mdxSourcePlugins } from "leadtype/mdx"; + +export default defineConfig({ + integrations: [mdx({ remarkPlugins: [...mdxSourcePlugins] })], +}); +``` + +Use Astro's native content collection schema for typed frontmatter. Call `source.loadPage()` from leadtype only when you need programmatic include resolution, search, or navigation. See [Astro's content collections docs](https://docs.astro.build/en/guides/content-collections/) for the routing pattern. + +### TanStack Start + +```ts title="vite.config.ts" +import mdx from "@mdx-js/rollup"; +import { tanstackStart } from "@tanstack/react-start/plugin/vite"; +import viteReact from "@vitejs/plugin-react"; +import { mdxSourcePlugins } from "leadtype/mdx"; +import remarkFrontmatter from "remark-frontmatter"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [ + { + ...mdx({ + providerImportSource: "@mdx-js/react", + remarkPlugins: [remarkFrontmatter, ...mdxSourcePlugins], + }), + enforce: "pre", + }, + tanstackStart(), + viteReact({ include: /\.(mdx|[jt]sx?)$/ }), + ], +}); +``` + +```tsx title="src/routes/docs/$.tsx" +import { createFileRoute, notFound } from "@tanstack/react-router"; +import { type ComponentType, lazy, Suspense, useMemo } from "react"; +import docsPages from "@/generated/docs-pages.json"; + +type DocsPage = { slug: string[]; globKey: string; urlPath: string }; + +const pagesBySlug = new Map( + (docsPages as DocsPage[]).map((p) => [p.slug.join("/"), p]) +); + +// import.meta.glob keys are POSIX paths; the build-time manifest writes +// matching keys so each slug maps to its lazy-loaded MDX module. +const mdxModules = import.meta.glob<{ default: ComponentType }>( + "../../../content/docs/**/*.mdx" +); + +const TRAILING_SLASH = /\/+$/; + +function resolvePage(splat: string | undefined): DocsPage | null { + if (!splat) return null; + return pagesBySlug.get(splat.replace(TRAILING_SLASH, "")) ?? null; +} + +function MissingMdxModule({ urlPath }: { urlPath: string }) { + return ( +
+ MDX module not found for {urlPath}. Re-run your docs + manifest script after adding new pages. +
+ ); +} + +export const Route = createFileRoute("/docs/$")({ + beforeLoad: ({ params }) => { + if (!resolvePage(params._splat)) throw notFound(); + }, + component: DocsCatchAllRoute, +}); + +function DocsCatchAllRoute() { + const { _splat } = Route.useParams(); + // beforeLoad guarantees this is non-null when the component renders. + const page = resolvePage(_splat) as DocsPage; + + // `lazy()` must run outside the render body — calling it inline returns + // a fresh lazy component every render, defeats Suspense caching, and + // remounts the MDX page on every parent re-render. Memoize on the + // globKey so the cached lazy component is reused across renders. Guard + // the lookup in case the manifest references a file the glob didn't + // pick up (stale manifest, file deleted between builds, …). + const MdxComponent = useMemo(() => { + const loader = mdxModules[page.globKey]; + if (!loader) { + return () => ; + } + return lazy(loader); + }, [page.globKey, page.urlPath]); + + return ( + + + + ); +} +``` + +Generate `docs-pages.json` at build time by calling `createDocsSource().listPages()` from a build script and writing each page's `slug`, `urlPath`, and `globKey` (path relative to the catch-all route, POSIX separators). + +### Vite + `@mdx-js/rollup` (works for Vue, Solid, Svelte starters) + +```ts title="vite.config.ts" +import mdx from "@mdx-js/rollup"; +import { mdxSourcePlugins } from "leadtype/mdx"; + +export default { + plugins: [ + mdx({ remarkPlugins: [...mdxSourcePlugins] }), + // ...your framework plugin: viteReact / vue / solid / svelte + ], +}; +``` + +### Nuxt + +```ts title="nuxt.config.ts" +import { mdxSourcePlugins } from "leadtype/mdx"; + +export default defineNuxtConfig({ + modules: ["@nuxtjs/mdc"], + mdc: { remarkPlugins: [...mdxSourcePlugins] }, +}); +``` + +### SvelteKit + `mdsvex` + +```ts title="svelte.config.js" +import { mdsvex } from "mdsvex"; +import { mdxSourcePlugins } from "leadtype/mdx"; + +export default { + extensions: [".svelte", ".svx", ".mdx"], + preprocess: mdsvex({ remarkPlugins: [...mdxSourcePlugins] }), +}; +``` + +### Pattern for any other host + +If your framework's MDX integration accepts a remark plugin list, leadtype works. Three pieces every time: + +1. Add `mdxSourcePlugins` to the remark list so `` and `` resolve at build time. +2. Implement components against the [tag types from `leadtype/mdx`](/docs/reference/mdx). +3. Call `createDocsSource()` if you want navigation, search, or programmatic page loading. + +## Implement the tag components + +Import prop types from `leadtype/mdx` and implement against your framework: + +```tsx title="lib/mdx-components.tsx" +import type { CalloutProps, TabsProps, StepProps } from "leadtype/mdx"; + +export const mdxComponents = { + Callout: ({ variant, title, children }: CalloutProps & { children?: React.ReactNode }) => ( + + ), + // ... Tabs, Tab, Steps, Step, Cards, Card, TypeTable, etc. +}; +``` + +The full tag inventory and intersection patterns for React, Vue, Svelte, Solid, and Astro live in [`leadtype/mdx`](/docs/reference/mdx). + +## Build the sidebar from navigation + +```tsx title="components/sidebar.tsx" +import { source } from "@/lib/source"; + +export async function Sidebar({ currentUrlPath }: { currentUrlPath: string }) { + const navigation = await source.getNavigation(); + return ( + + ); +} +``` + +Each `page` carries a `toc` field (`DocsTableOfContentsItem[]`) you can render as an "On this page" rail. + +## Match heading slugs + +`source.loadPage().toc` uses `slugifyDocsHeading` to derive anchor IDs. Your rendered heading components need matching `id` attributes: + +```tsx +import { slugifyDocsHeading } from "leadtype/llm/readability"; + +function textFromChildren(children: unknown): string { + if (typeof children === "string" || typeof children === "number") { + return String(children); + } + if (Array.isArray(children)) return children.map(textFromChildren).join(""); + // (recurse into React elements as needed) + return ""; +} + +const Heading2 = ({ children, id, ...props }: React.HTMLAttributes) => { + const headingId = id ?? slugifyDocsHeading(textFromChildren(children)); + return

{children}

; +}; +``` + +Wire `Heading2` (and `h3`, etc.) into your `mdxComponents` map. Authors can still pin an explicit `id` on a heading. + +## Build a search index + +```tsx title="app/api/search/route.ts" +import { source } from "@/lib/source"; + +const bundle = await source.buildSearchIndex(); + +export async function GET() { + return Response.json(bundle.index); +} +``` + +For provider-specific search (Vercel AI, TanStack, Cloudflare), feed the bundle into a `leadtype/search/*` adapter — see [Add search](/docs/build/add-search). + +## Troubleshooting + +- **`` tags survive into the rendered output.** You forgot to add `mdxSourcePlugins` to your MDX compiler's remark plugin list. +- **`` renders unresolved.** The source preset converts these to `` only when `extractTypeFromFile` succeeds. Make sure `typescript` is installed in the docs app and `basePath` resolves to the project that contains the type. +- **TOC links don't scroll.** Rendered heading IDs don't match. Wire `slugifyDocsHeading` into your heading components. +- **Sidebar order doesn't match `llms.txt`.** Your app and the CLI are loading different `docs.config.ts` files. Centralize the config and import it in both. + +## Reference + +- [`leadtype/mdx`](/docs/reference/mdx) — tag types, `mdxSourcePlugins`, include helpers +- [`createDocsSource`](/docs/reference/source) — full API surface for the primitive +- [`leadtype/fumadocs`](/docs/build/integrate-with-fumadocs) — fumadocs adapter recipe diff --git a/docs/build/validate-in-ci.mdx b/docs/build/validate-in-ci.mdx index e5822a1..213772d 100644 --- a/docs/build/validate-in-ci.mdx +++ b/docs/build/validate-in-ci.mdx @@ -2,6 +2,7 @@ title: "Validate in CI" description: "Run leadtype lint in CI so frontmatter, navigation, and link issues fail PRs before publish." group: docs-site +order: 70 --- # Validate in CI diff --git a/docs/docs.config.ts b/docs/docs.config.ts index f1fc592..9a3c826 100644 --- a/docs/docs.config.ts +++ b/docs/docs.config.ts @@ -15,7 +15,8 @@ export default defineDocsConfig({ { urlPath: "/docs" }, { urlPath: "/docs/quickstart" }, { urlPath: "/docs/how-it-works" }, - { urlPath: "/docs/build/connect-docs-site" }, + { urlPath: "/docs/build/build-a-docs-site" }, + { urlPath: "/docs/build/use-the-source-primitive" }, { urlPath: "/docs/build/add-search" }, { urlPath: "/docs/build/optimize-docs-for-agents" }, { urlPath: "/docs/package-docs/bundle" }, diff --git a/docs/index.mdx b/docs/index.mdx index 47b8a08..ce36574 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -29,44 +29,34 @@ It is not a docs website framework. Bring your own UI — Next.js, TanStack Star site_out --> search bundle_out --> offline_agents`} /> -## Choose your path +## Start here -Pick the job that matches what you are building. Each path starts with the minimum setup, then links to the lower-level API reference only when you need it. +**[Quickstart →](/docs/quickstart)** — three steps to a working source primitive, then plug it into your framework. + +Or skip ahead: [Build a docs site](/docs/build/build-a-docs-site) compares every integration path side-by-side. + +## What leadtype produces -## What you get - - - - Author MDX with familiar components — `Callout`, `Tabs`, `Steps`, `Mermaid`, `TypeTable`, and others. Add `group:` in frontmatter to place pages in the navigation tree. - - - For a website: converts MDX to markdown, builds `llms.txt` plus root `llms-full.txt`, generates a search index, writes Agent Readability discovery files, and resolves navigation. With `--bundle`: emits `AGENTS.md` plus per-topic `.md` files with relative links that still work after npm install. - - - Humans get HTML from your app. HTTP agents get markdown via content negotiation or `/llms.txt`. Package consumers can point their root `AGENTS.md` or README at `node_modules//AGENTS.md` for version-matched offline docs. - - - ## Next -- New here? Read the **[Quickstart](/docs/quickstart)** — five minutes to generated output. +- New here? Read the **[Quickstart](/docs/quickstart)** — three steps to a working source. - Want the mental model first? Read **[How it works](/docs/how-it-works)** for the pipeline diagram and the names of every artifact it produces. - Comparing tools? See **[Methodology](/docs/methodology)** for how leadtype differs from Fumadocs, Starlight, and Mintlify. diff --git a/docs/methodology.mdx b/docs/methodology.mdx index e714a61..c09b1f3 100644 --- a/docs/methodology.mdx +++ b/docs/methodology.mdx @@ -21,17 +21,21 @@ Choose a website framework when the main job is publishing a polished docs UI qu ## What leadtype owns -- MDX-to-markdown conversion via a remark plugin stack. -- `llms.txt`, markdown mirrors, and root `llms-full.txt` fallback context for agents. -- A static, edge-safe search index plus optional source-grounded answer streaming. -- Lint rules for frontmatter, navigation metadata, and internal links. -- CLI orchestration so the whole pipeline runs from one command. +- The **MDX tag contract** — typed prop shapes for ``, ``, ``, ``, and the rest of the custom tags (see [`leadtype/mdx`](/docs/reference/mdx)). +- A **build-time source preset** that expands `` partials, resolves ``, and strips authoring `import`s — without flattening live components. +- A **framework-neutral source primitive** (`createDocsSource`) exposing navigation, page loading, search index building, and include resolution. +- A thin **fumadocs adapter** (`leadtype/fumadocs`) wiring the source into fumadocs's `Source` interface. +- The agent / LLM pipeline: MDX-to-markdown conversion, `llms.txt`, root `llms-full.txt` fallback context, markdown mirrors, sitemap, and AGENTS.md bundles. +- A static, edge-safe **search index** plus optional source-grounded answer streaming. +- **Lint rules** for frontmatter, navigation metadata, and internal links. +- **CLI orchestration** so the whole pipeline runs from one command. ## What leadtype does not own - Visual UI, theming, or component styling. +- Runtime component implementations — your docs app implements components against the tag contract (see [Components](/docs/authoring/components) and [`leadtype/mdx`](/docs/reference/mdx)). - Routing, hosting, deployment, or analytics. -- A prebuilt MDX component library — your docs app provides those (see [Components](/docs/authoring/components)). +- The MDX compiler — leadtype produces the source preset; your bundler (Next App Router, Vite, fumadocs-mdx, Astro) does the compile. ## When the combination shines diff --git a/docs/package-docs/bundle.mdx b/docs/package-docs/bundle.mdx index ff43efc..f5dd9bc 100644 --- a/docs/package-docs/bundle.mdx +++ b/docs/package-docs/bundle.mdx @@ -31,7 +31,7 @@ The website is for humans and HTTP agents. The package-bundled docs are for codi [`AGENTS.md`](https://agents.md) is the **filesystem convention** that solves this. Tools like Claude Code, OpenAI Codex, Cursor, GitHub Copilot, Aider, Devin, and others can read `AGENTS.md` when working with files on disk. An `AGENTS.md` inside `node_modules//` is the right shape for version-matched package docs: relative links work, no network is required, and the content matches the installed version. -leadtype emits both — `llms.txt` in [website mode](/docs/build/connect-docs-site) for HTTP-discoverable agents, and `AGENTS.md` in `--bundle` mode for offline filesystem-discoverable agents. +leadtype emits both — `llms.txt` in [website mode](/docs/build/build-a-docs-site) for HTTP-discoverable agents, and `AGENTS.md` in `--bundle` mode for offline filesystem-discoverable agents. ## Generate into the package @@ -93,8 +93,7 @@ Add `AGENTS.md` and `docs` to `files` and run generation as a build or prepack s If you need full control over plugin order or custom validation, run the library APIs directly from a script: -```ts -// scripts/generate-docs.ts +```ts title="scripts/generate-docs.ts" import { rm } from "node:fs/promises"; import { convertAllMdx } from "leadtype/convert"; import { generateAgentsMd, resolveDocsNavigation } from "leadtype/llm"; @@ -169,7 +168,7 @@ This is the same pattern Next.js uses to point agents at `node_modules/next/dist Use this when **agents should understand the package from the installed dependency itself** — coding agents that don't have web access, IDE assistants, CLI tools, or air-gapped environments. -Don't use this if your only goal is a public docs website. For that, see [Connect a docs site](/docs/build/connect-docs-site). You can use both — they read the same source MDX, just emit different shapes. +Don't use this if your only goal is a public docs website. For that, see [Build a docs site](/docs/build/build-a-docs-site). You can use both — they read the same source MDX, just emit different shapes. ## What's next diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index 508c908..bae1e39 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -1,28 +1,23 @@ --- title: "Quickstart" -description: "Install leadtype, run the hosted-docs pipeline once, and inspect the generated artifacts." +description: "Install leadtype, author your first MDX page, and create a source. Then plug it into whatever framework you're using." group: get-started --- # Quickstart -Five minutes from a folder of MDX to generated docs artifacts. +Three steps to a working source primitive. Then plug it into your framework — Next, Fumadocs, Astro, Vite + Vue/Solid/Svelte, Nuxt, SvelteKit — using one of the recipes below. ## Install -`leadtype` ships a CLI plus focused library entry points. Start with the CLI; use the APIs only when your build needs custom plugin order, filtering, or output paths. - ## Author one page -Create `docs/index.mdx` in your repo: - -```mdx +```mdx title="content/docs/index.mdx" --- title: "My library" description: "What it does in one sentence." -group: get-started --- # My library @@ -32,67 +27,84 @@ Welcome. Every page needs a `title`. Add `group:` when the page should appear in generated navigation and `llms.txt` sections. See [Frontmatter](/docs/authoring/frontmatter) for the full content contract. -## Generate hosted docs output +## Create the source -```bash -npx leadtype generate \ - --src . \ - --out public \ - --base-url https://example.com -``` +```ts title="lib/source.ts" +import { createDocsSource } from "leadtype"; -That command reads `docs/*.mdx`, converts it to markdown, resolves groups, builds search JSON, and writes hosted agent artifacts. +export const source = await createDocsSource({ + contentDir: "./content/docs", + baseUrl: "https://example.com", +}); +``` - - - - - - - - - - - - - - +That's the framework-neutral primitive. You can now call: -Open `public/docs/index.md` first. It shows what your MDX becomes after the remark component flattening pipeline runs. Then open `public/llms.txt` to see the hosted routing file that HTTP agents start from. +- `source.loadPage(slug)` — resolved markdown + AST + frontmatter + TOC +- `source.listPages()` — every page's slug, urlPath, and metadata +- `source.getNavigation()` — grouped sidebar tree +- `source.buildSearchIndex()` — static search bundle +- `source.resolveInclude(specifier)` — standalone include resolver - - `apps/example/` in the [leadtype repo](https://github.com/inthhq/leadtype) is the production docs site for these docs. It runs the same pipeline you just ran, on TanStack Start, with markdown content negotiation, sitemap regeneration, and JSON-LD wired up. Clone it when you want a copy-pastable end-to-end setup. - +## Plug it into your framework -## Choose the next setup step - -| Goal | Next page | -| --- | --- | -| Wire the generator into an app build | [Connect a docs site](/docs/build/connect-docs-site) | -| Render HTML pages and an "On this page" sidebar | [Render MDX and TOC](/docs/build/render-mdx-and-toc) | -| Serve markdown, sitemaps, robots, and JSON-LD for agents | [Optimize docs for agents](/docs/build/optimize-docs-for-agents) | -| Query the generated static search index | [Add search](/docs/build/add-search) | -| Publish AGENTS.md inside an npm package | [Bundle docs into a package](/docs/package-docs/bundle) | +Pick the recipe that matches your stack. Each one shows the wiring on top of the same `createDocsSource()` you just set up. + + + + +## Or: write static artifacts to disk + +Some pipelines need flat files — CDN-only deploys, static exports, agent-only services, multiple sibling apps sharing one corpus. Use the CLI: + +```sh +npx leadtype generate --src . --out public --base-url https://example.com +``` + +That writes `llms.txt`, `llms-full.txt`, `docs/*.md`, search index, sitemap, and `agent-readability.json` to disk. See [Generate static artifacts](/docs/build/generate-static-artifacts) for the full flow. + +## Next steps + +| Goal | Page | +| --- | --- | +| Compare every integration shape side-by-side | [Build a docs site](/docs/build/build-a-docs-site) | +| Implement custom MDX tags (Callout, Tabs, Steps, …) | [`leadtype/mdx`](/docs/reference/mdx) | +| Add search | [Add search](/docs/build/add-search) | +| Validate frontmatter and links in CI | [Validate in CI](/docs/build/validate-in-ci) | +| Serve markdown, sitemaps, robots, and JSON-LD for agents | [Optimize docs for agents](/docs/build/optimize-docs-for-agents) | +| Publish AGENTS.md inside an npm package | [Bundle docs into a package](/docs/package-docs/bundle) | diff --git a/docs/reference/evals.mdx b/docs/reference/evals.mdx index 42b5ff8..3f3f277 100644 --- a/docs/reference/evals.mdx +++ b/docs/reference/evals.mdx @@ -16,7 +16,7 @@ Leadtype treats agent-facing docs as behavior, not just files on disk. The repo | Hosted docs | `llms.txt` + markdown mirrors + `llms-full.txt` variants | Simulates a hosted docs web root as local files. Agents start at `/llms.txt`, then choose page-level markdown, root `llms-full.txt`, or experimental grouped/router formats depending on the variant under test. | The hosted-docs benchmark uses the same nine-page corpus for every variant: -quickstart, how-it-works, frontmatter, components, connect-docs-site, +quickstart, how-it-works, frontmatter, components, build-a-docs-site, package-docs bundle, CLI, LLM bundles, and Search. Those pages are split across five groups: Get Started, Authoring, Build, Ship Package Docs, and Reference. diff --git a/docs/reference/llm.mdx b/docs/reference/llm.mdx index 0b46312..3e7e2a7 100644 --- a/docs/reference/llm.mdx +++ b/docs/reference/llm.mdx @@ -452,7 +452,7 @@ Now your sidebar can import a static manifest with the same group tree the LLM f ## Tables of contents -For the heading slug contract and renderer wiring, see [Render MDX and TOC](/docs/build/render-mdx-and-toc). This section covers the build-time APIs only. +For the heading slug contract and renderer wiring, see [Use the source primitive](/docs/build/use-the-source-primitive). This section covers the build-time APIs only. `resolveDocsNavigation` includes a `toc` array on every page by default. The default range is `h2`–`h3`, which keeps page-level `h1` titles out of sidebars. diff --git a/docs/reference/mdx.mdx b/docs/reference/mdx.mdx new file mode 100644 index 0000000..75d00b8 --- /dev/null +++ b/docs/reference/mdx.mdx @@ -0,0 +1,257 @@ +--- +title: "leadtype/mdx" +description: "Tag type contracts and the build-time source preset for consumers rendering MDX themselves." +group: reference +--- + +# `leadtype/mdx` + +The `leadtype/mdx` subpath is the **consumer-facing MDX surface** — everything you need to compile leadtype-authored MDX in your own renderer (fumadocs, Next App Router, Vite + @mdx-js, Astro content collections). + +It exports three things: + +1. **Tag type contracts** — typed prop shapes for every custom MDX tag. +2. **`mdxSourcePlugins`** — a remark preset that performs build-time resolution only (expand includes, resolve ``, strip authoring `import`s) and leaves every other custom tag as JSX. +3. **Include-resolution helpers** — `resolveInclude`, `parseIncludeSpecifier`, `extractMdxSection` for direct use. + +Pair it with `leadtype/remark` (markdown flattening for the agent/LLM pipeline) — they are sibling surfaces, not alternatives. Most projects use both. + +## The MDX-source preset + +```ts +import createMDX from "@next/mdx"; +import { mdxSourcePlugins } from "leadtype/mdx"; + +const withMDX = createMDX({ + options: { + remarkPlugins: [...mdxSourcePlugins], + }, +}); +``` + +The preset is intentionally minimal — only the transforms that **must** run at build time: + +| Plugin | Purpose | +| --- | --- | +| `remarkInclude` | Expands ``, ``, `` | +| `remarkResolveTypeTableJsx` | `` → `` | +| `remarkResolveDocPlaceholders` | Resolves `{{...}}` placeholders against doc context | +| `remarkRemoveImports` | Strips authoring `import` statements that aren't needed at runtime | + +Every other custom tag (``, ``, ``, ``, …) stays JSX so your runtime components render them. + +## Framework-neutral by design + +Tag types describe the **author surface** — the attributes that appear on a tag in source MDX. They deliberately do **not** import from any UI framework: `children` is typed as `unknown` so you can intersect with your framework's native child type. + +The same shape works in React, Vue, Svelte, Solid, Astro, Qwik, or anything else with an MDX compiler that accepts remark plugins. + +### React + +```tsx +import type { CalloutProps } from "leadtype/mdx"; +import type { HTMLAttributes, ReactNode } from "react"; + +type ReactCalloutProps = Omit & + HTMLAttributes & { children?: ReactNode }; + +export function Callout({ variant, title, children, ...rest }: ReactCalloutProps) { + return ( + + ); +} +``` + +### Vue + +```vue + + + +``` + +### Svelte + +```svelte + + + +``` + +### Solid + +```tsx +import type { CalloutProps } from "leadtype/mdx"; +import type { JSX } from "solid-js"; + +type SolidCalloutProps = Omit & { + children?: JSX.Element; +}; + +export function Callout(props: SolidCalloutProps) { + return ( + + ); +} +``` + +### Astro + +```astro +--- +import type { CalloutProps } from "leadtype/mdx"; +type Props = Omit; +const { variant = "info", title } = Astro.props as Props; +--- + + +``` + +### Full type inventory + +```ts +import type { + // Layout + AudienceProps, + AudienceTarget, + SectionProps, + DetailsProps, + + // Callouts + CalloutProps, + CalloutVariant, + CalloutTypeAlias, + + // Navigation / structure + TabsProps, + TabProps, + StepsProps, + StepProps, + AccordionProps, + AccordionItemProps, + + // Cards / topics + CardsProps, + CardProps, + CardVariant, + TopicSwitcherProps, + TopicSwitcherItem, + + // File tree + FileTreeProps, + FolderProps, + FileProps, + + // Code / commands + CommandTabsProps, + CommandMode, + PackageManager, + ExampleProps, + ExampleSourceFile, + PromptProps, + + // Type tables + TypeTableProps, + TypeTableProperty, + ExtractedTypeTableProps, + + // Diagrams + MermaidProps, +} from "leadtype/mdx"; +``` + +Every prop name is part of the 1.0 contract — bumping a shape is a breaking change. New optional props are minor. + +### Build-time only: `` + +`` is an authoring convenience. The source preset replaces it at build time with ``, so consumers only implement the runtime `` component: + +```ts +import type { TypeTableProps } from "leadtype/mdx"; + +export function TypeTable({ properties, title, description }: TypeTableProps) { + return ( +
+ {title ?

{title}

: null} + {description ?

{description}

: null} + + {Object.entries(properties).map(([name, prop]) => ( + + + + + + ))} +
{name}{prop.type}{prop.description}
+
+ ); +} +``` + +`` requires `typescript` to be installed as an optional peer dep in your docs project. + +## Include resolution + +`resolveInclude` reads + classifies an include target without going through remark — useful when loading a partial outside of the bundler pipeline: + +```ts +import { resolveInclude } from "leadtype/mdx"; + +const result = await resolveInclude("./shared/install.mdx#bun", { + fromDir: process.cwd(), +}); + +if (result.kind === "markdown") { + console.log(result.content); // frontmatter-stripped body + console.log(result.section); // "bun" +} else { + console.log(result.lang, result.content); // code-block form +} +``` + +`parseIncludeSpecifier(specifier)` and `extractMdxSection(root, id)` are exposed for callers that want to compose their own pipeline. + +## Re-exported path helpers + +Routing primitives that previously lived under `leadtype/internal` are re-exported for consumer use: + +```ts +import { + type DocsPathMount, + normalizeBaseUrl, + normalizeDocsPath, + normalizeUrlPrefix, + stripDocsExtension, + toDocsUrlPath, +} from "leadtype/mdx"; +``` diff --git a/docs/reference/source.mdx b/docs/reference/source.mdx new file mode 100644 index 0000000..63dbcc9 --- /dev/null +++ b/docs/reference/source.mdx @@ -0,0 +1,224 @@ +--- +title: "createDocsSource" +description: "Framework-neutral docs source primitive — navigation, page loader, search index, and include resolver." +group: reference +--- + +# `createDocsSource()` + +`createDocsSource()` is the **framework-neutral** entry point for any consumer that wants to render leadtype-authored MDX in their own renderer. It composes leadtype's primitives (`resolveDocsNavigation`, `convertMdxFile`, `createDocsSearchIndex`, `resolveInclude`) into a single source object. + +```ts +import { createDocsSource } from "leadtype"; + +const source = await createDocsSource({ + contentDir: "./content/docs", + baseUrl: "https://example.com", + groups: [ + { slug: "get-started", title: "Get Started" }, + { slug: "guides", title: "Guides" }, + ], +}); + +const page = await source.loadPage("quickstart"); +const navigation = await source.getNavigation(); +const search = await source.buildSearchIndex(); +``` + +For fumadocs specifically, use the thin [`leadtype/fumadocs`](/docs/build/integrate-with-fumadocs) adapter that wraps this primitive. + +## Configuration + +| Option | Type | Notes | +| --- | --- | --- | +| `contentDir` | `string` | Required. Directory containing source `.md` / `.mdx` files. | +| `baseUrl` | `string` | Used for absolute URLs in TOC and search index. | +| `groups` | `DocsGroup[]` | Doc groups for navigation. Empty groups = all pages ungrouped. | +| `mounts` | `DocsPathMount[]` | Multi-mount routing (advanced). | +| `remarkPlugins` | `PluggableList` | Defaults to `mdxSourcePlugins`. Pass `[]` to skip transforms. | +| `toc` | `DocsTableOfContentsOptions \| false` | TOC tuning; `false` skips TOC entirely. | +| `searchIndex` | `CreateDocsSearchIndexOptions` | Search-index tuning. | + +`createDocsSource` does no I/O on construction beyond a directory scan for `listPages()` caching. Page bodies are loaded lazily. + +## Source methods + +### `getNavigation(): Promise` + +Computes navigation from the configured groups + filesystem state. Identical shape to `resolveDocsNavigation` from `leadtype/llm`. + +### `listPages(): Promise` + +Enumerates every doc page under `contentDir`. Each entry includes: + +```ts +type DocsPageMeta = { + slug: string[]; // ["guides", "setup"] + urlPath: string; // "/docs/guides/setup" + relativePath: string; // "guides/setup" (no extension) + extension: ".md" | ".mdx"; + filePath: string; // absolute path + title: string; + description: string; + groups: string[]; +}; +``` + +Result is cached; subsequent calls don't re-walk the disk. + +### `loadPage(slug): Promise` + +Loads a single page. Returns `null` if no slug matches. Accepts either an array or a slash-joined string. + +```ts +const page = await source.loadPage("guides/setup"); +// or +const page = await source.loadPage(["guides", "setup"]); +``` + +Returned object extends `DocsPageMeta` with: + +```ts +{ + frontmatter: Record; + markdown: string; // resolved, plugin-transformed markdown + ast: Root; // mdast Root after the source preset — render this for live MDX + toc: DocsTableOfContentsItem[]; +} +``` + +### `buildSearchIndex(): Promise` + +Resolves every page through `convertMdxFile` and builds a `DocsSearchIndex`. Document IDs match each page's `urlPath`. + +### `resolveInclude(specifier, options?): Promise` + +Convenience wrapper around `resolveInclude` from `leadtype/mdx` with `fromDir` defaulted to `contentDir`. Useful for loading partials outside of the MDX compile path. + +## Choosing between `loadPage` and direct `.mdx` imports + +For most bundler-driven consumers (Next App Router, Vite, Astro), you'll **import source `.mdx` directly** and let the bundler's MDX pipeline compile it with `mdxSourcePlugins`. `loadPage()` is for cases where you need the resolved markdown body or AST programmatically: + +- Server-rendering through `next-mdx-remote` instead of bundler-native MDX +- Streaming partials to an agent / LLM at runtime +- Building a custom TOC or analyzing document structure + +## Framework integrations + +`createDocsSource()` is framework-neutral. Wire it into whatever you're already using: + +### Next App Router + +```ts title="next.config.mjs" +import createMDX from "@next/mdx"; +import { mdxSourcePlugins } from "leadtype/mdx"; + +export default createMDX({ + options: { remarkPlugins: [...mdxSourcePlugins] }, +})({ pageExtensions: ["ts", "tsx", "mdx"] }); +``` + +```tsx title="app/docs/[[...slug]]/page.tsx" +import { createDocsSource } from "leadtype"; +const source = await createDocsSource({ contentDir: "./content/docs" }); + +export default async function Page({ params }) { + const { slug } = await params; + const page = await source.loadPage(slug ?? []); + // … +} +``` + +### Astro Content Collections + +```ts title="astro.config.mjs" +import { defineConfig } from "astro/config"; +import mdx from "@astrojs/mdx"; +import { mdxSourcePlugins } from "leadtype/mdx"; + +export default defineConfig({ + integrations: [mdx({ remarkPlugins: [...mdxSourcePlugins] })], +}); +``` + +Use Astro's built-in content collection schema for typed frontmatter; use `createDocsSource()` only when you need leadtype's navigation, search index, or programmatic include resolution. + +### TanStack Start + +```ts title="vite.config.ts" +import mdx from "@mdx-js/rollup"; +import { tanstackStart } from "@tanstack/react-start/plugin/vite"; +import viteReact from "@vitejs/plugin-react"; +import { mdxSourcePlugins } from "leadtype/mdx"; +import remarkFrontmatter from "remark-frontmatter"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [ + { + ...mdx({ + providerImportSource: "@mdx-js/react", + remarkPlugins: [remarkFrontmatter, ...mdxSourcePlugins], + }), + enforce: "pre", + }, + tanstackStart(), + viteReact({ include: /\.(mdx|[jt]sx?)$/ }), + ], +}); +``` + +Pair with a TanStack Router catch-all (`src/routes/docs/$.tsx`) that consumes a build-time manifest generated from `createDocsSource().listPages()`. See [Use the source primitive → TanStack Start](/docs/build/use-the-source-primitive#tanstack-start) for the catch-all snippet. + +### Vite + `@mdx-js/rollup` (works for Vue, Solid, Svelte starters) + +```ts title="vite.config.ts" +import mdx from "@mdx-js/rollup"; +import { mdxSourcePlugins } from "leadtype/mdx"; + +export default { + plugins: [ + mdx({ remarkPlugins: [...mdxSourcePlugins] }), + // ...your framework plugin: viteReact / vue / solid / svelte + ], +}; +``` + +### Nuxt + +```ts title="nuxt.config.ts" +import { mdxSourcePlugins } from "leadtype/mdx"; + +export default defineNuxtConfig({ + modules: ["@nuxtjs/mdc"], + mdc: { + remarkPlugins: [...mdxSourcePlugins], + }, +}); +``` + +### SvelteKit + `mdsvex` + +```ts title="svelte.config.js" +import { mdsvex } from "mdsvex"; +import { mdxSourcePlugins } from "leadtype/mdx"; + +export default { + extensions: [".svelte", ".svx", ".mdx"], + preprocess: mdsvex({ remarkPlugins: [...mdxSourcePlugins] }), +}; +``` + +### Fumadocs + +See the dedicated [Integrate with Fumadocs](/docs/build/integrate-with-fumadocs) page — leadtype ships a first-party `leadtype/fumadocs` adapter. + +### Pattern for any other framework + +If your framework's MDX integration accepts a remark plugin list, leadtype works. Three pieces: + +1. Add `mdxSourcePlugins` to the remark list so `` / `` resolve at build time. +2. Implement components against the [tag types from `leadtype/mdx`](/docs/reference/mdx) — intersect with your framework's child type. +3. Optionally call `createDocsSource()` for navigation, search, and programmatic page loading. + +No leadtype code runs in your client bundle unless you explicitly import a runtime helper. The source primitive is server-side / build-time. diff --git a/packages/leadtype/package.json b/packages/leadtype/package.json index 9147680..c22ed7d 100644 --- a/packages/leadtype/package.json +++ b/packages/leadtype/package.json @@ -25,6 +25,14 @@ "types": "./dist/index.d.ts", "import": "./dist/index.js" }, + "./mdx": { + "types": "./dist/mdx/index.d.ts", + "import": "./dist/mdx/index.js" + }, + "./fumadocs": { + "types": "./dist/fumadocs/index.d.ts", + "import": "./dist/fumadocs/index.js" + }, "./remark": { "types": "./dist/remark/index.d.ts", "import": "./dist/remark/index.js" @@ -111,6 +119,7 @@ "@types/node": "^22.0.0", "ai": "^6.0.177", "bash-tool": "1.3.16", + "fumadocs-core": "^15.0.0", "jiti": "^2.7.0", "just-bash": "2.14.5", "mdast-util-mdx": "3.0.0", @@ -127,6 +136,7 @@ "@tanstack/ai": ">=0.15.0", "ai": ">=6.0.0", "bash-tool": ">=1.3.16", + "fumadocs-core": ">=15.0.0", "jiti": ">=2.0.0", "just-bash": ">=2.14.5", "typescript": ">=5.0.0" @@ -144,6 +154,9 @@ "bash-tool": { "optional": true }, + "fumadocs-core": { + "optional": true + }, "jiti": { "optional": true }, diff --git a/packages/leadtype/rollup.config.ts b/packages/leadtype/rollup.config.ts index aad7d95..75b3873 100644 --- a/packages/leadtype/rollup.config.ts +++ b/packages/leadtype/rollup.config.ts @@ -5,6 +5,8 @@ import esbuild from "rollup-plugin-esbuild"; const entries = { index: "src/index.ts", + "mdx/index": "src/mdx/index.ts", + "fumadocs/index": "src/fumadocs/index.ts", "remark/index": "src/remark/index.ts", "convert/index": "src/convert/index.ts", "llm/index": "src/llm/index.ts", diff --git a/packages/leadtype/src/cli.test.ts b/packages/leadtype/src/cli.test.ts index c558c1c..535dae3 100644 --- a/packages/leadtype/src/cli.test.ts +++ b/packages/leadtype/src/cli.test.ts @@ -176,7 +176,7 @@ describe("leadtype CLI", () => { expect(capture.stderr).toContain("Generated docs pipeline output"); expect(existsSync(path.join(outDir, "docs", "methodology.md"))).toBe(true); expect( - existsSync(path.join(outDir, "docs", "build", "connect-docs-site.md")) + existsSync(path.join(outDir, "docs", "build", "build-a-docs-site.md")) ).toBe(true); expect(existsSync(path.join(outDir, "llms.txt"))).toBe(true); expect(existsSync(path.join(outDir, "llms-full.txt"))).toBe(true); @@ -201,7 +201,7 @@ describe("leadtype CLI", () => { "utf8" ); expect(docsSummary).toContain("Methodology"); - expect(docsSummary).toContain("Connect a docs site"); + expect(docsSummary).toContain("Build a docs site"); expect(docsSummary).toContain("](/docs/methodology.md)"); const llmsFull = await readFile(path.join(outDir, "llms-full.txt"), "utf8"); @@ -809,7 +809,7 @@ Initial release. }; expect(result.filters.include).toEqual(["build/**"]); expect( - existsSync(path.join(outDir, "docs", "build", "connect-docs-site.md")) + existsSync(path.join(outDir, "docs", "build", "build-a-docs-site.md")) ).toBe(true); expect( existsSync(path.join(outDir, "docs", "build", "add-search.md")) @@ -831,7 +831,7 @@ Initial release. "--include", "build/**", "--exclude", - "build/connect-docs-site.mdx", + "build/build-a-docs-site.mdx", ], capture.io ); @@ -841,7 +841,7 @@ Initial release. existsSync(path.join(outDir, "docs", "build", "add-search.md")) ).toBe(true); expect( - existsSync(path.join(outDir, "docs", "build", "connect-docs-site.md")) + existsSync(path.join(outDir, "docs", "build", "build-a-docs-site.md")) ).toBe(false); }); @@ -1040,7 +1040,7 @@ This page is valid, but the output path is not a directory. // .md files should still ship. expect(existsSync(path.join(outDir, "docs", "methodology.md"))).toBe(true); expect( - existsSync(path.join(outDir, "docs", "build", "connect-docs-site.md")) + existsSync(path.join(outDir, "docs", "build", "build-a-docs-site.md")) ).toBe(true); }); diff --git a/packages/leadtype/src/convert/convert.test.ts b/packages/leadtype/src/convert/convert.test.ts index c402fb9..9054f7e 100644 --- a/packages/leadtype/src/convert/convert.test.ts +++ b/packages/leadtype/src/convert/convert.test.ts @@ -3,7 +3,7 @@ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; -import { convertAllMdx } from "./convert"; +import { convertAllMdx, convertMdxFile } from "./convert"; const tempDirs: string[] = []; @@ -52,3 +52,51 @@ describe("convertAllMdx", () => { } }); }); + +describe("convertMdxFile", () => { + it("returns ast, parsed frontmatter data, and serialized markdown", async () => { + const dir = await createTempProject(); + const filePath = path.join(dir, "page.mdx"); + await writeFile( + filePath, + "---\ntitle: Hello\ndescription: from convertMdxFile\n---\n\n# Heading\n\nBody.\n" + ); + + const result = await convertMdxFile(filePath); + + expect(result.ast.type).toBe("root"); + expect(result.data).toMatchObject({ + title: "Hello", + description: "from convertMdxFile", + }); + expect(result.markdown).toContain("# Heading"); + expect(result.markdown).toContain("Body."); + expect(result.frontmatter).toContain("title: Hello"); + }); + + it("synthesizes frontmatter from the body when none is authored", async () => { + const dir = await createTempProject(); + const filePath = path.join(dir, "untitled.mdx"); + await writeFile(filePath, "# Custom Title\n\nIntro paragraph.\n"); + + const result = await convertMdxFile(filePath); + + expect(result.data.title).toBe("Custom Title"); + expect(result.frontmatter).toContain("title:"); + }); + + it("applies the supplied remark plugins before stringification", async () => { + const dir = await createTempProject(); + const filePath = path.join(dir, "page.mdx"); + await writeFile(filePath, "# Hi\n\nBody.\n"); + + let pluginRan = false; + const tracerPlugin = () => () => { + pluginRan = true; + }; + + const result = await convertMdxFile(filePath, [tracerPlugin]); + expect(pluginRan).toBe(true); + expect(result.markdown).toContain("# Hi"); + }); +}); diff --git a/packages/leadtype/src/convert/convert.ts b/packages/leadtype/src/convert/convert.ts index 69c15d9..435d06a 100644 --- a/packages/leadtype/src/convert/convert.ts +++ b/packages/leadtype/src/convert/convert.ts @@ -4,6 +4,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises"; import { cpus } from "node:os"; import { basename, dirname, join, relative, resolve, sep } from "node:path"; import { promisify } from "node:util"; +import type { Root } from "mdast"; import { remark } from "remark"; import remarkGfm from "remark-gfm"; import remarkMdx from "remark-mdx"; @@ -58,8 +59,6 @@ const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/; const HEADING_REGEX = /^#\s+(.+)$/m; const YAML_QUOTE_REGEX = /["\\]/g; const TABLE_DIVIDER_REGEX = /^:?-{2,}:?$/; -const MERMAID_FENCE_REGEX = /```mermaid\r?\n([\s\S]*?)\r?\n```/g; -const HTML_BREAK_REGEX = //gi; const MDX_EXTENSION_REGEX = /\.mdx$/; const TITLE_CASE_REGEX = /\b\w/g; const NAME_SEPARATOR_REGEX = /[-_]+/g; @@ -225,14 +224,12 @@ function compactMarkdownTables(markdown: string): string { } function compactMermaidBlocks(markdown: string): string { - return markdown.replace(MERMAID_FENCE_REGEX, (_block, body: string) => { - const compactBody = body - .split("\n") - .map((line) => line.replace(HTML_BREAK_REGEX, " - ")) - .map((line) => line.replace(/,\s+-\s+/g, " - ")) - .join("\n"); - return `\`\`\`mermaid\n${compactBody}\n\`\`\``; - }); + // The previous implementation replaced `
` with ` - ` inside mermaid + // bodies for "readability", but `
` is mermaid's own syntax for line + // breaks inside node labels — substituting it broke any downstream + // renderer. No transform is currently needed; the function is kept as + // a named call site for future per-line normalization if it's ever needed. + return markdown; } export type MdxToMarkdownOptions = { @@ -331,6 +328,82 @@ export type ConvertResult = { frontmatter: string; }; +export type ConvertMdxFileResult = { + /** mdast Root after every supplied plugin has run. Use this to render MDX live. */ + ast: Root; + /** Resolved frontmatter block (no `---` fences) as it would appear on disk. */ + frontmatter: string; + /** Parsed frontmatter as a plain object. */ + data: Record; + /** Serialized markdown body (post compact-tables/compact-mermaid). */ + markdown: string; +}; + +/** + * Convert a single MDX file in memory and return the post-transform mdast + * AST alongside the parsed frontmatter and serialized markdown body. + * + * Useful when the caller wants to render MDX as live components (so they + * need the AST) but also wants the markdown form available (for search + * indexing, RSS, etc.) in a single pass. + * + * Frontmatter handling matches `convertMdxToMarkdown`: synthesized when + * absent, enriched from git when requested, placeholders resolved. + */ +export async function convertMdxFile( + sourcePath: string, + remarkPlugins: PluggableList = [], + enrichFromGitFlag = false +): Promise { + const raw = await readFile(sourcePath, "utf8"); + const processor = createRemarkProcessor(remarkPlugins); + const frontmatterMatch = raw.match(FRONTMATTER_REGEX); + let frontmatter = ""; + let content = raw; + + if (frontmatterMatch) { + frontmatter = frontmatterMatch[1] ?? ""; + content = frontmatterMatch[2] ?? ""; + } + + // Parse → run plugins → stringify, so we can keep the AST after transforms. + const parsed = processor.parse({ value: content, path: sourcePath }) as Root; + const transformed = (await processor.run(parsed, { + value: content, + path: sourcePath, + })) as Root; + const markdown = compactMermaidBlocks( + compactMarkdownTables(String(processor.stringify(transformed))) + ); + + let resolvedFrontmatter = + frontmatter.trim().length > 0 + ? frontmatter + : synthesizeFrontmatter(sourcePath, markdown); + + if (enrichFromGitFlag) { + const enrichment = await enrichFromGit(sourcePath); + resolvedFrontmatter = applyEnrichment(resolvedFrontmatter, enrichment); + } + + resolvedFrontmatter = resolveFrontmatterPlaceholders( + resolvedFrontmatter, + sourcePath + ); + + const parsedData = + resolvedFrontmatter.trim().length > 0 + ? parseFrontmatter(`---\n${resolvedFrontmatter}\n---\n`).data + : {}; + + return { + ast: transformed, + frontmatter: resolvedFrontmatter, + data: parsedData, + markdown, + }; +} + /** * Convert a single MDX file to markdown in memory. Returns the rendered * markdown plus the (possibly synthesized) frontmatter block. diff --git a/packages/leadtype/src/convert/index.ts b/packages/leadtype/src/convert/index.ts index 1dc29dc..c60abe9 100644 --- a/packages/leadtype/src/convert/index.ts +++ b/packages/leadtype/src/convert/index.ts @@ -1,6 +1,8 @@ export { + type ConvertMdxFileResult, type ConvertResult, convertAllMdx, + convertMdxFile, convertMdxToMarkdown, type MdxToMarkdownOptions, writeMdxFileAsMarkdown, diff --git a/packages/leadtype/src/fumadocs/index.ts b/packages/leadtype/src/fumadocs/index.ts new file mode 100644 index 0000000..ae1872e --- /dev/null +++ b/packages/leadtype/src/fumadocs/index.ts @@ -0,0 +1,164 @@ +/** + * `leadtype/fumadocs` — thin adapter mapping `createDocsSource()` output to + * fumadocs-core's `Source` interface. + * + * Requires `fumadocs-core >= 15.0.0` as an optional peer dependency. Install + * it alongside `leadtype` in your docs app: + * + * ```sh + * bun add fumadocs-core leadtype + * ``` + * + * Then wire the source into fumadocs's `loader()`: + * + * ```ts + * import { loader } from "fumadocs-core/source"; + * import { fumadocsSource } from "leadtype/fumadocs"; + * + * export const source = loader({ + * baseUrl: "/docs", + * source: await fumadocsSource({ contentDir: "./content/docs" }), + * }); + * ``` + * + * The adapter pre-walks `contentDir` at construction time so fumadocs sees a + * synchronous list of files. Page bodies are loaded on demand via the + * companion `loadPage()` helper, which you can call from a server component. + */ + +import { readFile } from "node:fs/promises"; +import path from "node:path"; +import type { Source } from "fumadocs-core/source"; +import { glob as fg } from "tinyglobby"; +import { + type CreateDocsSourceConfig, + createDocsSource, + type DocsPage, + type DocsSource, +} from "../source"; + +/** + * Page metadata fumadocs receives from this adapter. Extends fumadocs's + * default `PageData` (icon/title/description) with leadtype's group slugs so + * consumer sidebars can filter / group pages. + */ +export type LeadtypeFumadocsPageData = { + title: string; + description?: string; + groups: string[]; +}; + +/** + * Meta entries fumadocs builds its page tree from. Mirrors fumadocs's default + * `MetaData` so consumers can author `meta.json` files normally. + */ +export type LeadtypeFumadocsMetaData = { + icon?: string; + title?: string; + root?: boolean; + pages?: string[]; + defaultOpen?: boolean; + description?: string; +}; + +export type LeadtypeFumadocsSourceConfig = { + pageData: LeadtypeFumadocsPageData; + metaData: LeadtypeFumadocsMetaData; +}; + +export type LeadtypeFumadocsSource = Source & { + /** The underlying `DocsSource` — call `loadPage`, `buildSearchIndex`, etc. */ + leadtype: DocsSource; + /** Convenience: resolve a fumadocs page → leadtype `DocsPage`. */ + loadPage(slug: string | string[]): Promise; +}; + +/** + * Build a fumadocs-compatible Source from a leadtype docs directory. + * + * Walks both `.md`/`.mdx` pages **and** `meta.json` files under `contentDir`, + * yielding fumadocs the same nav tree it would build from a colocated + * fumadocs-mdx source. Set `includeMetaJson: false` to skip the meta walk if + * you'd rather have fumadocs auto-build the tree from page slugs. + * + * @example + * const source = await fumadocsSource({ contentDir: "./content/docs" }); + * const loader = loader({ baseUrl: "/docs", source }); + */ +export async function fumadocsSource( + config: CreateDocsSourceConfig & { includeMetaJson?: boolean } +): Promise { + const leadtype = await createDocsSource(config); + const metas = await leadtype.listPages(); + + const pageFiles = metas.map((meta) => ({ + type: "page" as const, + path: `${meta.relativePath}${meta.extension}`, + absolutePath: meta.filePath, + slugs: meta.slug, + data: { + title: meta.title, + description: meta.description || undefined, + groups: meta.groups, + } satisfies LeadtypeFumadocsPageData, + })); + + const metaFiles = + config.includeMetaJson === false + ? [] + : await readMetaFiles(leadtype.contentDir); + + return { + files: [...pageFiles, ...metaFiles], + leadtype, + loadPage: leadtype.loadPage, + }; +} + +async function readMetaFiles(contentDir: string): Promise< + Array<{ + type: "meta"; + path: string; + absolutePath: string; + data: LeadtypeFumadocsMetaData; + }> +> { + const matches = await fg("**/meta.json", { + absolute: true, + cwd: contentDir, + onlyFiles: true, + }); + return await Promise.all( + matches.map(async (filePath) => { + const relativePath = path + .relative(contentDir, filePath) + .replaceAll(path.sep, "/"); + const raw = await readFile(filePath, "utf8"); + let data: LeadtypeFumadocsMetaData = {}; + try { + const parsed = JSON.parse(raw) as unknown; + // Only accept plain objects. Arrays, strings, numbers, null, etc. + // are treated the same as malformed JSON — fumadocs expects an + // object-shaped meta record, and passing it through would break + // downstream consumers. + if ( + parsed !== null && + typeof parsed === "object" && + !Array.isArray(parsed) + ) { + data = parsed as LeadtypeFumadocsMetaData; + } + } catch { + // Malformed meta.json: keep the entry so fumadocs can surface a + // helpful warning during page-tree building instead of silently + // ignoring it. + } + return { + type: "meta" as const, + path: relativePath, + absolutePath: filePath, + data, + }; + }) + ); +} diff --git a/packages/leadtype/src/index.ts b/packages/leadtype/src/index.ts index 8aa67e4..6df59e5 100644 --- a/packages/leadtype/src/index.ts +++ b/packages/leadtype/src/index.ts @@ -1,7 +1,13 @@ -// Root entry for `leadtype`. Exposes config helpers used by docs sites, -// agent tooling, and the LLM-bundle pipeline. Specialized surfaces stay on -// dedicated subpaths (`leadtype/convert`, `/llm`, `/search`, `/lint`). -// TOC extraction APIs and slug helpers live on `leadtype/llm` only. +// Root entry for `leadtype`. Exposes the docs source primitive and the +// config helpers used across docs sites, agent tooling, and the LLM-bundle +// pipeline. Specialized surfaces stay on dedicated subpaths: +// - `leadtype/mdx` — tag types, source remark preset, include resolver +// - `leadtype/fumadocs` — adapter for fumadocs-core's Source interface +// - `leadtype/remark` — agent/LLM flattening plugins +// - `leadtype/convert` — MDX → markdown helpers +// - `leadtype/llm` — TOC extraction, slug helpers, agent readability +// - `leadtype/search` — search index + per-host adapters +// - `leadtype/lint` — frontmatter / meta.json validation export { type AgentReadabilityConfig, type AgentReadabilityManifest, @@ -13,3 +19,11 @@ export { defineDocsConfig, type ProductInfo, } from "./llm"; + +export { + type CreateDocsSourceConfig, + createDocsSource, + type DocsPage, + type DocsPageMeta, + type DocsSource, +} from "./source"; diff --git a/packages/leadtype/src/internal/package-surface.test.ts b/packages/leadtype/src/internal/package-surface.test.ts index 11d9d16..d43390d 100644 --- a/packages/leadtype/src/internal/package-surface.test.ts +++ b/packages/leadtype/src/internal/package-surface.test.ts @@ -8,6 +8,8 @@ describe("package surface", () => { it("matches the documented entry-point list", () => { const expectedExportedPaths = [ ".", + "./mdx", + "./fumadocs", "./remark", "./convert", "./llm", diff --git a/packages/leadtype/src/lint/schema.ts b/packages/leadtype/src/lint/schema.ts index 3f22dae..3381ded 100644 --- a/packages/leadtype/src/lint/schema.ts +++ b/packages/leadtype/src/lint/schema.ts @@ -54,6 +54,15 @@ export const defaultFrontmatterSchema = v.object({ tags: v.optional(v.array(v.string())), group: v.optional(v.union([v.string(), v.array(v.string())])), availableIn: v.optional(v.array(availableInEntry)), + /** + * Sidebar ordering within a group. Lower numbers come first. Pages + * without `order` sort alphabetically by URL path **after** explicitly + * ordered pages, so you can pin a few key pages and leave the rest as + * default. Conventionally numbered in tens (10, 20, 30) to leave room + * for insertions. Must be an integer — fractional orders are rejected + * by lint. + */ + order: v.optional(v.pipe(v.number(), v.integer())), // Layout full: v.optional(v.boolean()), diff --git a/packages/leadtype/src/llm/llm.ts b/packages/leadtype/src/llm/llm.ts index b44d753..f03a10e 100644 --- a/packages/leadtype/src/llm/llm.ts +++ b/packages/leadtype/src/llm/llm.ts @@ -73,6 +73,12 @@ export type SourceDoc = { relativePath: string; /** Group slugs declared in frontmatter `group:`. Empty array = ungrouped. */ groups: string[]; + /** + * Sidebar order within a group, parsed from frontmatter `order:`. Pages + * with an explicit order sort first (ascending). Pages without `order` + * fall back to alphabetical urlPath ordering. + */ + order?: number; }; type SourceDocWithContent = SourceDoc & { @@ -186,6 +192,13 @@ export type ResolveDocsNavigationConfig = { groups: DocsGroup[]; mounts?: DocsPathMount[]; toc?: boolean | DocsTableOfContentsOptions; + /** + * Name of the docs subdirectory under `srcDir`. Defaults to `"docs"` for + * backward compatibility. Set this when the docs folder isn't named `docs` + * (e.g. fumadocs sites using `content/docs` — the directory containing the + * `.mdx` files would be `srcDir/content`'s `docs` child). + */ + docsDirName?: string; }; export type ResolveDocsTableOfContentsConfig = { @@ -527,9 +540,10 @@ async function collectFiles( async function readSourceDocs( srcDir: string, baseUrl: string, - mounts?: DocsPathMount[] + mounts?: DocsPathMount[], + docsDirName: string = DOCS_DIRNAME ): Promise> { - const docsDir = path.join(srcDir, DOCS_DIRNAME); + const docsDir = path.join(srcDir, docsDirName); const docs = new Map(); if (!existsSync(docsDir)) { @@ -555,6 +569,11 @@ async function readSourceDocs( ); const urlPath = toUrlPath(relativePath, mounts); const groups = normalizeGroupValue(parsed.data.group); + const orderRaw = parsed.data.order; + const order = + typeof orderRaw === "number" && Number.isFinite(orderRaw) + ? orderRaw + : undefined; return { urlPath, doc: { @@ -564,6 +583,7 @@ async function readSourceDocs( absoluteUrl: toAbsoluteUrl(urlPath, baseUrl), relativePath: stripDocsExtension(relativePath), groups, + ...(order === undefined ? {} : { order }), content: parsed.content, }, }; @@ -647,12 +667,20 @@ function buildGroupMembership( const ungrouped: SourceDoc[] = []; const unknown: { page: SourceDoc; slug: string }[] = []; - // Stable page order: by urlPath. Inputs are already iteration-order-stable - // when they come from a Map in insertion order, but explicit sort makes - // the rendered llms.txt deterministic regardless of source. - const ordered = [...pages].sort((left, right) => - left.urlPath.localeCompare(right.urlPath) - ); + // Page order within a group: + // 1. Pages with an explicit `order:` field sort first, ascending. + // 2. Pages without `order` sort alphabetically by urlPath as a tiebreaker. + // This lets authors pin a few key pages with `order: 10, 20, 30, …` and + // leave the rest at the default. Sorting is stable and deterministic so + // the rendered llms.txt, sidebar, and AGENTS.md match across runs. + const ordered = [...pages].sort((left, right) => { + const leftOrder = left.order ?? Number.POSITIVE_INFINITY; + const rightOrder = right.order ?? Number.POSITIVE_INFINITY; + if (leftOrder !== rightOrder) { + return leftOrder - rightOrder; + } + return left.urlPath.localeCompare(right.urlPath); + }); for (const page of ordered) { if (page.groups.length === 0) { @@ -1173,7 +1201,12 @@ export async function resolveDocsNavigation( ): Promise { const srcDir = path.resolve(config.srcDir); const baseUrl = normalizeBaseUrl(config.baseUrl); - const sourceDocs = await readSourceDocs(srcDir, baseUrl, config.mounts); + const sourceDocs = await readSourceDocs( + srcDir, + baseUrl, + config.mounts, + config.docsDirName + ); const resolved = resolveGroups(config.groups); const membership = buildGroupMembership([...sourceDocs.values()], resolved); const tocOptions = resolveNavigationTocOptions(config.toc); diff --git a/packages/leadtype/src/mdx/index.ts b/packages/leadtype/src/mdx/index.ts new file mode 100644 index 0000000..08012bc --- /dev/null +++ b/packages/leadtype/src/mdx/index.ts @@ -0,0 +1,78 @@ +/** @biome-ignore lint/performance/noBarrelFile: dedicated package subpath */ + +/** + * `leadtype/mdx` — consumer-facing MDX surface. + * + * Exports: + * - **Tag type contracts** for every custom MDX tag (`CalloutProps`, + * `TabsProps`, `TypeTableProps`, …). Implement components against these + * in your renderer. + * - **`mdxSourcePlugins`** — remark preset for compiling source MDX in a + * host bundler (Next, Vite, fumadocs, …). Expands includes, resolves + * ``, strips authoring `import`s; preserves every + * other custom tag as JSX. + * - **`resolveInclude` / `parseIncludeSpecifier` / `extractMdxSection`** — + * low-level include-resolution helpers, framework-neutral. + * + * For the markdown-flattening pipeline used by the LLM/agent outputs, see + * `leadtype/remark` instead. + */ + +// Canonical path / URL primitives so source consumers can derive slugs +// without reaching into `internal/`. +export { + type DocsPathMount, + normalizeBaseUrl, + normalizeDocsPath, + normalizeUrlPrefix, + stripDocsExtension, + toDocsUrlPath, +} from "../internal/docs-url"; +// Include-resolution primitives (re-exports from the remark plugin file) +export { + extractMdxSection, + type IncludeResolution, + parseIncludeSpecifier, + type ResolveIncludeOptions, + type ResolveIncludePathOptions, + resolveInclude, + resolveIncludePath, +} from "../remark/plugins/include.remark"; +// Source preset for bundler consumers +export { mdxSourcePlugins } from "./source-preset"; +// Tag type contracts +export type { + AccordionItemProps, + AccordionProps, + AudienceProps, + AudienceTarget, + CalloutProps, + CalloutTypeAlias, + CalloutVariant, + CardProps, + CardsProps, + CardVariant, + CommandMode, + CommandTabsModeProps, + CommandTabsProps, + CommandTabsTemplateProps, + DetailsProps, + ExampleProps, + ExampleSourceFile, + ExtractedTypeTableProps, + FileProps, + FileTreeProps, + FolderProps, + MermaidProps, + PackageManager, + PromptProps, + SectionProps, + StepProps, + StepsProps, + TabProps, + TabsProps, + TopicSwitcherItem, + TopicSwitcherProps, + TypeTableProperty, + TypeTableProps, +} from "./tag-types"; diff --git a/packages/leadtype/src/mdx/source-preset.ts b/packages/leadtype/src/mdx/source-preset.ts new file mode 100644 index 0000000..97f9699 --- /dev/null +++ b/packages/leadtype/src/mdx/source-preset.ts @@ -0,0 +1,35 @@ +/** + * Remark preset for **source-MDX consumers** — bundlers and frameworks that + * compile the original `.mdx` files into live components (Next App Router, + * Vite + @mdx-js, fumadocs, etc.). + * + * This preset performs **build-time resolution only**: + * 1. `` / `` partials are expanded. + * 2. `` is resolved to ``. + * 3. Placeholder strings inside frontmatter / content are resolved. + * 4. Authoring-only `import` statements are stripped. + * + * It deliberately leaves every other custom tag (``, ``, + * ``, ``, ``, …) as JSX so the consumer's runtime + * components render them. For the flattened-markdown agent pipeline, use + * `defaultRemarkPlugins` from `leadtype/remark` instead. + */ + +import type { PluggableList } from "unified"; +import { remarkResolveDocPlaceholders } from "../remark/plugins/doc-placeholders.remark"; +import { remarkInclude } from "../remark/plugins/include.remark"; +import { remarkRemoveImports } from "../remark/plugins/remove-imports.remark"; +import { remarkResolveTypeTableJsx } from "../remark/plugins/type-table-jsx.remark"; + +/** + * Default remark plugin list for compiling source MDX in a host bundler. + * Order matters: includes expand first (so type-table / placeholder passes + * see merged content), then type-table extraction, then placeholder resolution, + * then import stripping. + */ +export const mdxSourcePlugins: PluggableList = [ + remarkInclude, + remarkResolveTypeTableJsx, + remarkResolveDocPlaceholders, + remarkRemoveImports, +]; diff --git a/packages/leadtype/src/mdx/tag-types.ts b/packages/leadtype/src/mdx/tag-types.ts new file mode 100644 index 0000000..d7f747f --- /dev/null +++ b/packages/leadtype/src/mdx/tag-types.ts @@ -0,0 +1,316 @@ +/** + * Type contracts for leadtype's custom MDX tags. + * + * These describe the **author surface** — the attributes that appear on a tag + * in source MDX (e.g. ``). They are + * framework-neutral on purpose: `children` is typed as `unknown` so consumers + * can intersect with their renderer's specific child type. + * + * React consumers typically intersect with React's HTML attribute types, e.g. + * + * ```ts + * import type { CalloutProps } from "leadtype/mdx"; + * import type { HTMLAttributes, ReactNode } from "react"; + * + * type ReactCalloutProps = Omit & + * HTMLAttributes & { children?: ReactNode }; + * ``` + * + * Every tag type is part of the 1.0 contract — bumping the prop shape is a + * breaking change. + */ + +// --------------------------------------------------------------------------- +// Callout +// --------------------------------------------------------------------------- + +/** Stable variants accepted by the `variant` attribute on ``. */ +export type CalloutVariant = + | "info" + | "note" + | "tip" + | "warning" + | "success" + | "error" + | "canary" + | "deprecated" + | "experimental"; + +/** + * Legacy `type=` alias for `variant=`. Accepts the same values plus `"warn"` + * (which normalizes to `"warning"`). New authoring should prefer `variant=`. + */ +export type CalloutTypeAlias = CalloutVariant | "warn"; + +export type CalloutProps = { + variant?: CalloutVariant; + /** @deprecated use {@link CalloutProps.variant} */ + type?: CalloutTypeAlias; + title?: string; + children?: unknown; +}; + +// --------------------------------------------------------------------------- +// Tabs +// --------------------------------------------------------------------------- + +export type TabsProps = { + /** Optional explicit list of tab labels; falls back to `` ordering. */ + items?: string[]; + /** Zero-based index of the tab shown on first render. */ + defaultIndex?: number; + /** Shared id used to sync active tab across multiple `` instances. */ + groupId?: string; + children?: unknown; +}; + +export type TabProps = { + /** Identifier matched against the parent `` list. */ + value: string; + children?: unknown; +}; + +// --------------------------------------------------------------------------- +// Steps +// --------------------------------------------------------------------------- + +export type StepsProps = { + children?: unknown; +}; + +export type StepProps = { + /** Optional heading rendered above the step body. */ + title?: string; + children?: unknown; +}; + +// --------------------------------------------------------------------------- +// TypeTable (runtime + extractor variant) +// --------------------------------------------------------------------------- + +export type TypeTableProperty = { + type: string; + description?: string; + typeDescription?: string; + typeDescriptionLink?: string; + default?: string; + required?: boolean; + deprecated?: boolean; +}; + +export type TypeTableProps = { + /** + * Map of property name → property metadata. When using + * `` or ``, leadtype's source preset + * replaces the node with `` and fills this from the parsed + * TypeScript source. If extraction failed (file not found, `typescript` + * not installed, wrong `basePath`), this is `{}` and `name`/`path` are + * still passed through so the runtime component can render a placeholder. + */ + properties: Record; + title?: string; + description?: string; + /** + * Original `name="…"` from ``. Set whether extraction + * succeeded or not — useful for placeholder UI when `properties` is empty. + */ + name?: string; + /** Original `path="…"` from ``. */ + path?: string; +}; + +/** + * Authoring-side shortcut. Replaced at build time by the source preset + * with `` carrying the extracted properties. + * Consumers do not normally implement a runtime component for this. + */ +export type ExtractedTypeTableProps = { + /** Exported TypeScript identifier to extract from `path`. */ + name: string; + /** Path to the TypeScript file containing `name`. */ + path: string; + /** Override the extractor's base directory for `path` resolution. */ + basePath?: string; + title?: string; + description?: string; +}; + +// --------------------------------------------------------------------------- +// Mermaid +// --------------------------------------------------------------------------- + +export type MermaidProps = { + /** Mermaid source. Falls back to `children` when omitted. */ + chart?: string; + children?: unknown; +}; + +// --------------------------------------------------------------------------- +// Accordion +// --------------------------------------------------------------------------- + +export type AccordionProps = { + children?: unknown; +}; + +export type AccordionItemProps = { + title: string; + defaultOpen?: boolean; + children?: unknown; +}; + +// --------------------------------------------------------------------------- +// Cards / Card +// --------------------------------------------------------------------------- + +export type CardsProps = { + children?: unknown; +}; + +/** Built-in card layouts. Arbitrary strings still type-check for forward compat. */ +export type CardVariant = "default" | "interactive" | (string & {}); + +export type CardProps = { + href: string; + title?: string; + description?: string; + /** Renderer decides how to display this; usually a small inline node. */ + icon?: unknown; + variant?: CardVariant; + children?: unknown; +}; + +// --------------------------------------------------------------------------- +// FileTree +// --------------------------------------------------------------------------- + +export type FileTreeProps = { + /** Label for the implicit root folder. */ + root?: string; + children?: unknown; +}; + +export type FolderProps = { + name: string; + /** Render the folder open by default. */ + defaultOpen?: boolean; + children?: unknown; +}; + +export type FileProps = { + name: string; +}; + +// --------------------------------------------------------------------------- +// Audience +// --------------------------------------------------------------------------- + +export type AudienceTarget = "agent" | "human"; + +export type AudienceProps = { + target: AudienceTarget; + children?: unknown; +}; + +// --------------------------------------------------------------------------- +// CommandTabs +// --------------------------------------------------------------------------- + +export type PackageManager = "npm" | "pnpm" | "yarn" | "bun"; + +export type CommandMode = "run" | "install" | "create"; + +type CommandTabsCommon = { + /** Preferred manager on first render. */ + defaultManager?: PackageManager; +}; + +/** Mode form: ``. */ +export type CommandTabsModeProps = CommandTabsCommon & { + command: string; + mode?: CommandMode; + commands?: never; +}; + +/** Template form: ``. */ +export type CommandTabsTemplateProps = CommandTabsCommon & { + commands: Partial>; + command?: never; + mode?: never; +}; + +export type CommandTabsProps = CommandTabsModeProps | CommandTabsTemplateProps; + +// --------------------------------------------------------------------------- +// TopicSwitcher +// --------------------------------------------------------------------------- + +export type TopicSwitcherItem = { + /** Stable identifier used by `activeValue`. */ + value: string; + /** Display label. Falls back to `value` when omitted. */ + label?: string; + description?: string; + href?: string; + /** Treat this item as the active topic regardless of `activeValue`. */ + current?: boolean; +}; + +export type TopicSwitcherProps = { + items: TopicSwitcherItem[]; + /** Section label rendered above the switcher. */ + label?: string; + /** Stable id of the currently active topic. */ + activeValue?: string; +}; + +// --------------------------------------------------------------------------- +// Prompt +// --------------------------------------------------------------------------- + +export type PromptProps = { + title?: string; + description?: string; + children?: unknown; +}; + +// --------------------------------------------------------------------------- +// Example +// --------------------------------------------------------------------------- + +export type ExampleSourceFile = { + filename: string; + language?: string; + code: string; +}; + +export type ExampleProps = { + title?: string; + description?: string; + filename?: string; + language?: string; + /** Primary code block when only one file is shown. */ + code?: string; + /** Multi-file tabbed view; takes precedence over single-file `code`. */ + sourceFiles?: ExampleSourceFile[]; + children?: unknown; +}; + +// --------------------------------------------------------------------------- +// Section (semantic wrapper; usually stripped before render) +// --------------------------------------------------------------------------- + +export type SectionProps = { + /** Anchor id consumed by ``. */ + id: string; + children?: unknown; +}; + +// --------------------------------------------------------------------------- +// Details +// --------------------------------------------------------------------------- + +export type DetailsProps = { + /** First `` child becomes the disclosure label. */ + children?: unknown; +}; diff --git a/packages/leadtype/src/remark/plugins/include.remark.test.ts b/packages/leadtype/src/remark/plugins/include.remark.test.ts new file mode 100644 index 0000000..e78bc60 --- /dev/null +++ b/packages/leadtype/src/remark/plugins/include.remark.test.ts @@ -0,0 +1,176 @@ +import { mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { + extractMdxSection, + parseIncludeSpecifier, + resolveInclude, + resolveIncludePath, +} from "./include.remark"; + +describe("parseIncludeSpecifier", () => { + it("returns just the file when there is no #section", () => { + expect(parseIncludeSpecifier("./shared/setup.mdx")).toEqual({ + file: "./shared/setup.mdx", + }); + }); + + it("splits the specifier on the last # for section anchors", () => { + expect(parseIncludeSpecifier("./shared/setup.mdx#install")).toEqual({ + file: "./shared/setup.mdx", + section: "install", + }); + }); + + it("treats only the LAST # as the section delimiter", () => { + expect(parseIncludeSpecifier("./shared/#weird/setup.mdx#install")).toEqual({ + file: "./shared/#weird/setup.mdx", + section: "install", + }); + }); +}); + +describe("resolveIncludePath", () => { + let root: string; + + beforeEach(async () => { + root = await mkdtemp(path.join(tmpdir(), "leadtype-include-path-")); + await writeFile(path.join(root, "partial.mdx"), "body\n"); + }); + + afterEach(async () => { + await rm(root, { force: true, recursive: true }); + }); + + it("prefers a baseDir override over fromDir", () => { + const resolved = resolveIncludePath("partial.mdx", { + fromDir: "/does/not/exist", + baseDir: root, + }); + expect(resolved).toBe(path.join(root, "partial.mdx")); + }); + + it("resolves relative to fromDir when the file exists there", () => { + const resolved = resolveIncludePath("partial.mdx", { fromDir: root }); + expect(resolved).toBe(path.join(root, "partial.mdx")); + }); + + it("falls back to basePaths when fromDir misses", () => { + const resolved = resolveIncludePath("partial.mdx", { + fromDir: "/does/not/exist", + basePaths: [root], + }); + expect(resolved).toBe(path.join(root, "partial.mdx")); + }); +}); + +describe("resolveInclude", () => { + let root: string; + + beforeEach(async () => { + root = await mkdtemp(path.join(tmpdir(), "leadtype-resolve-include-")); + }); + + afterEach(async () => { + await rm(root, { force: true, recursive: true }); + }); + + it("returns markdown content for .mdx files and strips frontmatter", async () => { + const filePath = path.join(root, "partial.mdx"); + await writeFile(filePath, "---\ntitle: x\n---\nHello\n"); + + const result = await resolveInclude("partial.mdx", { fromDir: root }); + + expect(result).toMatchObject({ + kind: "markdown", + content: "Hello\n", + resolvedPath: filePath, + }); + expect(result.kind === "markdown" && result.section).toBeUndefined(); + }); + + it("carries through the section anchor parsed from the specifier", async () => { + const filePath = path.join(root, "partial.mdx"); + await writeFile(filePath, "body\n"); + + const result = await resolveInclude("partial.mdx#install", { + fromDir: root, + }); + + expect(result).toEqual({ + kind: "markdown", + content: "body\n", + resolvedPath: filePath, + section: "install", + }); + }); + + it("classifies non-markdown files as code blocks", async () => { + const filePath = path.join(root, "snippet.ts"); + await writeFile(filePath, "export const x = 1;\n"); + + const result = await resolveInclude("snippet.ts", { fromDir: root }); + + expect(result).toEqual({ + kind: "code", + content: "export const x = 1;\n", + lang: "ts", + resolvedPath: filePath, + }); + }); + + it("forces code output when lang is set even for .md files", async () => { + const filePath = path.join(root, "doc.md"); + await writeFile(filePath, "# heading\n"); + + const result = await resolveInclude("doc.md", { + fromDir: root, + lang: "markdown", + }); + + expect(result).toMatchObject({ + kind: "code", + lang: "markdown", + content: "# heading\n", + }); + }); + + it("throws when the target file does not exist", async () => { + await expect( + resolveInclude("missing.mdx", { fromDir: root }) + ).rejects.toThrow(/ENOENT|no such file/i); + }); +}); + +describe("extractMdxSection", () => { + it("returns null when no section matches", () => { + expect( + extractMdxSection({ type: "root", children: [] }, "anything") + ).toBeNull(); + }); + + it("returns the children of a matching
mdxJsxFlowElement", () => { + const heading = { + type: "heading", + depth: 2, + children: [{ type: "text", value: "Install" }], + } as const; + const root = { + type: "root" as const, + children: [ + { + type: "mdxJsxFlowElement", + name: "section", + attributes: [ + { type: "mdxJsxAttribute", name: "id", value: "install" }, + ], + children: [heading], + }, + ], + } as unknown as Parameters[0]; + + const extracted = extractMdxSection(root, "install"); + expect(extracted?.children).toEqual([heading]); + }); +}); diff --git a/packages/leadtype/src/remark/plugins/include.remark.ts b/packages/leadtype/src/remark/plugins/include.remark.ts index a7a057b..59459bd 100644 --- a/packages/leadtype/src/remark/plugins/include.remark.ts +++ b/packages/leadtype/src/remark/plugins/include.remark.ts @@ -1,6 +1,14 @@ /** - * Remark plugin to handle include/import MDX elements. - * This replaces the circular re-export with an actual implementation. + * Remark plugin + standalone resolver for include/import MDX elements. + * + * Two public surfaces: + * - `remarkInclude(basePaths?)` — unified plugin that expands `` / + * `` tags in an mdast tree (consumed by the agent flattening + * pipeline and by the MDX-source preset). + * - `resolveInclude(specifier, options)` — framework-neutral resolver that + * reads + classifies the target file. Consumers calling `createDocsSource()` + * use this to load partials at request/build time without going through + * remark. */ import { existsSync } from "node:fs"; @@ -21,7 +29,7 @@ const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/; const sharedProcessor = remark().use(remarkMdx).use(remarkGfm); // Simple frontmatter parser for our build pipeline -function parseFrontmatter(content: string): { content: string } { +function stripFrontmatterBlock(content: string): { content: string } { const match = content.match(FRONTMATTER_REGEX); if (!match || match[2] === undefined) { @@ -48,7 +56,14 @@ function flattenNode(node: Record): string { return ""; } -function parseSpecifier(specifier: string): { +/** + * Split an include specifier into the file path and optional section anchor. + * + * @example + * parseIncludeSpecifier("./shared/setup.mdx#install") + * // → { file: "./shared/setup.mdx", section: "install" } + */ +export function parseIncludeSpecifier(specifier: string): { file: string; section?: string; } { @@ -63,8 +78,13 @@ function parseSpecifier(specifier: string): { }; } -// Extract a specific
from a parsed MDX root -function extractSection(root: Root, sectionId: string): Root | null { +/** + * Extract a `
` subtree from a parsed mdast Root. Returns + * a Root whose children are the section's children, or null if no matching + * section exists. Used by `resolveInclude` consumers that want to slice + * an included document down to one anchor. + */ +export function extractMdxSection(root: Root, sectionId: string): Root | null { for (const child of root.children as unknown as Record[]) { const type = child.type as string | undefined; const name = (child as Record).name as string | undefined; @@ -273,7 +293,7 @@ function includeContentAsMarkdown( let parsed = chosenParser.parse(bodyContent.trim()) as Root; if (options.section) { - const extracted = extractSection(parsed, options.section); + const extracted = extractMdxSection(parsed, options.section); if (extracted) { parsed = extracted; } else { @@ -299,30 +319,46 @@ function includeContentAsMarkdown( } } -// Resolve file path with custom base paths -function resolveIncludePath( +export type ResolveIncludePathOptions = { + /** Directory of the document containing the include reference. */ + fromDir: string; + /** Fallback base directories searched if the file isn't found relative to fromDir. */ + basePaths?: string[]; + /** Explicit override that pins resolution to this directory (from `baseDir=`). */ + baseDir?: string; + /** When true, resolve relative to `process.cwd()` (from the `cwd` attribute). */ + cwd?: boolean; +}; + +/** + * Resolve an include's file specifier to an absolute path. + * + * Resolution order: + * 1. `baseDir` override, if provided + * 2. `cwd` flag → resolve from `process.cwd()` + * 3. relative to `fromDir` + * 4. each entry in `basePaths` + * 5. fall back to first `basePaths` entry, else `fromDir` + */ +export function resolveIncludePath( file: string, - directory: string, - params: Record, - basePaths: string[] + options: ResolveIncludePathOptions ): string { - const baseDir = params.baseDir; + const { fromDir, basePaths = [], baseDir, cwd } = options; + if (baseDir) { return resolve(baseDir, file); } - // If 'cwd' attribute is set, use process.cwd() - if ("cwd" in params) { + if (cwd) { return resolve(process.cwd(), file); } - // Try relative to current directory first - const targetPath = resolve(directory, file); + const targetPath = resolve(fromDir, file); if (existsSync(targetPath)) { return targetPath; } - // Try provided base directories only (no heuristics) for (const basePath of basePaths) { const candidate = resolve(basePath, file); if (existsSync(candidate)) { @@ -330,12 +366,86 @@ function resolveIncludePath( } } - // Fall back to first base path if available, otherwise directory if (basePaths.length > 0 && basePaths[0]) { return resolve(basePaths[0], file); } - return resolve(directory, file); + return resolve(fromDir, file); +} + +export type ResolveIncludeOptions = { + /** Directory of the document containing the include reference. */ + fromDir: string; + /** Fallback base directories searched if the specifier isn't found relative to fromDir. */ + basePaths?: string[]; + /** Explicit override that pins resolution to this directory (from `baseDir=`). */ + baseDir?: string; + /** When true, resolve relative to `process.cwd()` (from the `cwd` attribute). */ + cwd?: boolean; + /** Force code-block rendering with this language even for .md/.mdx files. */ + lang?: string; +}; + +export type IncludeResolution = + | { + kind: "markdown"; + /** File body with any leading frontmatter block removed. */ + content: string; + /** Absolute path of the resolved file. */ + resolvedPath: string; + /** Section anchor parsed from the specifier (`#anchor`), if any. */ + section?: string; + } + | { + kind: "code"; + content: string; + lang: string; + resolvedPath: string; + }; + +/** + * Read the file referenced by an include specifier and classify it as either + * `markdown` (parse and splice as AST) or `code` (render as a code fence). + * Pure content resolution — does not touch any mdast. + * + * Consumers calling `createDocsSource()` use this directly. The `remarkInclude` + * plugin wraps this with AST mutation logic. + * + * @throws if the resolved file cannot be read. + */ +export async function resolveInclude( + specifier: string, + options: ResolveIncludeOptions +): Promise { + const { file, section } = parseIncludeSpecifier(specifier); + const resolvedPath = resolveIncludePath(file, { + fromDir: options.fromDir, + basePaths: options.basePaths, + baseDir: options.baseDir, + cwd: options.cwd, + }); + + const isMarkdownFile = file.endsWith(".md") || file.endsWith(".mdx"); + const asCode = Boolean(options.lang) || !isMarkdownFile; + + const raw = await readFile(resolvedPath, "utf8"); + + if (asCode) { + return { + kind: "code", + content: raw, + lang: options.lang ?? extname(file).slice(1), + resolvedPath, + }; + } + + const { content } = stripFrontmatterBlock(raw); + return { + kind: "markdown", + content, + resolvedPath, + ...(section ? { section } : {}), + }; } // Check if node is an include node @@ -352,7 +462,7 @@ function isIncludeNode( ); } -// Process a single include node +// Process a single include node — thin AST adapter around resolveInclude. async function processIncludeNode( node: Record, workingDir: string, @@ -379,73 +489,29 @@ async function processIncludeNode( return; } - const { file: includeFile, section } = parseSpecifier(specifier); - - const targetPath = resolveIncludePath( - includeFile, - workingDir, - params, - basePaths - ); - - // Register dependency with host compiler (for hot reload / rebuilds) - const compiler = ( - fileData as - | { _compiler?: { addDependency?: (p: string) => void } } - | undefined - )?._compiler; - compiler?.addDependency?.(targetPath); - - const isCodeFile = !( - includeFile.endsWith(".md") || includeFile.endsWith(".mdx") - ); - const asCode = Boolean(params.lang) || isCodeFile; + const { file: includeFile } = parseIncludeSpecifier(specifier); + let resolution: IncludeResolution; try { - const content = await readFile(targetPath, "utf8"); - - if (asCode) { - const lang = params.lang ?? extname(includeFile).slice(1); - - Object.assign(node, { - type: "code", - lang, - meta: params.meta, - value: content, - data: {}, - } satisfies Code); - return; - } - - // For markdown/MDX files, parse and include the content properly - const { content: bodyContent } = parseFrontmatter(content); - - // Prefer host site's processor to preserve its plugins/transforms - const ext = includeFile.endsWith(".md") ? "md" : "mdx"; - const hostProcessor = (fileData as Record | undefined) - ?._processor as { getProcessor?: (kind: string) => unknown } | undefined; - const parser = hostProcessor?.getProcessor - ? hostProcessor.getProcessor(ext) - : sharedProcessor; - - includeContentAsMarkdown(node, includeFile, bodyContent, { - baseDir: dirname(targetPath), - section, - parser: parser as ParserLike, + resolution = await resolveInclude(specifier, { + fromDir: workingDir, + basePaths, + baseDir: params.baseDir ?? undefined, + cwd: "cwd" in params, + lang: params.lang ?? undefined, }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.warn({ human: { - message: `failed to include ${targetPath}: ${errorMessage}`, + message: `failed to include ${includeFile}: ${errorMessage}`, }, json: { event: "include.read_failed", - fields: { target: targetPath, reason: errorMessage }, + fields: { target: includeFile, reason: errorMessage }, }, }); - // Replace with error message Object.assign(node, { type: "paragraph", children: [ @@ -455,7 +521,41 @@ async function processIncludeNode( }, ], }); + return; } + + // Register dependency with host compiler (for hot reload / rebuilds) + const compiler = ( + fileData as + | { _compiler?: { addDependency?: (p: string) => void } } + | undefined + )?._compiler; + compiler?.addDependency?.(resolution.resolvedPath); + + if (resolution.kind === "code") { + Object.assign(node, { + type: "code", + lang: resolution.lang, + meta: params.meta, + value: resolution.content, + data: {}, + } satisfies Code); + return; + } + + // Prefer host site's processor to preserve its plugins/transforms + const ext = resolution.resolvedPath.endsWith(".md") ? "md" : "mdx"; + const hostProcessor = (fileData as Record | undefined) + ?._processor as { getProcessor?: (kind: string) => unknown } | undefined; + const parser = hostProcessor?.getProcessor + ? hostProcessor.getProcessor(ext) + : sharedProcessor; + + includeContentAsMarkdown(node, includeFile, resolution.content, { + baseDir: dirname(resolution.resolvedPath), + section: resolution.section, + parser: parser as ParserLike, + }); } export function remarkInclude( diff --git a/packages/leadtype/src/remark/plugins/mermaid.remark.ts b/packages/leadtype/src/remark/plugins/mermaid.remark.ts index 9b4e9f8..1493934 100644 --- a/packages/leadtype/src/remark/plugins/mermaid.remark.ts +++ b/packages/leadtype/src/remark/plugins/mermaid.remark.ts @@ -11,12 +11,11 @@ import { // Precompiled regexes const ESCAPED_NL = /\\n/g; // "\\n" -> actual newline const CRLF = /\r\n/g; // CRLF -> LF -const LEADING_BACKTICK_LINE = /^`\s*\n/; // a lone backtick then newline -const TRAILING_BACKTICK_LINE = /\n\s*`$/; // newline then a lone backtick +const LEADING_BACKTICK = /^`+/; // backticks at the very start (after trim) +const TRAILING_BACKTICK = /`+$/; // backticks at the very end (after trim) const TRAILING_WHITESPACE = /[ \t]+$/; // trailing spaces/tabs on a line const LEADING_BLANK_LINES = /^\s*\n+/; // one or more blank lines at start const TRAILING_BLANK_LINES = /\n+\s*$/; // one or more blank lines at end -const HTML_BREAK = //gi; function cleanMermaidSource(raw: string): string { // Step 1: Normalize CRLF to LF @@ -25,14 +24,22 @@ function cleanMermaidSource(raw: string): string { // Step 2: Convert escaped newlines s = s.replace(ESCAPED_NL, "\n"); - // Step 2.5: Convert HTML line breaks to plain text separators for readability - s = s.replace(HTML_BREAK, " / "); + // NOTE: `
` is mermaid's own syntax for line breaks inside node labels. + // We intentionally leave it untouched — replacing it would break diagrams + // for any downstream mermaid renderer (agents that render mermaid get the + // correct multi-line labels; agents that don't render mermaid skip the + // code fence entirely). - // Step 3: Strip only outer blank lines (not leading spaces) + // Step 3: Strip outer blank lines (not leading spaces, which mermaid uses + // for hierarchy) s = s.replace(LEADING_BLANK_LINES, "").replace(TRAILING_BLANK_LINES, ""); - // Step 4: Remove leading/trailing backtick guard lines - s = s.replace(LEADING_BACKTICK_LINE, "").replace(TRAILING_BACKTICK_LINE, ""); + // Step 4: Strip the wrapping template-literal backticks. Author MDX usually + // writes ` app`} />` so the + // attribute value comes through with backticks inline with the first/last + // statement. The old regex required the backticks on their own lines and + // missed this common form. + s = s.replace(LEADING_BACKTICK, "").replace(TRAILING_BACKTICK, ""); // Step 5: Split into lines, trim only trailing whitespace from each line, rejoin // (preserving leading indentation) diff --git a/packages/leadtype/src/remark/plugins/type-table-jsx.remark.ts b/packages/leadtype/src/remark/plugins/type-table-jsx.remark.ts new file mode 100644 index 0000000..38252c7 --- /dev/null +++ b/packages/leadtype/src/remark/plugins/type-table-jsx.remark.ts @@ -0,0 +1,164 @@ +/** + * Remark plugin that resolves `` + * (and its `` alias) into `` + * MDX JSX nodes at build time. + * + * Companion to `remarkTypeTableToMarkdown` (which flattens to a markdown + * table for the agent/LLM pipeline). This variant keeps the node as JSX + * so the consumer's runtime `` component receives the resolved + * `properties` prop directly — no markdown table baked in. + * + * Use this in the `mdxSourcePlugins` preset shipped from `leadtype/mdx`. + */ + +import { resolve } from "node:path"; +import type { Root } from "mdast"; +import type { MdxJsxFlowElement } from "mdast-util-mdx"; +import { getAttributeValue, hasName, type MdxNode } from "../libs"; +import { extractTypeFromFile } from "./type-table.remark"; + +const DEFAULT_EXTRACTED_TYPE_BASE_PATH = "docs"; + +export type RemarkResolveTypeTableJsxOptions = { + /** Base directory used to resolve relative `path=` attributes. */ + basePath?: string; +}; + +type AttrValueExpression = { + type: "mdxJsxAttributeValueExpression"; + value: string; +}; + +type JsxAttribute = { + type: "mdxJsxAttribute"; + name: string; + value: string | AttrValueExpression | null; +}; + +function stringAttribute(name: string, value: string): JsxAttribute { + return { type: "mdxJsxAttribute", name, value }; +} + +function expressionAttribute(name: string, expression: string): JsxAttribute { + return { + type: "mdxJsxAttribute", + name, + value: { type: "mdxJsxAttributeValueExpression", value: expression }, + }; +} + +function isExtractedTypeTableNode(node: MdxNode): boolean { + return hasName(node, "ExtractedTypeTable") || hasName(node, "AutoTypeTable"); +} + +function buildTypeTableNode(opts: { + properties: Record; + title?: string; + description?: string; + name?: string; + path?: string; +}): MdxJsxFlowElement { + 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", + name: "TypeTable", + attributes: attributes as MdxJsxFlowElement["attributes"], + children: [], + }; +} + +export function remarkResolveTypeTableJsx( + options: RemarkResolveTypeTableJsxOptions = {} +): (tree: Root) => Root { + const defaultBasePath = resolve( + process.cwd(), + DEFAULT_EXTRACTED_TYPE_BASE_PATH + ); + const basePath = options.basePath ?? defaultBasePath; + + return (tree: Root): Root => { + const replace = ( + parentChildren: Root["children"], + index: number, + replacement: MdxJsxFlowElement + ) => { + parentChildren.splice( + index, + 1, + replacement as unknown as Root["children"][number] + ); + }; + + const visitChildren = (parentChildren: Root["children"]): void => { + for (let index = 0; index < parentChildren.length; index += 1) { + const child = parentChildren[index] as MdxNode | undefined; + if (!child) { + continue; + } + + const node = child as unknown as { + type?: string; + children?: Root["children"]; + }; + + if ( + child && + (child.type === "mdxJsxFlowElement" || + child.type === "mdxJsxTextElement") && + isExtractedTypeTableNode(child) + ) { + const name = getAttributeValue(child, "name"); + const path = getAttributeValue(child, "path"); + const title = getAttributeValue(child, "title") ?? undefined; + const description = + getAttributeValue(child, "description") ?? undefined; + const overrideBasePath = + getAttributeValue(child, "basePath") ?? basePath; + + if (!(name && path)) { + continue; + } + + // Always rewrite the tag to `` so consumers only ever + // implement one runtime component. If extraction failed, `properties` + // is `{}` and `name`/`path` are still passed through — the consumer's + // TypeTable can render a placeholder for that case. + const extracted = extractTypeFromFile(path, name, overrideBasePath); + replace( + parentChildren, + index, + buildTypeTableNode({ + properties: extracted ?? {}, + title, + description, + name, + path, + }) + ); + continue; + } + + if (node.children && Array.isArray(node.children)) { + visitChildren(node.children); + } + } + }; + + visitChildren(tree.children); + return tree; + }; +} diff --git a/packages/leadtype/src/source/index.ts b/packages/leadtype/src/source/index.ts new file mode 100644 index 0000000..10f8720 --- /dev/null +++ b/packages/leadtype/src/source/index.ts @@ -0,0 +1,384 @@ +/** + * `createDocsSource()` — framework-neutral docs source primitive. + * + * Wraps the existing leadtype primitives (`resolveDocsNavigation`, + * `createDocsSearchIndex`, `convertMdxFile`, `resolveInclude`, …) into a + * single object consumers can wire into any renderer: + * + * - fumadocs: see `leadtype/fumadocs`. + * - Next App Router: import the source object, call `loadPage(slug)` from a + * server component, render `result.ast` with `@mdx-js/mdx`. + * - Vite + @mdx-js/rollup: import source `.mdx` directly through the bundler + * with `mdxSourcePlugins`; this primitive provides nav + search. + * + * The primitive does **no I/O on construction** beyond a directory scan for + * `listPages()`. Page bodies are loaded on demand. + */ + +import { existsSync } from "node:fs"; +import { readFile } from "node:fs/promises"; +import path from "node:path"; +import type { Root } from "mdast"; +import { glob as fg } from "tinyglobby"; +import type { PluggableList } from "unified"; +import { convertMdxFile } from "../convert"; +import { + type DocsPathMount, + normalizeBaseUrl, + normalizeDocsPath, + stripDocsExtension, + toAbsoluteUrl, + toDocsUrlPath, +} from "../internal/docs-url"; +import { parseFrontmatter } from "../internal/frontmatter"; +import type { + DocsGroup, + DocsTableOfContentsItem, + DocsTableOfContentsOptions, +} from "../llm"; +import { extractDocsTableOfContents, resolveDocsNavigation } from "../llm"; +import type { DocsNavigation } from "../llm/readability"; +import { mdxSourcePlugins } from "../mdx/source-preset"; +import { + type IncludeResolution, + type ResolveIncludeOptions, + resolveInclude, +} from "../remark/plugins/include.remark"; +import { + type CreateDocsSearchIndexOptions, + createDocsSearchIndex, + type DocsSearchBundle, + type DocsSearchDocument, + type DocsSearchIndex, +} from "../search/search"; + +const DOC_EXTENSIONS = [".md", ".mdx"] as const; + +export type DocsPageMeta = { + /** Slug segments derived from the relative path (no extension, no `index`). */ + slug: string[]; + /** Canonical site URL path (e.g. `/docs/quickstart`). */ + urlPath: string; + /** Source path relative to `contentDir`, **without** extension. */ + relativePath: string; + /** Source file extension (".md" or ".mdx"). */ + extension: ".md" | ".mdx"; + /** Absolute path of the source file. Useful for deep framework adapters. */ + filePath: string; + /** Resolved title (frontmatter `title:`, falling back to the filename). */ + title: string; + /** Resolved description from frontmatter; may be empty. */ + description: string; + /** Group slugs declared in frontmatter. */ + groups: string[]; +}; + +export type DocsPage = DocsPageMeta & { + /** Parsed frontmatter as a plain object. */ + frontmatter: Record; + /** Serialized markdown after the configured remark plugins ran. */ + markdown: string; + /** mdast Root after the configured plugins ran — render this for live MDX. */ + ast: Root; + /** Table of contents derived from the document's headings. */ + toc: DocsTableOfContentsItem[]; +}; + +export type CreateDocsSourceConfig = { + /** Directory containing source `.md` / `.mdx` files (e.g. `"./content/docs"`). */ + contentDir: string; + /** + * Optional doc-groups for navigation. When omitted, navigation is still + * computed but `groups` will be empty (all pages appear under `ungrouped`). + */ + groups?: DocsGroup[]; + /** Base URL for absolute links (search index, TOC anchors). */ + baseUrl?: string; + /** Multi-mount configuration; matches `resolveDocsNavigation`. */ + mounts?: DocsPathMount[]; + /** + * Remark plugins to apply when loading pages. Defaults to `mdxSourcePlugins` + * (expand includes, resolve ``, strip authoring `import`s). + * Pass `[]` to skip transforms. + */ + remarkPlugins?: PluggableList; + /** TOC extraction options. Pass `false` to skip TOC computation entirely. */ + toc?: DocsTableOfContentsOptions | false; + /** Search-index tuning. */ + searchIndex?: CreateDocsSearchIndexOptions; +}; + +export type DocsSource = { + /** Absolute path to the resolved docs directory. */ + contentDir: string; + /** Compute the docs navigation from configured groups + filesystem state. */ + getNavigation(): Promise; + /** Enumerate every doc page found under `contentDir`. */ + listPages(): Promise; + /** + * Load a single page by slug. Accepts either an already-split slug array or + * a slash-joined string. Returns `null` if no matching file exists. + */ + loadPage(slug: string | string[]): Promise; + /** Build a search index from every page's resolved markdown. */ + buildSearchIndex(): Promise; + /** + * Resolve an `` reference outside of a remark pass (e.g. when + * loading a partial for direct rendering). `fromPath` defaults to + * `contentDir`. + */ + resolveInclude( + specifier: string, + options?: Partial + ): Promise; +}; + +function isDocFile(filePath: string): boolean { + return DOC_EXTENSIONS.some((ext) => filePath.endsWith(ext)); +} + +function deriveSlug(relativePath: string): string[] { + const withoutExtension = stripDocsExtension(relativePath); + return withoutExtension + .split("/") + .filter((segment) => segment.length > 0) + .reduce((acc, segment, index, array) => { + if (segment === "index" && index === array.length - 1) { + return acc; + } + acc.push(segment); + return acc; + }, []); +} + +function titleFromRelativePath(relativePath: string): string { + const stripped = stripDocsExtension(relativePath); + const last = stripped.split("/").filter(Boolean).pop() ?? "Untitled"; + const segment = last === "index" ? "Index" : last; + return segment + .replace(/[-_]+/g, " ") + .replace(/\b\w/g, (char) => char.toUpperCase()); +} + +function normalizeGroupValue(value: unknown): string[] { + if (typeof value === "string") { + return [value.trim()].filter(Boolean); + } + if (Array.isArray(value)) { + return value + .filter((entry): entry is string => typeof entry === "string") + .map((entry) => entry.trim()) + .filter(Boolean); + } + return []; +} + +async function readPageMeta( + filePath: string, + contentDir: string, + mounts?: DocsPathMount[] +): Promise { + const relativePath = normalizeDocsPath(path.relative(contentDir, filePath)); + const raw = await readFile(filePath, "utf8"); + const parsed = parseFrontmatter(raw); + const title = + String(parsed.data.title ?? "").trim() || + titleFromRelativePath(relativePath); + const description = String(parsed.data.description ?? "").trim(); + const groups = normalizeGroupValue(parsed.data.group); + const slug = deriveSlug(relativePath); + const urlPath = toDocsUrlPath(relativePath, mounts); + const extension = filePath.endsWith(".mdx") ? ".mdx" : ".md"; + return { + slug, + urlPath, + relativePath: stripDocsExtension(relativePath), + extension, + filePath, + title, + description, + groups, + }; +} + +export async function createDocsSource( + config: CreateDocsSourceConfig +): Promise { + const contentDir = path.resolve(config.contentDir); + if (!existsSync(contentDir)) { + throw new Error( + `createDocsSource: contentDir does not exist at "${contentDir}"` + ); + } + + const baseUrl = normalizeBaseUrl(config.baseUrl); + const remarkPlugins = config.remarkPlugins ?? mdxSourcePlugins; + const tocOptions: DocsTableOfContentsOptions | false = + config.toc === false ? false : (config.toc ?? {}); + + let cachedFiles: string[] | null = null; + let cachedMetas: DocsPageMeta[] | null = null; + // Slug → meta lookup populated alongside cachedMetas so loadPage runs in O(1). + let cachedMetaBySlug: Map | null = null; + + async function listFiles(): Promise { + if (cachedFiles) { + return cachedFiles; + } + const matches = await fg("**/*.{md,mdx}", { + absolute: true, + cwd: contentDir, + onlyFiles: true, + }); + cachedFiles = matches + .filter(isDocFile) + .sort((left, right) => left.localeCompare(right)); + return cachedFiles; + } + + async function listMetas(): Promise { + if (cachedMetas) { + return cachedMetas; + } + const files = await listFiles(); + const metas = await Promise.all( + files.map((filePath) => readPageMeta(filePath, contentDir, config.mounts)) + ); + // Reject duplicate slugs / urlPaths. Without this guard, two files that + // normalize to the same route (e.g. `guide.mdx` and `guide/index.mdx`, or + // both `.md` and `.mdx` variants) would silently overwrite each other in + // the slug Map below, leaving consumers with an indeterminate page. + // `resolveDocsNavigation` already errors on this; keep the source + // primitive consistent. + const slugIndex = new Map(); + const urlPathIndex = new Map(); + for (const meta of metas) { + const slugKey = meta.slug.join("/"); + const existingSlug = slugIndex.get(slugKey); + if (existingSlug) { + throw new Error( + `Duplicate slug "/${slugKey}" — both "${existingSlug.relativePath}${existingSlug.extension}" and "${meta.relativePath}${meta.extension}" resolve to the same route. Rename one or remove it.` + ); + } + const existingUrl = urlPathIndex.get(meta.urlPath); + if (existingUrl) { + throw new Error( + `Duplicate URL path "${meta.urlPath}" — both "${existingUrl.relativePath}${existingUrl.extension}" and "${meta.relativePath}${meta.extension}" resolve to the same route. Rename one or remove it.` + ); + } + slugIndex.set(slugKey, meta); + urlPathIndex.set(meta.urlPath, meta); + } + + cachedMetas = metas; + cachedMetaBySlug = slugIndex; + return cachedMetas; + } + + async function findMetaForSlug(slug: string[]): Promise { + // Ensure the slug index is populated. `listMetas()` is cached after the + // first call so subsequent loadPage() invocations are O(1). + await listMetas(); + return cachedMetaBySlug?.get(slug.join("/")) ?? null; + } + + async function getNavigation(): Promise { + return await resolveDocsNavigation({ + srcDir: path.dirname(contentDir), + docsDirName: path.basename(contentDir), + baseUrl: config.baseUrl, + groups: config.groups ?? [], + mounts: config.mounts, + toc: tocOptions === false ? false : tocOptions, + }); + } + + async function listPages(): Promise { + return await listMetas(); + } + + async function loadPage( + slugInput: string | string[] + ): Promise { + const slug = Array.isArray(slugInput) + ? slugInput + : slugInput.split("/").filter(Boolean); + const meta = await findMetaForSlug(slug); + if (!meta) { + return null; + } + + const result = await convertMdxFile(meta.filePath, remarkPlugins); + const toc = + tocOptions === false + ? [] + : extractDocsTableOfContents( + result.markdown, + { + urlPath: meta.urlPath, + absoluteUrl: toAbsoluteUrl(meta.urlPath, baseUrl), + }, + tocOptions + ); + + return { + ...meta, + frontmatter: result.data, + markdown: result.markdown, + ast: result.ast, + toc, + }; + } + + async function buildSearchIndex(): Promise { + const metas = await listMetas(); + const documents: DocsSearchDocument[] = await Promise.all( + metas.map(async (meta) => { + const result = await convertMdxFile(meta.filePath, remarkPlugins); + return { + id: meta.urlPath, + title: meta.title, + description: meta.description, + urlPath: meta.urlPath, + absoluteUrl: toAbsoluteUrl(meta.urlPath, baseUrl), + relativePath: meta.relativePath, + content: result.markdown, + }; + }) + ); + const index: DocsSearchIndex = createDocsSearchIndex( + documents, + config.searchIndex + ); + return { + index, + content: index.content ?? { + version: index.version, + generatedAt: index.generatedAt, + chunks: [], + }, + }; + } + + async function resolveIncludeBound( + specifier: string, + options?: Partial + ): Promise { + return await resolveInclude(specifier, { + fromDir: options?.fromDir ?? contentDir, + basePaths: options?.basePaths, + baseDir: options?.baseDir, + cwd: options?.cwd, + lang: options?.lang, + }); + } + + return { + contentDir, + getNavigation, + listPages, + loadPage, + buildSearchIndex, + resolveInclude: resolveIncludeBound, + }; +} diff --git a/packages/leadtype/src/source/source.test.ts b/packages/leadtype/src/source/source.test.ts new file mode 100644 index 0000000..632d6ee --- /dev/null +++ b/packages/leadtype/src/source/source.test.ts @@ -0,0 +1,177 @@ +import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { createDocsSource } from "./index"; + +async function writeMdx(filePath: string, content: string): Promise { + await mkdir(path.dirname(filePath), { recursive: true }); + await writeFile(filePath, content); +} + +describe("createDocsSource", () => { + let contentDir: string; + + beforeEach(async () => { + contentDir = await mkdtemp(path.join(tmpdir(), "leadtype-source-")); + }); + + afterEach(async () => { + await rm(contentDir, { force: true, recursive: true }); + }); + + it("lists every .md / .mdx page under contentDir with stable slug derivation", async () => { + await writeMdx( + path.join(contentDir, "quickstart.mdx"), + "---\ntitle: Quickstart\n---\nBody.\n" + ); + await writeMdx( + path.join(contentDir, "guides/setup.md"), + "---\ntitle: Setup\n---\nBody.\n" + ); + await writeMdx( + path.join(contentDir, "guides/index.mdx"), + "---\ntitle: Guides\n---\nBody.\n" + ); + + const source = await createDocsSource({ contentDir }); + const pages = await source.listPages(); + + const slugs = pages.map((page) => page.slug.join("/")).sort(); + expect(slugs).toEqual(["guides", "guides/setup", "quickstart"]); + + const quickstart = pages.find((p) => p.slug.join("/") === "quickstart"); + expect(quickstart).toMatchObject({ + title: "Quickstart", + extension: ".mdx", + }); + expect(quickstart?.filePath).toMatch(/quickstart\.mdx$/); + }); + + it("loadPage returns parsed frontmatter, ast, markdown, and toc", async () => { + await writeMdx( + path.join(contentDir, "quickstart.mdx"), + "---\ntitle: Quickstart\ndescription: Get started\n---\n\n## Install\n\nRun the command.\n" + ); + + const source = await createDocsSource({ + contentDir, + baseUrl: "https://example.com", + }); + const page = await source.loadPage("quickstart"); + + expect(page).not.toBeNull(); + expect(page?.frontmatter).toMatchObject({ + title: "Quickstart", + description: "Get started", + }); + expect(page?.ast.type).toBe("root"); + expect(page?.markdown).toContain("## Install"); + expect(page?.toc.map((item) => item.title)).toEqual(["Install"]); + }); + + it("loadPage accepts both string and string[] slug forms", async () => { + await writeMdx( + path.join(contentDir, "guides/setup.mdx"), + "---\ntitle: Setup\n---\nBody.\n" + ); + + const source = await createDocsSource({ contentDir }); + const byString = await source.loadPage("guides/setup"); + const byArray = await source.loadPage(["guides", "setup"]); + + expect(byString?.slug).toEqual(["guides", "setup"]); + expect(byArray?.slug).toEqual(["guides", "setup"]); + expect(byString?.title).toBe(byArray?.title); + }); + + it("loadPage returns null when no slug matches", async () => { + await writeMdx(path.join(contentDir, "quickstart.mdx"), "# Q\n"); + + const source = await createDocsSource({ contentDir }); + const missing = await source.loadPage("nope"); + expect(missing).toBeNull(); + }); + + it("expands references via the default source preset", async () => { + await writeMdx( + path.join(contentDir, "shared/install.mdx"), + "## Install\n\nRun `bun add leadtype`.\n" + ); + await writeMdx( + path.join(contentDir, "quickstart.mdx"), + '---\ntitle: Quickstart\n---\n\nIntro.\n\n\n' + ); + + const source = await createDocsSource({ contentDir }); + const page = await source.loadPage("quickstart"); + + expect(page?.markdown).toContain("## Install"); + expect(page?.markdown).toContain("bun add leadtype"); + }); + + it("buildSearchIndex emits an index whose document ids match urlPaths", async () => { + await writeMdx( + path.join(contentDir, "quickstart.mdx"), + "---\ntitle: Quickstart\n---\nBody one.\n" + ); + await writeMdx( + path.join(contentDir, "guides/setup.mdx"), + "---\ntitle: Setup\n---\nBody two.\n" + ); + + const source = await createDocsSource({ + contentDir, + baseUrl: "https://example.com", + }); + const bundle = await source.buildSearchIndex(); + + expect(bundle.index.documents.length).toBe(2); + const documentIds = bundle.index.documents.map(([id]) => id).sort(); + expect(documentIds).toEqual(["/docs/guides/setup", "/docs/quickstart"]); + }); + + it("getNavigation routes pages into declared groups", async () => { + await writeMdx( + path.join(contentDir, "guides/setup.mdx"), + "---\ntitle: Setup\ngroup: get-started\n---\nBody.\n" + ); + await writeMdx( + path.join(contentDir, "quickstart.mdx"), + "---\ntitle: Quickstart\ngroup: get-started\n---\nBody.\n" + ); + + const source = await createDocsSource({ + contentDir, + groups: [{ slug: "get-started", title: "Get Started" }], + }); + const navigation = await source.getNavigation(); + + expect(navigation.groups).toHaveLength(1); + expect(navigation.groups[0]?.pages.map((p) => p.title).sort()).toEqual([ + "Quickstart", + "Setup", + ]); + }); + + it("resolveInclude defaults the fromDir to contentDir", async () => { + await writeMdx( + path.join(contentDir, "shared/install.mdx"), + "## Install\n\nDo the thing.\n" + ); + + const source = await createDocsSource({ contentDir }); + const result = await source.resolveInclude("shared/install.mdx"); + + expect(result.kind).toBe("markdown"); + if (result.kind === "markdown") { + expect(result.content).toContain("Do the thing."); + } + }); + + it("throws if contentDir does not exist", async () => { + await expect( + createDocsSource({ contentDir: path.join(contentDir, "does-not-exist") }) + ).rejects.toThrow(/does not exist/); + }); +});