;
+}
+```
+
+### Extension Constants
+
+```typescript
+// Default extensions for JavaScript/TypeScript modules
+const JAVASCRIPT_MODULE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.d.ts'];
+
+// Type import extensions (prioritize .d.ts first)
+const TYPE_IMPORT_EXTENSIONS = ['.d.ts', '.ts', '.tsx', '.js', '.jsx'];
+
+// Value import extensions (standard priority)
+const VALUE_IMPORT_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.d.ts'];
+```
+
+## Performance Optimizations
+
+### Filesystem Access Optimization
+
+The utilities are optimized to minimize filesystem calls:
+
+- **Single directory read** when `includeTypeDefs` is enabled
+- **Batch resolution** for multiple imports in the same directory
+- **Efficient file mapping** using basename lookups
+- **Index file caching** for directory-based imports
+
+## Error Handling
+
+All functions include comprehensive error handling:
+
+```typescript
+try {
+ const result = await resolveModulePath('/nonexistent/path', directoryReader);
+} catch (error) {
+ console.error(`Module resolution failed: ${error.message}`);
+ // Error: Could not resolve module at path "/nonexistent/path".
+ // Tried extensions: .ts, .tsx, .js, .jsx, .d.ts
+}
+```
+
+## Testing
+
+The utilities include comprehensive test coverage:
+
+- **102 total tests** across 7 test files
+- **Integration tests** for full pipeline scenarios
+- **Unit tests** for individual functions
+- **Edge case coverage** for error conditions
+- **Performance benchmarks** for optimization verification
diff --git a/docs/app/docs-infra/functions/page.mdx b/docs/app/docs-infra/functions/page.mdx
new file mode 100644
index 000000000..79880c832
--- /dev/null
+++ b/docs/app/docs-infra/functions/page.mdx
@@ -0,0 +1,43 @@
+# Functions
+
+Developer utilities and build-time tools for the documentation infrastructure.
+
+## Configuration Plugins
+
+- [`withDocsInfra`](./with-docs-infra/page.mdx) - Next.js plugin for configuring webpack loaders, Turbopack rules, and MDX processing for docs sites
+
+## Factories
+
+- [`abstractCreateDemo`](./abstract-create-demo/page.mdx) - Factory utilities for creating structured demos that work with CodeHighlighter
+- [`abstractCreateDemoClient`](./abstract-create-demo-client/page.mdx) - Factory for creating client providers for live demo externals
+- [`createDemoData`](./create-demo-data/page.mdx) - Factory for creating demo metadata and configuration objects
+
+## Source Processing
+
+- [`parseSource`](./parse-source/page.mdx) - Parse source code into syntax-highlighted HAST using Starry Night
+- [`transformTypescriptToJavascript`](./transform-typescript-to-javascript/page.mdx) - Transform TypeScript/TSX to JavaScript/JSX with Babel and Prettier
+
+## Server Utilities
+
+- [`loadServerCodeMeta`](./load-server-code-meta/page.mdx) - Server utility for loading demo metadata and source information
+- [`loadServerSource`](./load-server-source/page.mdx) - Server utility for loading source code with recursive import resolution
+
+## Webpack Loaders
+
+- [`loadPrecomputedCodeHighlighter`](./load-precomputed-code-highlighter/page.mdx) - Webpack loader for build-time demo optimization
+- [`loadPrecomputedCodeHighlighterClient`](./load-precomputed-code-highlighter-client/page.mdx) - Webpack loader for client-side externals bundling in live demos
+
+## Rehype Plugins
+
+- [`transformHtmlCode`](./transform-html-code/page.mdx) - Plugin for transforming HTML code blocks into precomputed data structures
+
+## Remark Plugins
+
+- [`transformMarkdownCode`](./transform-markdown-code/page.mdx) - Remark plugin for transforming markdown code blocks with variants into HTML structures
+- [`transformMarkdownRelativePaths`](./transform-markdown-relative-paths/page.mdx) - Remark plugin for transforming relative markdown links to absolute paths
+- [`transformMarkdownBlockquoteCallouts`](./transform-markdown-blockquote-callouts/page.mdx) - Remark plugin for GitHub-style callout blockquotes (NOTE, TIP, IMPORTANT, WARNING, CAUTION)
+
+## Utilities
+
+- [`hastUtils`](./hast-utils/page.mdx) - Utilities for working with HAST (Hypertext Abstract Syntax Tree) and converting to JSX
+- [`loaderUtils`](./loader-utils/page.mdx) - A collection of utilities for loading and processing code, including parsing imports and resolving module paths
diff --git a/docs/app/docs-infra/functions/parse-source/page.mdx b/docs/app/docs-infra/functions/parse-source/page.mdx
new file mode 100644
index 000000000..9c13f8d05
--- /dev/null
+++ b/docs/app/docs-infra/functions/parse-source/page.mdx
@@ -0,0 +1,107 @@
+# parseSource
+
+The `parseSource` utility parses source code into HAST (Hypertext Abstract Syntax Tree) nodes with syntax highlighting using [Starry Night](https://github.com/wooorm/starry-night). It converts code into highlighted HTML structures for display in documentation and demos.
+
+## Features
+
+- Syntax highlighting using Starry Night with GitHub-style highlighting
+- Supports multiple programming languages via grammar definitions
+- Adds line gutters for code display
+- Graceful fallback for unsupported file types
+- Returns HAST nodes compatible with React rendering
+
+## Usage
+
+```ts
+import { createParseSource } from '@mui/internal-docs-infra/pipeline/parseSource';
+
+// Create an initialized parseSource function
+const parseSource = await createParseSource();
+
+// Parse and highlight code
+const highlighted = parseSource('const x = 42;', 'example.js');
+
+// Returns HAST tree like:
+// {
+// type: 'root',
+// children: [
+// { type: 'element', tagName: 'span', properties: { className: ['pl-k'] }, children: [{ type: 'text', value: 'const' }] },
+// { type: 'text', value: ' ' },
+// { type: 'element', tagName: 'span', properties: { className: ['pl-s1'] }, children: [{ type: 'text', value: 'x' }] },
+// // ... more highlighted nodes
+// ]
+// }
+```
+
+## Initialization
+
+The function requires initialization before use:
+
+```ts
+import { createParseSource, parseSource } from '@mui/internal-docs-infra/pipeline/parseSource';
+
+// Option 1: Create initialized function
+const myParseSource = await createParseSource();
+const result = myParseSource(source, fileName);
+
+// Option 2: Use global instance (after createParseSource has been called)
+const result = parseSource(source, fileName);
+```
+
+## Supported Languages
+
+The parser supports languages based on file extensions through the grammar definitions:
+
+- **JavaScript**: `.js`, `.mjs`, `.cjs`
+- **TypeScript**: `.ts`, `.tsx`
+- **CSS**: `.css`
+- **HTML**: `.html`, `.htm`
+- **JSON**: `.json`
+- **And many more** via Starry Night grammars
+
+## Fallback Behavior
+
+For unsupported file types, returns a simple HAST structure:
+
+```ts
+const result = parseSource('Some unknown content', 'file.xyz');
+// Returns:
+// {
+// type: 'root',
+// children: [
+// {
+// type: 'text',
+// value: 'Some unknown content'
+// }
+// ]
+// }
+```
+
+## Error Handling
+
+Throws an error if Starry Night is not initialized:
+
+```ts
+// This will throw an error
+parseSource('code', 'file.js'); // Error: Starry Night not initialized
+
+// Must initialize first
+await createParseSource();
+parseSource('code', 'file.js'); // Now works
+```
+
+## Line Gutters
+
+The parser automatically adds line gutters using `starryNightGutter` for better code display with line numbers.
+
+## When to Use
+
+- When implementing syntax highlighting for code blocks
+- When preparing code for display in [`CodeHighlighter`](../../components/code-highlighter/page.mdx)
+- When building custom code processing pipelines that need highlighted output
+
+## Related
+
+- [Starry Night](https://github.com/wooorm/starry-night) - The underlying syntax highlighter
+- [`hastUtils`](../hast-utils/page.mdx) - For converting HAST to React JSX
+- [`CodeHighlighter`](../../components/code-highlighter/page.mdx) - Uses this for syntax highlighting
diff --git a/docs/app/docs-infra/functions/transform-html-code/page.mdx b/docs/app/docs-infra/functions/transform-html-code/page.mdx
new file mode 100644
index 000000000..383da605b
--- /dev/null
+++ b/docs/app/docs-infra/functions/transform-html-code/page.mdx
@@ -0,0 +1,181 @@
+# transformHtmlCode
+
+A rehype plugin that transforms `` elements containing `` blocks into precomputed data for the CodeHighlighter component. This plugin extracts source code from HTML, processes it through syntax highlighting and transformations, then stores the results for efficient client-side rendering.
+
+## Overview
+
+This plugin is typically used in the **second stage** of a markdown-to-HTML processing pipeline, after [`transformMarkdownCodeVariants`](/functions/transformMarkdownCodeVariants) has converted markdown code blocks into HTML structures.
+
+## Key Features
+
+- **Multiple code variants**: Process multiple languages or versions within a single code block
+- **Automatic language detection**: Determines file types from `class="language-*"` attributes
+- **Custom variant names**: Supports `data-variant` attribute for explicit naming
+- **Custom filenames**: Supports `data-filename` attribute for specific file extensions
+- **Opt-in transformations**: Code blocks are only transformed when marked with `data-transform="true"`
+- **Built-in transformations**: Includes TypeScript-to-JavaScript conversion for marked blocks
+- **Syntax highlighting**: Pre-processes code for faster client rendering
+- **Error handling**: Gracefully handles processing errors
+
+## Installation & Usage
+
+```typescript
+import { unified } from 'unified';
+import rehypeParse from 'rehype-parse';
+import { transformHtmlCode } from '@mui/internal-docs-infra/transformHtmlCode';
+
+const processor = unified()
+ .use(rehypeParse)
+ .use(transformHtmlCode) // No configuration needed
+ .use(rehypeStringify);
+```
+
+### With Next.js and MDX
+
+```javascript
+// next.config.js
+const withMDX = require('@next/mdx')({
+ options: {
+ remarkPlugins: [transformMarkdownCodeVariants],
+ rehypePlugins: [transformHtmlCode],
+ },
+});
+
+module.exports = withMDX({
+ // your Next.js config
+});
+```
+
+## Input Examples
+
+### Single Code Block
+
+```html
+
+const greeting: string = "Hello, world!";
+console.log(greeting);
+
+```
+
+### Multiple Code Variants (Different Languages)
+
+```html
+
+ console.log("Hello, world!");
+ console.log("Hello, world!" as string);
+
+```
+
+### Custom Variant Names
+
+```html
+
+ console.log("Running on client");
+ console.log("Running on server");
+
+```
+
+### Custom Filenames
+
+```html
+
+const App = () => Hello React!
;
+export default App;
+
+```
+
+### Code Block Transformations
+
+By default, code blocks are processed for syntax highlighting only. To enable transformations (like TypeScript-to-JavaScript conversion), explicitly mark the code block:
+
+```html
+
+const greeting: string = "Hello, world!";
+console.log(greeting);
+
+```
+
+This will generate both the original TypeScript and the transformed JavaScript versions.
+
+## Output
+
+After processing, the plugin:
+
+1. **Replaces content** with a placeholder message
+2. **Stores processed data** in `dataPrecompute` attribute as JSON
+3. **Preserves original structure** for the CodeHighlighter component
+
+```html
+
+ Error: expected pre tag to be handled by CodeHighlighter
+
+```
+
+## Variant Naming
+
+| Scenario | Variant Names |
+| --------------------- | ---------------------------------- |
+| Single code block | `"Default"` |
+| Different languages | `"Js"`, `"Ts"`, `"Html"`, etc. |
+| Same language | `"Variant 1"`, `"Variant 2"`, etc. |
+| Custom `data-variant` | Uses the specified name |
+
+## Supported Languages
+
+| Class | Extension | Notes |
+| ------------------------------------ | --------- | ----------------------------------------------------- |
+| `language-js`, `language-javascript` | `.js` | |
+| `language-ts`, `language-typescript` | `.ts` | Includes TS→JS transform when `data-transform="true"` |
+| `language-tsx` | `.tsx` | React TypeScript |
+| `language-jsx` | `.jsx` | React JavaScript |
+| `language-json` | `.json` | |
+| `language-html` | `.html` | |
+| `language-css` | `.css` | |
+| `language-bash`, `language-shell` | `.sh` | |
+| `language-yaml`, `language-yml` | `.yaml` | |
+| Others | `.txt` | Fallback |
+
+## Integration with transformMarkdownCodeVariants
+
+These plugins work together in a processing pipeline:
+
+1. **transformMarkdownCodeVariants** (remark): Converts markdown code blocks with variants into HTML
+2. **transformHtmlCode** (rehype): Processes the HTML and precomputes data for rendering
+
+### Complete Example
+
+**Markdown Input:**
+
+````markdown
+npm
+
+```bash variant-group=install
+npm install package
+```
+
+pnpm
+
+```bash variant-group=install
+pnpm install package
+```
+````
+
+**After transformMarkdownCodeVariants:**
+
+```html
+
+ npm install package
+ pnpm install package
+
+```
+
+**After transformHtmlCode:**
+
+```html
+
+ Error: expected pre tag to be handled by CodeHighlighter
+
+```
+
+- Processes code blocks in parallel across the document
+- Optimized text extraction with single-pass traversal
diff --git a/docs/app/docs-infra/functions/transform-markdown-blockquote-callouts/page.mdx b/docs/app/docs-infra/functions/transform-markdown-blockquote-callouts/page.mdx
new file mode 100644
index 000000000..92eaf309a
--- /dev/null
+++ b/docs/app/docs-infra/functions/transform-markdown-blockquote-callouts/page.mdx
@@ -0,0 +1,120 @@
+# Transform Markdown Blockquote Callouts
+
+The `transformMarkdownBlockquoteCallouts` plugin is a Remark plugin that enables GitHub-style callout blocks in markdown. It transforms blockquotes with special markers (like `> [!NOTE]`) into HTML blockquotes with a `data-callout-type` attribute, making it easy to style and render callouts in your documentation.
+
+## Features
+
+- **GitHub-style callouts**: Supports `[!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, and `[!CAUTION]` markers
+- **HTML data attributes**: Adds `data-callout-type` to blockquotes for easy styling
+- **Removes callout marker**: Cleans up the markdown so the marker does not appear in the rendered output
+- **Works with multi-paragraph and nested content**
+- **Compatible with Next.js, MDX, and unified pipelines**
+
+## Supported Callout Types
+
+| Marker | Attribute Value |
+| -------------- | --------------- |
+| `[!NOTE]` | `note` |
+| `[!TIP]` | `tip` |
+| `[!IMPORTANT]` | `important` |
+| `[!WARNING]` | `warning` |
+| `[!CAUTION]` | `caution` |
+
+### Default Colors
+
+| Callout Type | Color |
+| ------------ | ------ |
+| `note` | Blue |
+| `tip` | Green |
+| `important` | Purple |
+| `warning` | Orange |
+| `caution` | Red |
+
+> [!NOTE]
+> These are the default colors used by GitHub. Colors may vary in your implementation, but try to maintain a similar visual hierarchy.
+
+## Example
+
+### How to Write a Callout in Markdown
+
+```markdown
+> [!TIP]
+> This is a tip callout. You can use any of the supported callout types:
+>
+> - [!NOTE]
+> - [!TIP]
+> - [!IMPORTANT]
+> - [!WARNING]
+> - [!CAUTION]
+>
+> The callout marker must be the first text in the blockquote.
+```
+
+### Live Callout Examples
+
+> [!NOTE]
+> This is a note callout. It uses the `[!NOTE]` marker and renders with a special style.
+
+> [!TIP]
+> This is a tip callout. Use tips for helpful suggestions or shortcuts.
+
+> [!IMPORTANT]
+> This is an important callout. Use for critical information.
+
+> [!WARNING]
+> This is a warning callout. Use for potential problems or risks.
+
+> [!CAUTION]
+> This is a caution callout. Use for things users should be careful about.
+
+You can also have multiple paragraphs:
+
+> [!NOTE]
+> This callout has multiple paragraphs.
+>
+> The second paragraph is also included in the callout.
+
+## Usage
+
+Add the plugin to your unified/remark pipeline:
+
+```ts
+import { unified } from 'unified';
+import remarkParse from 'remark-parse';
+import remarkRehype from 'remark-rehype';
+import rehypeStringify from 'rehype-stringify';
+import { transformMarkdownBlockquoteCallouts } from '@mui/internal-docs-infra/pipeline/transformMarkdownBlockquoteCallouts';
+
+const processor = unified()
+ .use(remarkParse)
+ .use(transformMarkdownBlockquoteCallouts)
+ .use(remarkRehype)
+ .use(rehypeStringify);
+
+const markdown = `
+> [!TIP]
+> This is a tip!
+`;
+
+const html = await processor.process(markdown);
+console.log(html.toString());
+```
+
+## Integration
+
+This plugin is included by default in the docs-infra markdown pipeline. You can also use it in any unified/remark-based project.
+
+- **Next.js/MDX**: Add to your remarkPlugins array
+- **Custom pipelines**: Use as shown above
+
+## Best Practices
+
+1. **Use only supported callout types**: Only the five types above will be recognized and styled
+2. **Start callouts at the beginning of a blockquote**: The marker must be the first text in the first paragraph
+3. **Use for notes, tips, warnings, and important info**: Reserve callouts for content that needs to stand out
+4. **Style callouts in your CSS**: Target `[data-callout-type]` attributes for custom styles
+
+## Related
+
+- [GitHub Callouts Syntax](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts) — GitHub's official callout syntax
+- [remark](https://github.com/remarkjs/remark) — Markdown processor
diff --git a/docs/app/docs-infra/functions/transform-markdown-code/page.mdx b/docs/app/docs-infra/functions/transform-markdown-code/page.mdx
new file mode 100644
index 000000000..69fbf94a1
--- /dev/null
+++ b/docs/app/docs-infra/functions/transform-markdown-code/page.mdx
@@ -0,0 +1,473 @@
+# transformMarkdownCode
+
+A remark plugin that transforms markdown code blocks into HTML structures with enhanced metadata support. This plugin handles both individual code blocks with options and multi-variant code examples. It's the **first stage** in a processing pipeline, typically followed by [`transformHtmlCode`](/functions/transformHtmlCode) for final rendering.
+
+## Overview
+
+Use this plugin to enhance markdown code blocks with custom data attributes for highlighting, transformations, and other features. It also supports creating multi-variant code examples to show the same code in different languages, package managers, or configurations.
+
+## Key Features
+
+- **Individual code blocks**: Transform single code blocks with options (e.g., `fileName=test.ts`, `highlight=2-3`)
+- **Multiple variant formats**: Support for `variant=name` and `variant-group=name` syntax
+- **Automatic grouping**: Adjacent code blocks with variants are combined into single examples
+- **Language detection**: Preserves syntax highlighting with `class="language-*"` attributes
+- **Label support**: Extract labels from text between code blocks
+- **Clean HTML output**: Generates semantic HTML structure for further processing
+
+## Installation & Usage
+
+```typescript
+import { unified } from 'unified';
+import remarkParse from 'remark-parse';
+import { transformMarkdownCode } from '@mui/internal-docs-infra/transformMarkdownCode';
+
+const processor = unified().use(remarkParse).use(transformMarkdownCode);
+```
+
+### With Next.js and MDX
+
+```javascript
+// next.config.js
+const withMDX = require('@next/mdx')({
+ options: {
+ remarkPlugins: [transformMarkdownCode],
+ rehypePlugins: [transformHtmlCode], // For final processing
+ },
+});
+
+module.exports = withMDX({
+ // your Next.js config
+});
+```
+
+## Syntax Examples
+
+### Individual Code Blocks with Options
+
+The simplest usage - transform single code blocks by adding options directly:
+
+**Basic Example:**
+
+````markdown
+```ts fileName=greeting.ts
+const greeting: string = 'Hello, world!';
+console.log(greeting);
+```
+````
+
+**HTML Output:**
+
+```html
+const greeting: string = "Hello, world!";
+console.log(greeting);
+```
+
+**Multiple Options:**
+
+````markdown
+```javascript fileName=test.js transform
+function test() {
+ console.log('line 2');
+ console.log('line 3');
+}
+```
+````
+
+**HTML Output:**
+
+```html
+function test() {
+console.log('line 2');
+console.log('line 3');
+}
+```
+
+Individual code blocks with options are processed immediately and don't require grouping with other blocks.
+
+### Basic Variants (variant=name)
+
+Show the same task across different package managers. This style is ideal when the variant names are self-evident from the code content and don't need explicit labeling:
+
+**Markdown Input:**
+
+````markdown
+```bash variant=npm
+npm install package
+```
+
+```bash variant=pnpm
+pnpm install package
+```
+
+```bash variant=yarn
+yarn add package
+```
+````
+
+**HTML Output:**
+
+```html
+
+ npm install package
+ pnpm install package
+ yarn add package
+
+```
+
+### Labeled Variants (variant-group=name)
+
+Add descriptive labels for each variant when the differences aren't obvious from the code alone. This is particularly useful for implementation approaches, configuration strategies, or conceptual differences:
+
+**Markdown Input:**
+
+````markdown
+Production Environment
+
+```javascript variant-group=deployment
+const config = {
+ apiUrl: process.env.PROD_API_URL,
+ cache: { ttl: 3600 },
+ logging: { level: 'error' },
+};
+```
+
+Development Environment
+
+```javascript variant-group=deployment
+const config = {
+ apiUrl: 'http://localhost:3000',
+ cache: { ttl: 0 },
+ logging: { level: 'debug' },
+};
+```
+
+Testing Environment
+
+```javascript variant-group=deployment
+const config = {
+ apiUrl: 'http://test-api.example.com',
+ cache: { ttl: 300 },
+ logging: { level: 'warn' },
+};
+```
+````
+
+**HTML Output:**
+
+```html
+
+ const config = {
+ apiUrl: process.env.PROD_API_URL,
+ cache: { ttl: 3600 },
+ logging: { level: 'error' }
+};
+ const config = {
+ apiUrl: 'http://localhost:3000',
+ cache: { ttl: 0 },
+ logging: { level: 'debug' }
+};
+ const config = {
+ apiUrl: 'http://test-api.example.com',
+ cache: { ttl: 300 },
+ logging: { level: 'warn' }
+};
+
+```
+
+### Different Languages
+
+Show examples across multiple programming languages:
+
+**Markdown Input:**
+
+````markdown
+```javascript variant=client
+fetch('/api/data').then((res) => res.json());
+```
+
+```python variant=server
+import requests
+response = requests.get('/api/data')
+```
+
+```go variant=cli
+resp, err := http.Get("/api/data")
+```
+````
+
+**HTML Output:**
+
+```html
+
+ fetch('/api/data').then(res => res.json())
+ import requests
+response = requests.get('/api/data')
+ resp, err := http.Get("/api/data")
+
+```
+
+### Custom Properties
+
+Add extra metadata using additional properties:
+
+**Markdown Input:**
+
+````markdown
+```bash variant=npm filename=install.sh
+npm install package
+```
+
+```bash variant=pnpm filename=install.sh
+pnpm install package
+```
+````
+
+**HTML Output:**
+
+```html
+
+ npm install package
+ pnpm install package
+
+```
+
+## Plugin Behavior
+
+### Individual Code Block Processing
+
+Code blocks with options (but no `variant` or `variant-group`) are processed immediately:
+
+- **Single transformation**: Creates a `` element with data attributes
+- **No grouping required**: Works with standalone code blocks
+- **Option handling**: Converts options to HTML data attributes (e.g., `fileName=test.ts` → `data-file-name="test.ts"`)
+- **Language preservation**: Maintains syntax highlighting classes
+
+### Grouping Rules
+
+- **Adjacent blocks**: Code blocks must be consecutive (blank lines allowed)
+- **Minimum size**: Groups require at least 2 code blocks
+- **Same format**: All blocks must use either `variant=` or `variant-group=`
+- **Single blocks**: Code blocks with variants that don't form groups remain unchanged
+
+### Label Extraction (variant-group only)
+
+For `variant-group` format, paragraphs between code blocks become variant names:
+
+````markdown
+Client-side
+
+```js variant-group=implementation
+fetch('/api/data');
+```
+
+Server-side
+
+```js variant-group=implementation
+const data = await db.query();
+```
+````
+
+Creates variants named "Client-side" and "Server-side".
+
+### Language Support
+
+All markdown code block languages are supported:
+
+- `js`, `javascript`, `ts`, `typescript`
+- `python`, `go`, `rust`, `java`, `c`, `cpp`
+- `bash`, `shell`, `zsh`, `fish`
+- `html`, `css`, `json`, `yaml`, `xml`
+- And any other language identifier
+
+## Integration with transformHtmlCode
+
+This plugin works seamlessly with [`transformHtmlCode`](../transform-html-code/page.mdx):
+
+1. **transformMarkdownCode** converts markdown to HTML
+2. **transformHtmlCode** processes HTML for rendering components
+
+### Complete Pipeline Example
+
+**Step 1 - Markdown:**
+
+````markdown
+npm
+
+```bash variant-group=install
+npm install package
+```
+
+pnpm
+
+```bash variant-group=install
+pnpm install package
+```
+````
+
+**Step 2 - After transformMarkdownCode:**
+
+```html
+
+ npm install package
+ pnpm install package
+
+```
+
+**Step 3 - After transformHtmlCode:**
+
+```html
+
+ Error: expected pre tag to be handled by CodeHighlighter
+
+```
+
+## Common Use Cases
+
+### Individual Code Enhancement
+
+Add metadata to single code blocks for transformations, highlighting, or special processing.
+
+### Package Manager Examples
+
+Show installation instructions for different package managers.
+
+### Framework Comparisons
+
+Display the same functionality in React, Vue, Angular, etc.
+
+### Configuration Files
+
+Show different formats (JSON, YAML, TOML) for the same configuration.
+
+### API Examples
+
+Demonstrate requests in different programming languages or tools (curl, fetch, axios).
+
+## Configuration
+
+This plugin works out-of-the-box with no configuration required. It automatically:
+
+- Detects options in code block metadata for individual blocks
+- Detects variant syntax in code block metadata for grouping
+- Groups adjacent code blocks with variants
+- Extracts labels from paragraphs (variant-group format)
+- Preserves all code block languages and properties
+- Generates clean, semantic HTML output
+
+## Troubleshooting
+
+### Individual Code Blocks Not Processing
+
+**Problem**: Code blocks with options aren't getting transformed.
+
+**Solutions**:
+
+- Ensure options are in the code block metadata: ` ```js transform` not ` ```js`
+- Check that options don't include `variant` or `variant-group` (those trigger grouping behavior)
+- Verify the code block has a language specified
+
+### Code Blocks Not Grouping
+
+**Problem**: Adjacent code blocks with variants aren't combining into a single `` element.
+
+**Solutions**:
+
+- Ensure all blocks use the same format (`variant=` or `variant-group=`)
+- Check that blocks are truly adjacent (only blank lines allowed between)
+- Verify you have at least 2 code blocks with variant metadata
+
+### Labels Not Working
+
+**Problem**: Using `variant-group` but variant names aren't extracted from paragraphs.
+
+**Solutions**:
+
+- Ensure paragraphs are directly between code blocks
+- Use simple text paragraphs (no complex markdown)
+- Check that all code blocks use `variant-group=samename`
+
+### Missing Language Classes
+
+**Problem**: Generated HTML doesn't have `class="language-*"` attributes.
+
+**Solutions**:
+
+- Ensure code blocks specify a language: ` ```javascript ` not just ` ``` `
+- Check that the language comes before the variant metadata
+- Verify your markdown processor supports language specification
+
+## Technical Details
+
+### HTML Structure
+
+Generated HTML follows this pattern:
+
+```html
+
+
+ {escaped code content}
+
+
+
+```
+
+### Data Attributes
+
+- `data-variant`: The variant name (from `variant=` or extracted label)
+- `data-*`: Any additional properties from code block metadata
+- `class="language-*"`: Preserved for syntax highlighting compatibility
+
+### Performance
+
+- Single-pass processing of the markdown AST
+- Efficient adjacent node grouping algorithm
+- Minimal memory overhead for typical document sizes
+
+## Implementation Details
+
+### AST Transformation
+
+The plugin uses `unist-util-visit` to traverse the markdown AST and identify code blocks with metadata. It then:
+
+1. **Parses metadata**: Extracts options and variant information from code block metadata
+2. **Determines processing type**: Individual blocks vs. variant groups
+3. **Groups adjacent blocks**: Collects consecutive code blocks with variants that belong together
+4. **Generates MDX JSX**: Creates proper `mdxJsxFlowElement` nodes with `pre` and `code` elements
+5. **Sets attributes**: Adds `data-variant`, `className`, and custom data attributes
+6. **Replaces nodes**: Substitutes the original code blocks with the generated MDX JSX elements
+
+### Language Detection
+
+The plugin handles language information in two ways:
+
+- **With explicit language**: `````javascript variant=npm` - language is `javascript`, meta is `variant=npm`
+- **Without explicit language**: `````variant=npm` - meta information is in the language field
+
+### Metadata Parsing
+
+The `parseMeta` function splits metadata strings by spaces and parses key=value pairs:
+
+- `transform` - Boolean flag that becomes `data-transform="true"`
+- `highlight=2-3` - Becomes `data-highlight="2-3"`
+- `variant=name` - Sets the variant for grouping
+- `variant-group=name` - Sets the variant group for label-based grouping
+- `filename=package.json` - Becomes `data-filename="package.json"`
+- Any other properties become data attributes
+
+## Error Handling
+
+The plugin is designed to be robust and graceful:
+
+- Invalid metadata is ignored
+- Individual code blocks with options are processed immediately
+- Single code blocks with variants are left unchanged
+- Non-adjacent code blocks are not grouped
+- Generates proper MDX JSX elements that integrate seamlessly
+
+## Performance Considerations
+
+- Uses `Set` data structures for efficient duplicate tracking
+- Processes nodes in a single AST traversal
+- Minimal memory overhead for metadata parsing
+- Parallel-friendly (no global state dependencies)
diff --git a/docs/app/docs-infra/functions/transform-markdown-demo-links/page.mdx b/docs/app/docs-infra/functions/transform-markdown-demo-links/page.mdx
new file mode 100644
index 000000000..6b243b941
--- /dev/null
+++ b/docs/app/docs-infra/functions/transform-markdown-demo-links/page.mdx
@@ -0,0 +1,237 @@
+# transformMarkdownDemoLinks
+
+A remark plugin that automatically cleans up "[See Demo]" links and horizontal rules that follow Demo components in markdown documentation. This plugin identifies Demo components (without `.Title`) and removes associated navigation elements to create cleaner documentation.
+
+## Overview
+
+Use this plugin to automatically clean up demo-related markdown patterns in your documentation. It removes redundant "[See Demo]" links that point to demo pages and optional horizontal rules that follow Demo components, streamlining the reading experience by eliminating unnecessary navigation clutter.
+
+## Key Features
+
+- **Demo component detection**: Identifies `
+
+[See Demo](/material-ui/react-button/)
+
+---
+
+More content here...
+```
+
+**After:**
+
+```markdown
+
+
+More content here...
+```
+
+### Demo Component with Properties
+
+**Before:**
+
+```markdown
+
+
+[See Demo](/material-ui/react-button/playground/)
+
+---
+
+Next section content...
+```
+
+**After:**
+
+```markdown
+
+
+Next section content...
+```
+
+### Horizontal Rule Only Cleanup
+
+Removes horizontal rules even when there's no "[See Demo]" link:
+
+**Before:**
+
+```markdown
+
+
+---
+
+Continue reading...
+```
+
+**After:**
+
+```markdown
+
+
+Continue reading...
+```
+
+## Demo Component Detection
+
+The plugin detects Demo components in various formats:
+
+**Standalone HTML:**
+
+```markdown
+
+```
+
+**Opening/Closing Tags:**
+
+```markdown
+
+ Content here
+
+```
+
+**Multiline with Properties:**
+
+```markdown
+
+```
+
+### Demo.Title Exclusion
+
+Demo components **with** `.Title` are **not** processed:
+
+**Preserved (not cleaned up):**
+
+```markdown
+Button Examples
+
+[See Demo](/material-ui/react-button/)
+
+---
+```
+
+This pattern remains unchanged because `Demo.Title` components serve a different purpose and their associated links should be preserved.
+
+## Plugin Behavior
+
+### Processing Rules
+
+1. **Demo detection**: Scans for HTML elements containing "Demo" components
+2. **Sequential processing**: Checks the next elements after each Demo component
+3. **Link identification**: Looks for paragraphs containing "[See Demo]" links
+4. **Rule removal**: Removes horizontal rules that follow Demo components
+5. **Safe removal**: Only removes elements that directly follow Demo components
+
+### Element Matching
+
+**"[See Demo]" Links:**
+
+- Must be in a paragraph element
+- Must contain text that starts with "[See Demo]"
+- Must be the immediate next element after a Demo component
+
+**Horizontal Rules:**
+
+- Standard markdown horizontal rules (`---`)
+- Must immediately follow a Demo component or "[See Demo]" link
+
+### Content Preservation
+
+The plugin preserves:
+
+- All non-Demo HTML components
+- Demo components with `.Title`
+- "[See Demo]" links not following Demo components
+- Horizontal rules not following Demo components
+- All other markdown content and structure
+
+## Common Use Cases
+
+### Documentation Cleanup
+
+Remove redundant navigation elements from documentation that already contains interactive demos.
+
+### Migration from External Demos
+
+Clean up documentation when migrating from external demo pages to embedded demos.
+
+### Streamlined Reading Experience
+
+Eliminate navigation clutter to create a more focused reading experience.
+
+## Configuration
+
+This plugin works out-of-the-box with no configuration required. It automatically detects Demo components and cleans up associated navigation elements.
+
+For more information about Demo components and how they work with the CodeHighlighter system, see the [CodeHighlighter documentation](/components/code-highlighter).
+
+## Troubleshooting
+
+### Links Not Being Removed
+
+**Problem**: "[See Demo]" links remain in the output.
+
+**Solutions**:
+
+- Ensure the link immediately follows a Demo component
+- Verify the link text starts with "[See Demo]"
+- Check that the Demo component doesn't have `.Title`
+
+### Demo Components Not Detected
+
+**Problem**: The plugin doesn't recognize Demo components.
+
+**Solutions**:
+
+- Verify the HTML element contains "Demo" in the tag name
+- Check that it's not `Demo.Title` (which is excluded)
+- Ensure proper HTML syntax in the markdown
+
+### Unexpected Content Removal
+
+**Problem**: Content is being removed that shouldn't be.
+
+**Solutions**:
+
+- Verify that removed content immediately follows a Demo component
+- Check for "[See Demo]" text in unexpected places
+- Ensure proper markdown structure
diff --git a/docs/app/docs-infra/functions/transform-markdown-relative-paths/page.mdx b/docs/app/docs-infra/functions/transform-markdown-relative-paths/page.mdx
new file mode 100644
index 000000000..e60989b67
--- /dev/null
+++ b/docs/app/docs-infra/functions/transform-markdown-relative-paths/page.mdx
@@ -0,0 +1,264 @@
+# Transform Markdown Relative Paths
+
+The `transformMarkdownRelativePaths` plugin is a Remark plugin that automatically transforms relative markdown links to work seamlessly in both development environments (VSCode, GitHub) and production builds. It strips page file extensions and converts relative paths to absolute URLs.
+
+## Features
+
+- **Page extension stripping** - Removes `/page.tsx`, `/page.jsx`, `/page.js`, `/page.mdx`, `/page.md` from URLs
+- **Relative path resolution** - Converts `./` and `../` paths to absolute URLs based on current file location
+- **Native markdown compatibility** - Works with standard markdown linking syntax
+- **Automatic integration** - Included by default when using the Next.js plugin
+
+## How It Works
+
+The plugin processes markdown links in two phases:
+
+1. **Strip page extensions** - Removes page file extensions from all URLs
+2. **Resolve relative paths** - Converts relative paths to absolute paths based on the current file's directory structure
+
+### URL Transformations
+
+| Original Link | Current File | Transformed Link |
+| ----------------------------- | ----------------------- | ----------------------- |
+| `./getting-started/page.mdx` | `/docs/page.mdx` | `/docs/getting-started` |
+| `../api-reference/page.tsx` | `/docs/guides/page.mdx` | `/docs/api-reference` |
+| `/components/page.tsx` | Any file | `/components` |
+| `../hooks/page.mdx#use-state` | `/docs/page.mdx` | `/hooks#use-state` |
+
+## Best Practices for Linking
+
+### Use Relative Links with page.mdx
+
+Always link to `page.mdx` files using relative paths for the best compatibility:
+
+1. **Use Relative Links for Internal Navigation**
+ - Link to sibling pages with `./sibling-page/page.mdx`
+ - Link to parent directory with `../page.mdx`
+ - Link to child directories with `./child/page.mdx`
+
+2. **Maintain GitHub Compatibility**
+ - All links work in GitHub's file browser
+ - VSCode navigation follows links correctly
+ - No broken links in raw markdown view
+
+3. **Keep Links Simple and Readable**
+ - Use clear, descriptive file names
+ - Organize content in logical directory structures
+ - Avoid deeply nested paths when possible
+
+4. **Avoid Links in Headers**
+ - Don't use markdown links within header text (h1-h6)
+ - Links in headers can interfere with anchor generation
+ - Use separate paragraphs below headers for navigation links
+
+5. **Use Function/Class Names as Link Text**
+ - When linking to functions or classes, use their actual names as the link text
+ - Example: [`validateInput`](../validate-input/page.mdx) not [Input Validator](../validate-input/page.mdx)
+ - This makes it clear what API you're referencing and improves searchability
+
+````markdown
+### Basic Example
+
+```markdown
+
+
+# Getting Started
+
+Welcome to our documentation! Check out the [API Reference](../api-reference/page.mdx)
+or explore our [Components](../components/page.mdx).
+
+For more detailed guides, see:
+
+- [Installation Guide](./installation/page.mdx)
+- [Configuration](./configuration/page.mdx)
+- [Troubleshooting](./troubleshooting/page.mdx)
+```
+````
+
+### Advanced Example with Fragments
+
+```markdown
+
+
+# User Guides
+
+## Quick Start
+
+Start with our [Getting Started](../getting-started/page.mdx) guide.
+
+## Advanced Topics
+
+Learn about:
+
+- [Custom Hooks](../hooks/page.mdx#custom-hooks)
+- [State Management](../state/page.mdx#management)
+- [Performance Tips](../performance/page.mdx#optimization)
+```
+
+### Directory Structure Examples
+
+Given this directory structure:
+
+```
+docs/app/
+├── components/
+│ ├── page.mdx
+│ ├── button/
+│ │ └── page.mdx
+│ └── input/
+│ └── page.mdx
+└── guides/
+ └── getting-started/
+ └── page.mdx
+```
+
+From `/components/input/page.mdx`:
+
+```markdown
+
+
+[Button](../button/page.mdx)
+
+
+
+[Components Overview](../page.mdx)
+
+
+
+[Getting Started](../../guides/getting-started/page.mdx)
+```
+
+### Why This Approach Works
+
+1. **GitHub Compatibility** - Links work when browsing the repository on GitHub
+2. **VSCode Navigation** - Click-to-navigate works in your editor
+3. **Build-time Optimization** - Plugin transforms links for production
+4. **SEO Friendly** - Clean URLs without file extensions in production
+
+## Integration
+
+### Automatic with Next.js Plugin
+
+The plugin is automatically included when using the docs-infra Next.js plugin:
+
+```javascript
+// next.config.js
+const { withDocsInfra } = require('@mui/internal-docs-infra/next');
+
+module.exports = withDocsInfra({
+ // Your Next.js config
+});
+```
+
+### Manual Integration
+
+You can also use the plugin directly with Remark:
+
+```javascript
+import { remark } from 'remark';
+import { transformMarkdownRelativePaths } from '@mui/internal-docs-infra/pipeline/transformMarkdownRelativePaths';
+
+const processor = remark().use(transformMarkdownRelativePaths);
+
+const result = await processor.process(markdownContent);
+```
+
+## Advanced Usage
+
+### With Query Parameters and Fragments
+
+The plugin preserves query parameters and URL fragments:
+
+```markdown
+
+
+[API Reference](./button/page.mdx?section=props#api-table)
+
+
+
+/components/button?section=props#api-table
+```
+
+### Nested Directory Navigation
+
+The plugin correctly handles deeply nested directory structures:
+
+```markdown
+
+
+
+
+[Components](../../page.mdx)
+
+
+
+[Select](../select/page.mdx)
+
+
+
+[Guides](../../../guides/page.mdx)
+```
+
+## Configuration
+
+The plugin works out of the box without configuration. It automatically:
+
+- Detects the current file's location
+- Resolves relative paths based on the `/app/` directory structure
+- Preserves absolute paths and external URLs unchanged
+- Maintains query parameters and URL fragments
+
+## Examples
+
+### Component Cross-References
+
+```markdown
+
+
+This component works with [`FormProvider`](../form-provider/page.mdx)
+and [`useValidation`](../use-validation/page.mdx) hook.
+
+For advanced usage, see the [Integration Guide](../../guides/integration/page.mdx).
+```
+
+### Function Documentation Links
+
+```markdown
+
+
+This function is used by [`validateForm`](../validate-form/page.mdx)
+and [`submitForm`](../submit-form/page.mdx).
+
+See the [Component examples](../../components/page.mdx) for usage.
+```
+
+### Index Page Navigation
+
+```markdown
+
+
+## Available Components
+
+- [`Button`](./button/page.mdx) - Interactive button component
+- [`Input`](./input/page.mdx) - Text input with validation
+- [`Form`](./form/page.mdx) - Form container with state management
+
+## Related Utilities
+
+- [`validateInput`](../utils/validate-input/page.mdx) - Input validation
+- [`formatData`](../utils/format-data/page.mdx) - Data formatting helper
+```
+
+## Benefits
+
+- **Developer Experience** - Natural markdown linking that works everywhere
+- **Maintainability** - Links remain valid as files move within the `/app/` structure
+- **Performance** - Clean URLs in production builds
+- **Accessibility** - Standard markdown link behavior for screen readers
+- **Version Control** - Easy to review link changes in diffs
+
+## Related
+
+- **Next.js Integration** - Automatic inclusion in Next.js builds
+- **Remark Pipeline** - Part of the markdown processing pipeline
+- **Markdown Standards** - Compatible with standard markdown linking syntax
diff --git a/docs/app/docs-infra/functions/transform-typescript-to-javascript/page.mdx b/docs/app/docs-infra/functions/transform-typescript-to-javascript/page.mdx
new file mode 100644
index 000000000..e19533132
--- /dev/null
+++ b/docs/app/docs-infra/functions/transform-typescript-to-javascript/page.mdx
@@ -0,0 +1,133 @@
+# transformTypescriptToJavascript
+
+The `transformTypescriptToJavascript` utility transforms TypeScript/TSX code into JavaScript/JSX using Babel, preserving formatting and automatically generating JavaScript variants for documentation and demos.
+
+## Features
+
+- Removes TypeScript types and decorators while preserving React JSX
+- Handles both `.ts` → `.js` and `.tsx` → `.jsx` transformations
+- Preserves blank lines and code formatting using smart newline replacement
+- Automatically formats output with Prettier (configurable)
+- Removes TypeScript-specific comments that reference removed constructs
+- Supports TSX with React components
+
+## Usage
+
+```ts
+import { transformTypescriptToJavascript } from '@mui/internal-docs-infra/pipeline/transformTypescriptToJavascript';
+
+// Transform TypeScript to JavaScript
+const result = await transformTypescriptToJavascript(
+ 'const x: number = 1;\ninterface Props { name: string; }',
+ 'example.ts',
+);
+
+// Returns:
+// {
+// js: {
+// source: 'const x = 1;\n',
+// fileName: 'example.js'
+// }
+// }
+```
+
+## Transformer Configuration
+
+The utility is also available as a source transformer for automatic processing:
+
+```ts
+import { TypescriptToJavascriptTransformer } from '@mui/internal-docs-infra/pipeline/transformTypescriptToJavascript';
+
+// Transformer configuration
+// {
+// extensions: ['ts', 'tsx'],
+// transformer: transformTypescriptToJavascript
+// }
+```
+
+## Advanced Configuration
+
+The underlying `removeTypes` function supports Prettier configuration:
+
+```ts
+import { removeTypes } from '@mui/internal-docs-infra/pipeline/transformTypescriptToJavascript/removeTypes';
+
+// With default Prettier formatting
+const formatted = await removeTypes(code, 'file.ts', true);
+
+// With custom Prettier options
+const customFormatted = await removeTypes(code, 'file.ts', {
+ singleQuote: false,
+ tabWidth: 4,
+});
+
+// Skip Prettier formatting entirely
+const unformatted = await removeTypes(code, 'file.ts', false);
+```
+
+## How It Works
+
+1. **Preserve Formatting**: Replaces multiple newlines with markers to preserve blank lines
+2. **Remove Comments**: Identifies and removes comments associated with TypeScript-specific constructs
+3. **Babel Transform**: Uses Babel with TypeScript plugin to strip types and decorators
+4. **Restore Formatting**: Replaces newline markers back to preserve original spacing
+5. **Prettier Format**: Applies consistent code formatting (unless disabled)
+
+## Supported Features
+
+### TypeScript Constructs Removed
+
+- Type annotations (`: string`, `: number`, etc.)
+- Interface declarations
+- Type aliases
+- Import type statements
+- Declare statements
+- Decorators (with legacy support)
+
+### Preserved Features
+
+- All JavaScript functionality
+- React JSX (in `.tsx` files)
+- Comments (except those tied to removed TS constructs)
+- Original code structure and formatting
+
+## File Extension Mapping
+
+- `*.ts` files → `*.js` files
+- `*.tsx` files → `*.jsx` files
+
+## Example Transformations
+
+```ts
+// Input (TypeScript)
+interface ButtonProps {
+ onClick: () => void;
+ children: React.ReactNode;
+}
+
+const Button: React.FC = ({ onClick, children }) => {
+ return {children} ;
+};
+
+// Output (JavaScript)
+const Button = ({ onClick, children }) => {
+ return {children} ;
+};
+```
+
+## When to Use
+
+- When implementing automatic TypeScript → JavaScript code generation
+- When building documentation that shows both TS and JS versions
+- When creating demos that need to support both TypeScript and JavaScript users
+- As part of source transformers in [`CodeHighlighter`](../../components/code-highlighter/page.mdx)
+
+## Related
+
+- [`CodeHighlighter`](../../components/code-highlighter/page.mdx) - Uses this transformer for variant generation
+- [Babel TypeScript Plugin](https://babeljs.io/docs/en/babel-plugin-transform-typescript) - Underlying transformation engine
+- [`SourceTransformers`](../source-transformers/page.mdx) - Framework for code transformations
+
+## Implementation Notes
+
+This implementation is based on [`ember-cli/babel-remove-types`](https://github.com/ember-cli/babel-remove-types) but converted to use Babel standalone with added TSX support for React components.
diff --git a/docs/app/docs-infra/functions/with-docs-infra/page.mdx b/docs/app/docs-infra/functions/with-docs-infra/page.mdx
new file mode 100644
index 000000000..86853cf34
--- /dev/null
+++ b/docs/app/docs-infra/functions/with-docs-infra/page.mdx
@@ -0,0 +1,230 @@
+# withDocsInfra
+
+The `withDocsInfra` function is a Next.js configuration plugin that sets up webpack loaders, Turbopack rules, and page extensions required for MUI docs infrastructure. It automatically configures code highlighting loaders for demo files and provides sensible defaults for documentation sites.
+
+## Features
+
+- Configures webpack and Turbopack loaders for demo files (`index.ts` and `client.ts`)
+- Sets up page extensions to support `.md`, `.mdx`, `.ts`, `.tsx`, `.js`, `.jsx` files
+- Enables export output mode by default for static site generation
+- Provides extensible patterns for custom demo file structures
+- Includes companion `getDocsInfraMdxOptions` for MDX plugin configuration
+
+## Basic Usage
+
+```js
+// next.config.mjs
+import { withDocsInfra } from '@mui/internal-docs-infra/withDocsInfra';
+
+const nextConfig = {
+ // Your existing Next.js config
+};
+
+export default withDocsInfra()(nextConfig);
+```
+
+## Advanced Configuration
+
+```js
+// next.config.mjs
+import { withDocsInfra, getDocsInfraMdxOptions } from '@mui/internal-docs-infra/withDocsInfra';
+import createMDX from '@next/mdx';
+
+const withMDX = createMDX(getDocsInfraMdxOptions());
+
+const nextConfig = withDocsInfra({
+ // Add support for additional file extensions
+ additionalPageExtensions: ['vue', 'svelte'],
+
+ // Disable export output if using server features
+ enableExportOutput: false,
+
+ // Add custom demo patterns beyond the defaults
+ additionalDemoPatterns: {
+ index: ['./app/**/demos/*/demo-*/index.ts'],
+ client: ['./app/**/demos/*/demo-*/client.ts'],
+ },
+
+ // Add custom Turbopack rules
+ additionalTurbopackRules: {
+ './custom/**/*.ts': {
+ loaders: ['custom-loader'],
+ },
+ },
+})({
+ // Your existing Next.js config
+});
+
+export default withMDX(nextConfig);
+```
+
+## Default Configurations
+
+### Page Extensions
+
+By default, `withDocsInfra` enables these page extensions:
+
+- `js`, `jsx` - JavaScript files
+- `md`, `mdx` - Markdown files
+- `ts`, `tsx` - TypeScript files
+
+### Demo File Patterns
+
+The plugin automatically configures loaders for these patterns:
+
+**Turbopack Rules:**
+
+```js
+{
+ './app/**/demos/*/index.ts': {
+ loaders: ['@mui/internal-docs-infra/pipeline/loadPrecomputedCodeHighlighter']
+ },
+ './app/**/demos/*/client.ts': {
+ loaders: ['@mui/internal-docs-infra/pipeline/loadPrecomputedCodeHighlighterClient']
+ }
+}
+```
+
+**Webpack Rules:**
+
+```js
+{
+ test: /\/demos\/[^\/]+\/index\.ts$/,
+ use: [
+ defaultLoaders.babel,
+ '@mui/internal-docs-infra/pipeline/loadPrecomputedCodeHighlighter'
+ ]
+}
+```
+
+## MDX Integration
+
+Use `getDocsInfraMdxOptions` to configure MDX with docs-infra plugins:
+
+```js
+import { getDocsInfraMdxOptions } from '@mui/internal-docs-infra/withDocsInfra';
+import createMDX from '@next/mdx';
+
+// Default docs-infra MDX plugins
+const withMDX = createMDX(getDocsInfraMdxOptions());
+
+// With additional plugins
+const withMDX = createMDX(
+ getDocsInfraMdxOptions({
+ additionalRemarkPlugins: [['remark-emoji']],
+ additionalRehypePlugins: [['rehype-highlight']],
+ }),
+);
+
+// With custom plugin overrides
+const withMDX = createMDX(
+ getDocsInfraMdxOptions({
+ remarkPlugins: [['remark-gfm'], ['custom-plugin']],
+ rehypePlugins: [['custom-rehype-plugin']],
+ }),
+);
+```
+
+## Default MDX Plugins
+
+### Remark Plugins (Markdown processing)
+
+- `remark-gfm` - GitHub Flavored Markdown support
+- [`transformMarkdownRelativePaths`](../transform-markdown-relative-paths/page.mdx) - Convert relative paths for docs
+- [`transformMarkdownBlockquoteCallouts`](../transform-markdown-blockquote-callouts/page.mdx) - Convert blockquotes to callouts
+- [`transformMarkdownCode`](../transform-markdown-code/page.mdx) - Process code blocks
+
+### Rehype Plugins (HTML processing)
+
+- [`transformHtmlCode`](../transform-html-code/page.mdx) - Transform HTML code elements
+
+## Configuration Options
+
+### WithDocsInfraOptions
+
+| Option | Type | Default | Description |
+| -------------------------- | ---------- | ------------------------------ | ---------------------------------------------- |
+| `additionalPageExtensions` | `string[]` | `[]` | Additional file extensions to treat as pages |
+| `enableExportOutput` | `boolean` | `true` | Enable Next.js export output mode |
+| `demoPathPattern` | `string` | `'./app/**/demos/*/index.ts'` | Pattern for demo index files |
+| `clientDemoPathPattern` | `string` | `'./app/**/demos/*/client.ts'` | Pattern for demo client files |
+| `additionalDemoPatterns` | `object` | `{}` | Additional demo patterns for custom structures |
+| `additionalTurbopackRules` | `object` | `{}` | Custom Turbopack loader rules |
+
+### DocsInfraMdxOptions
+
+| Option | Type | Description |
+| ------------------------- | ------- | ------------------------------------------ |
+| `remarkPlugins` | `Array` | Override default remark plugins completely |
+| `rehypePlugins` | `Array` | Override default rehype plugins completely |
+| `additionalRemarkPlugins` | `Array` | Add plugins to default remark plugins |
+| `additionalRehypePlugins` | `Array` | Add plugins to default rehype plugins |
+
+## Example: Custom Demo Structure
+
+If your demos follow a different pattern (e.g., `demo-variant/index.ts`):
+
+```js
+const nextConfig = withDocsInfra({
+ additionalDemoPatterns: {
+ index: ['./app/**/demos/*/demo-*/index.ts'],
+ client: ['./app/**/demos/*/demo-*/client.ts'],
+ },
+})({});
+```
+
+This will configure loaders for paths like:
+
+- `./app/components/demos/Button/demo-variant/index.ts`
+- `./app/components/demos/Button/demo-basic/client.ts`
+
+## How It Works
+
+1. **Page Extensions**: Configures Next.js to recognize additional file types as pages
+2. **Export Output**: Enables static export mode for documentation sites
+3. **Turbopack Rules**: Sets up fast refresh and bundling for demo files in development
+4. **Webpack Rules**: Configures production bundling with appropriate loaders
+5. **Pattern Conversion**: Converts glob patterns to webpack regex for file matching
+
+## Integration Example
+
+Complete Next.js configuration for a docs site:
+
+```js
+// next.config.mjs
+import { withDocsInfra, getDocsInfraMdxOptions } from '@mui/internal-docs-infra/withDocsInfra';
+import createMDX from '@next/mdx';
+
+const withMDX = createMDX(
+ getDocsInfraMdxOptions({
+ additionalRemarkPlugins: [['remark-emoji']],
+ }),
+);
+
+const nextConfig = withDocsInfra({
+ additionalDemoPatterns: {
+ index: ['./app/**/demos/*/demo-*/index.ts'],
+ client: ['./app/**/demos/*/demo-*/client.ts'],
+ },
+})({
+ // Your app-specific config
+ experimental: {
+ appDir: true,
+ },
+});
+
+export default withMDX(nextConfig);
+```
+
+## When to Use
+
+- **Required** for any Next.js app using MUI docs-infra components
+- When building documentation sites with live demos
+- When you need automatic code highlighting for demo files
+- When using MDX with docs-infra markdown transformations
+
+## Related
+
+- [`loadPrecomputedCodeHighlighter`](../load-precomputed-code-highlighter/page.mdx) - Loader for demo index files
+- [`loadPrecomputedCodeHighlighterClient`](../load-precomputed-code-highlighter-client/page.mdx) - Loader for demo client files
+- [`CodeHighlighter`](../../components/code-highlighter/page.mdx) - Component that renders highlighted demos
diff --git a/docs/app/docs-infra/hooks/page.mdx b/docs/app/docs-infra/hooks/page.mdx
new file mode 100644
index 000000000..3c528d8ea
--- /dev/null
+++ b/docs/app/docs-infra/hooks/page.mdx
@@ -0,0 +1,12 @@
+# Handler Hooks
+
+- [`useCode`](./use-code/page.mdx): A hook for rendering code blocks using the props given from [`CodeHighlighter`](../components/code-highlighter/page.mdx) component.
+- [`useDemo`](./use-demo/page.mdx): A hook for rendering demos using the props given from [`CodeHighlighter`](../components/code-highlighter/page.mdx) component.
+- [`useErrors`](./use-errors/page.mdx): A hook for managing and displaying error states in documentation components.
+
+# Helper Hooks
+
+- [`useCopier`](./use-copier/page.mdx): A hook for copying text to the clipboard.
+- [`useUrlHashState`](./use-url-hash-state/page.mdx): A hook for synchronizing component state with URL hash fragments for deep linking.
+- [`useLocalStorageState`](./use-local-storage-state/page.mdx): A hook for persisting state in localStorage with SSR support.
+- [`usePreference`](./use-preference/page.mdx): A hook for managing user preferences with localStorage persistence and SSR support.
diff --git a/docs/app/docs-infra/hooks/use-code/page.mdx b/docs/app/docs-infra/hooks/use-code/page.mdx
new file mode 100644
index 000000000..5c72179a9
--- /dev/null
+++ b/docs/app/docs-infra/hooks/use-code/page.mdx
@@ -0,0 +1,540 @@
+# useCode Hook
+
+The `useCode` hook provides programmatic access to code display, editing, and transformation functionality within `CodeHighlighter` components. It's designed for scenarios where you need fine-grained control over code behavior or want to build custom code interfaces that focus purely on code management, without component rendering.
+
+The hook implements the [Props Context Layering pattern](../../patterns/props-context-layering/page.mdx) to work seamlessly across server and client boundaries, automatically merging initial props with enhanced context values.
+
+## Overview
+
+The `useCode` hook orchestrates multiple specialized sub-hooks to provide a complete code management solution. It automatically integrates with `CodeHighlighterContext` when available, making it perfect for custom code interfaces that need to interact with the broader code highlighting system.
+
+Key features include:
+
+- **URL-aware file navigation** with automatic hash management
+- **Automatic name and slug generation** from URLs when not provided
+- **Multi-file code management** with seamless file switching
+- **Transform support** for code modifications (e.g., JS ↔ TS conversion)
+- **Clipboard integration** with copy functionality
+- **Source editing capabilities** for interactive code editing
+
+## Architecture
+
+The hook is built using a modular architecture with six specialized sub-hooks:
+
+- **useVariantSelection**: Manages code variant selection and related data
+- **useTransformManagement**: Handles code transforms and their application
+- **useFileNavigation**: Manages file selection within code variants
+- **useUIState**: Controls UI state like expansion
+- **useCopyFunctionality**: Handles clipboard operations
+- **useSourceEditing**: Manages source code editing capabilities
+
+## Basic Usage
+
+```tsx
+import { useCode } from '@mui/internal-docs-infra';
+import type { ContentProps } from '@mui/internal-docs-infra/CodeHighlighter/types';
+
+function CodeContent(props: ContentProps<{}>) {
+ const code = useCode(props, {
+ defaultOpen: true,
+ initialVariant: 'TypeScript',
+ });
+
+ return (
+
+
{code.userProps.name}
+
+ Current: {code.selectedVariant}
+ code.selectVariant(e.target.value)}>
+ {code.variants.map((variant) => (
+
+ {variant}
+
+ ))}
+
+
+
+ {/* File navigation with URL hash support */}
+ {code.files.length > 1 && (
+
+ {code.files.map((file) => (
+ code.selectFileName(file.name)}
+ className={code.selectedFileName === file.name ? 'active' : ''}
+ >
+ {file.name}
+
+ ))}
+
+ )}
+
+ {code.selectedFile}
+
+
Copy Code
+
+ );
+}
+```
+
+## API Reference
+
+### Parameters
+
+#### `contentProps: ContentProps`
+
+The content properties from your `CodeHighlighter` component - typically passed directly from props.
+
+#### `opts?: UseCodeOpts`
+
+Optional configuration object for customizing hook behavior.
+
+```tsx
+interface UseCodeOpts {
+ defaultOpen?: boolean; // Whether to start expanded
+ copy?: any; // Copy functionality options
+ githubUrlPrefix?: string; // GitHub URL prefix for links
+ codeSandboxUrlPrefix?: string; // CodeSandbox URL prefix
+ stackBlitzPrefix?: string; // StackBlitz URL prefix
+ initialVariant?: string; // Initially selected variant
+ initialTransform?: string; // Initially selected transform
+}
+```
+
+### Return Value
+
+The hook returns a `code` object with the following properties:
+
+#### Variant Management
+
+- **`variants: string[]`** - Array of available variant keys
+- **`selectedVariant: string`** - Currently selected variant key
+- **`selectVariant: React.Dispatch>`** - Function to change variant
+
+#### File Navigation
+
+- **`files: Array<{ name: string; slug?: string; component: React.ReactNode }>`** - Available files in current variant with optional URL slugs
+- **`selectedFile: React.ReactNode`** - Currently selected file component
+- **`selectedFileName: string | undefined`** - Name of currently selected file
+- **`selectFileName: (fileName: string) => void`** - Function to select a file (automatically updates URL hash)
+
+#### UI State
+
+- **`expanded: boolean`** - Whether the code view is expanded
+- **`expand: () => void`** - Function to expand the code view
+- **`setExpanded: React.Dispatch>`** - Function to set expansion state
+
+#### Copy Functionality
+
+- **`copy: (event: React.MouseEvent) => Promise`** - Function to copy code to clipboard
+
+#### Transform Management
+
+- **`availableTransforms: string[]`** - Array of available transform keys
+- **`selectedTransform: string | null | undefined`** - Currently selected transform
+- **`selectTransform: (transformName: string | null) => void`** - Function to select a transform
+
+#### Source Editing
+
+- **`setSource?: (source: string) => void`** - Function to update source code (when available)
+
+#### User Properties
+
+- **`userProps: UserProps`** - Generated user properties including name, slug, and custom props
+
+## Advanced Usage
+
+### URL Management and File Navigation
+
+The `useCode` hook automatically manages URL hashes to reflect the currently selected file. This provides deep-linking capabilities and preserves navigation state across page reloads.
+
+```tsx
+function CodeViewer(props) {
+ const code = useCode(props, {
+ initialVariant: 'TypeScript',
+ });
+
+ // File selection automatically updates URL hash without polluting browser history
+ // URLs follow pattern: #mainSlug:fileName or #mainSlug:variant:fileName
+
+ return (
+
+ {code.files.map((file) => (
+ code.selectFileName(file.name)}
+ className={code.selectedFileName === file.name ? 'active' : ''}
+ >
+ {file.name}
+
+ ))}
+
+ {/* File slug can be used for sharing or bookmarking */}
+
+ Current file URL: #{code.files.find((f) => f.name === code.selectedFileName)?.slug}
+
+
+ {code.selectedFile}
+
+ );
+}
+```
+
+### Automatic Name and Slug Generation
+
+When `name` or `slug` properties are not provided, the hook automatically generates them from the `url` property (or context URL). This is particularly useful when working with file-based demos or dynamic content.
+
+```tsx
+// Component with explicit name and slug
+
+ {/* code */}
+
+
+// Component with automatic generation from URL
+
+ {/*
+ Automatically generates:
+ - name: "Advanced Table"
+ - slug: "advanced-table"
+ */}
+
+
+function MyCodeContent(props) {
+ const code = useCode(props);
+
+ // Access generated user properties
+ console.log(code.userProps.name); // "Advanced Table"
+ console.log(code.userProps.slug); // "advanced-table"
+
+ return {/* render code */}
;
+}
+```
+
+### Accessing Transform Functionality
+
+```tsx
+function TransformSelector(props) {
+ const code = useCode(props, {
+ initialTransform: 'typescript',
+ });
+
+ return (
+
+ {code.availableTransforms.length > 0 && (
+ code.selectTransform(e.target.value || null)}
+ >
+ No Transform
+ {code.availableTransforms.map((transform) => (
+
+ {transform}
+
+ ))}
+
+ )}
+ {code.selectedFile}
+
+ );
+}
+```
+
+### Context Integration
+
+The hook automatically integrates with `CodeHighlighterContext` when used as a Content component:
+
+```tsx
+// Simple wrapper component using CodeHighlighter directly
+export function Code({ children, fileName }: { children: string; fileName?: string }) {
+ return (
+
+ {children}
+
+ );
+}
+
+// Your custom content component using useCode
+function CodeContent(props: ContentProps<{}>) {
+ // Automatically receives code from CodeHighlighter context
+ const code = useCode(props);
+
+ return (
+
+ {code.selectedFile}
+ Copy
+
+ );
+}
+
+// Usage - simple and direct
+
+ {`function hello() {
+ console.log('Hello, world!');
+}`}
+
;
+```
+
+## File Navigation
+
+When your code has multiple files, you should provide navigation between them. The recommended approach is to use conditional display - only show tabs when multiple files exist, otherwise show just the filename:
+
+> [!NOTE]
+> If you're creating demos that combine component previews with multi-file code examples, consider using [`useDemo`](../use-demo/page.mdx) instead, which handles both component rendering and file navigation.
+
+```tsx
+function CodeWithTabs(props) {
+ const code = useCode(props, { preClassName: styles.codeBlock });
+
+ return (
+
+
+ {code.files.length > 1 ? (
+
+ {code.files.map((file) => (
+ code.selectFileName(file.name)}
+ className={`${styles.tab} ${
+ code.selectedFileName === file.name ? styles.active : ''
+ }`}
+ >
+ {file.name}
+
+ ))}
+
+ ) : (
+
{code.selectedFileName}
+ )}
+
+
+
{code.selectedFile}
+
+ );
+}
+```
+
+This pattern ensures a clean user experience by avoiding unnecessary tab UI when only one file exists.
+
+## URL Hash Patterns
+
+The hook generates URL hashes for file navigation following these patterns:
+
+### Initial/Default Variant
+
+```
+#mainSlug:fileName
+# Examples:
+#button-demo:button.tsx
+#data-table:index.ts
+#advanced-form:styles.css
+```
+
+### Non-Initial Variants
+
+```
+#mainSlug:variantName:fileName
+# Examples:
+#button-demo:tailwind:button.tsx
+#data-table:typescript:index.ts
+#advanced-form:styled-components:styles.css
+```
+
+### File Naming Conventions
+
+- File names are converted to kebab-case while preserving extensions
+- Complex names like `ButtonWithTooltip.tsx` become `button-with-tooltip.tsx`
+- Special characters are replaced with dashes
+- Multiple consecutive dashes are collapsed to single dashes
+
+### URL Management Behavior
+
+- **Initial Load**: If URL contains a hash matching a file slug, that file is automatically selected
+- **File Selection**: Selecting a file updates the URL hash without adding to browser history (uses `replaceState`)
+- **Variant Changes**: When variants change, URL is updated to reflect the new variant for the current file
+- **No Auto-Hash**: URLs are only set when user explicitly selects files or when URL hash is already present
+
+## Sub-hooks Architecture
+
+The `useCode` hook is composed of several specialized sub-hooks that can be used independently:
+
+### useVariantSelection
+
+Manages variant selection logic and provides variant-related data.
+
+### useTransformManagement
+
+Handles code transforms, including delta validation and transform application.
+
+### useFileNavigation
+
+Manages file selection and navigation within code variants. Features URL hash management for deep-linking and automatic slug generation for each file.
+
+### useUIState
+
+Controls UI-related state like expansion management.
+
+### useCopyFunctionality
+
+Handles clipboard operations and copy state management.
+
+### useSourceEditing
+
+Manages source code editing capabilities when available.
+
+## Best Practices
+
+### 1. Leverage Automatic URL Generation
+
+```tsx
+// Recommended: Let the hook generate name/slug from URL
+
+ {/* Automatically gets name: "Advanced Search", slug: "advanced-search" */}
+
+
+// Override only when needed
+
+ {/* Uses custom name, but auto-generated slug: "search" */}
+
+```
+
+### 2. Handle Deep-Linking and URL States
+
+```tsx
+function CodeViewer(props) {
+ const code = useCode(props, {
+ initialVariant: 'TypeScript', // Fallback if no URL hash
+ });
+
+ // URL hash automatically handled - no manual intervention needed
+ // Users can bookmark specific files and return to them
+
+ return (
+
+ {code.files.map((file) => (
+ code.selectFileName(file.name)}
+ data-slug={file.slug} // Available for analytics or debugging
+ >
+ {file.name}
+
+ ))}
+ {code.selectedFile}
+
+ );
+}
+```
+
+### 3. Focus on Code Management
+
+```tsx
+// Recommended: Use for code-specific functionality
+function CodeSelector(props) {
+ const code = useCode(props);
+
+ return (
+
+
+ {code.selectedFile}
+
+ );
+}
+```
+
+### 4. Handle Loading States
+
+```tsx
+function SafeCodeInterface(props) {
+ const code = useCode(props);
+
+ if (!code.selectedFile) {
+ return Loading code...
;
+ }
+
+ return {code.selectedFile}
;
+}
+```
+
+### 5. Leverage Options for Initial State
+
+```tsx
+const code = useCode(props, {
+ defaultOpen: true, // Start expanded
+ initialVariant: 'TypeScript', // Pre-select variant
+ initialTransform: 'js', // Pre-apply transform
+});
+```
+
+## Performance Considerations
+
+- The hook uses extensive memoization to prevent unnecessary re-renders
+- Transform computations are cached and only recalculated when necessary
+- File navigation state is optimized for quick switching between files
+- Copy functionality includes debouncing to prevent excessive clipboard operations
+
+## Error Handling
+
+The hook includes built-in error handling for:
+
+- Invalid code structures
+- Missing transforms
+- File navigation errors
+- Copy operation failures
+
+Errors are logged to the console and the hook gracefully degrades functionality when errors occur.
+
+## Related
+
+- **[useDemo](../use-demo/page.mdx)**: For managing component rendering alongside code display - use this when you need both code and component functionality
+- **[CodeHighlighter](../../components/code-highlighter/page.mdx)**: The main component this hook is designed to work with
+
+## Troubleshooting
+
+### Transforms Not Available
+
+Transforms are only shown when they have meaningful changes. Empty transforms are automatically filtered out.
+
+### Context Not Working
+
+Ensure your component is used within a `CodeHighlighter` component that provides the necessary context.
+
+### URL Hash Not Working
+
+- Ensure your component is running in a browser environment (not SSR)
+- Check that file names in your code match the expected slug patterns
+- Verify that the URL hash matches the generated file slugs exactly
+- Use browser dev tools to inspect `code.files[].slug` values for debugging
+
+### Name/Slug Not Generated
+
+- Ensure a valid `url` property is provided to `CodeHighlighter` or available in context
+- Check that the URL follows a recognizable pattern (file paths or simple strings)
+- If URL parsing fails, provide explicit `name` and `slug` properties as fallbacks
+
+### File Navigation Issues
+
+- Ensure file names are unique within each variant
+- Check that `extraFiles` and main files don't have conflicting names
+- Verify transforms don't create duplicate file names
diff --git a/docs/app/docs-infra/hooks/use-copier/page.mdx b/docs/app/docs-infra/hooks/use-copier/page.mdx
new file mode 100644
index 000000000..cb23b7629
--- /dev/null
+++ b/docs/app/docs-infra/hooks/use-copier/page.mdx
@@ -0,0 +1,219 @@
+# useCopier
+
+The `useCopier` hook provides robust clipboard copy functionality with success state management, error handling, and customizable callbacks. It's designed for code blocks, buttons, and interactive elements that need copy-to-clipboard functionality.
+
+## Features
+
+- **Flexible content source** - Accepts static strings or dynamic functions
+- **Success state tracking** - Provides `recentlySuccessful` state for UI feedback
+- **Configurable timeout** - Customizable reset timer for success state
+- **Error handling** - Built-in error handling with optional callback
+- **Event integration** - Supports additional onClick handlers
+- **Memory safe** - Automatic cleanup of timeouts
+
+## API
+
+```typescript
+const { copy, recentlySuccessful } = useCopier(contents, options);
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+| ---------- | --------------------------------------- | ---------------------------------------------- |
+| `contents` | `string \| (() => string \| undefined)` | Static text or function returning text to copy |
+| `options` | `UseCopierOpts` | Optional configuration object |
+
+### Options
+
+| Option | Type | Default | Description |
+| ---------- | ------------------------------------------------------ | ------- | ------------------------------------ |
+| `onCopied` | `() => void` | - | Callback fired after successful copy |
+| `onError` | `(error: unknown) => void` | - | Callback fired when copy fails |
+| `onClick` | `(event: React.MouseEvent) => void` | - | Additional click handler |
+| `timeout` | `number` | `2000` | Duration (ms) to show success state |
+
+### Returns
+
+| Property | Type | Description |
+| -------------------- | ------------------------------------------------------ | ------------------------------------ |
+| `copy` | `(event: React.MouseEvent) => void` | Function to trigger copy operation |
+| `recentlySuccessful` | `boolean` | Whether copy was recently successful |
+
+## Usage Examples
+
+### Basic Copy Button
+
+```tsx
+import { useCopier } from '@mui/internal-docs-infra/useCopier';
+
+function CopyButton({ text }: { text: string }) {
+ const { copy, recentlySuccessful } = useCopier(text);
+
+ return {recentlySuccessful ? 'Copied!' : 'Copy'} ;
+}
+```
+
+### Dynamic Content
+
+```tsx
+import { useCopier } from '@mui/internal-docs-infra/useCopier';
+
+function CodeBlockCopy({ getCode }: { getCode: () => string }) {
+ const { copy, recentlySuccessful } = useCopier(getCode);
+
+ return (
+
+ {recentlySuccessful ? '✓ Copied' : 'Copy Code'}
+
+ );
+}
+```
+
+### With Error Handling
+
+```tsx
+import { useCopier } from '@mui/internal-docs-infra/useCopier';
+
+function CopyWithFeedback({ content }: { content: string }) {
+ const [error, setError] = React.useState(null);
+
+ const { copy, recentlySuccessful } = useCopier(content, {
+ onCopied: () => setError(null),
+ onError: (err) => setError('Failed to copy to clipboard'),
+ timeout: 3000,
+ });
+
+ return (
+
+ {recentlySuccessful ? 'Copied!' : 'Copy'}
+ {error && {error} }
+
+ );
+}
+```
+
+### With Custom Analytics
+
+```tsx
+import { useCopier } from '@mui/internal-docs-infra/useCopier';
+
+function AnalyticsCopyButton({ text, label }: { text: string; label: string }) {
+ const { copy, recentlySuccessful } = useCopier(text, {
+ onCopied: () => {
+ // Track successful copy events
+ analytics.track('Code Copied', { label });
+ },
+ onError: (error) => {
+ // Track copy failures
+ analytics.track('Copy Failed', { label, error: String(error) });
+ },
+ onClick: (event) => {
+ // Track all copy attempts
+ analytics.track('Copy Attempted', { label });
+ },
+ });
+
+ return (
+
+ {recentlySuccessful ? 'Copied!' : `Copy ${label}`}
+
+ );
+}
+```
+
+### Integration with Code Highlighter
+
+```tsx
+import { useCopier } from '@mui/internal-docs-infra/useCopier';
+import { CodeHighlighter } from '@mui/internal-docs-infra/CodeHighlighter';
+
+function CodeBlockWithCopy({ code, language }: { code: string; language: string }) {
+ const { copy, recentlySuccessful } = useCopier(() => {
+ // Get the current code, potentially from a transformed state
+ return getCurrentCode();
+ });
+
+ return (
+
+
+ {language}
+
+ {recentlySuccessful ? <>✓ Copied> : <>📋 Copy>}
+
+
+
+
+ );
+}
+```
+
+## How It Works
+
+1. **Content Resolution**: When `copy` is called, resolves content from string or function
+2. **Clipboard API**: Uses the `clipboard-copy` library for cross-browser compatibility
+3. **Success State**: Sets `recentlySuccessful` to `true` after successful copy
+4. **Timeout Management**: Automatically resets success state after configured timeout
+5. **Cleanup**: Clears timeouts to prevent memory leaks
+
+## State Management
+
+The hook manages internal state for copy feedback:
+
+- **Initial state**: `recentlySuccessful` is `false`
+- **On copy attempt**: State resets to `false` and clears any existing timeout
+- **On success**: State becomes `true` and starts timeout countdown
+- **After timeout**: State resets to `false`
+- **On error**: State remains `false` but error callback is fired
+
+## Error Handling
+
+Copy operations can fail for various reasons:
+
+- **Security restrictions**: Some browsers require user interaction
+- **Permission denied**: Clipboard access may be blocked
+- **Empty content**: No content to copy (function returns undefined)
+
+The hook provides the `onError` callback to handle these cases gracefully.
+
+## TypeScript Support
+
+The hook is fully typed with proper TypeScript definitions:
+
+```typescript
+export type UseCopierOpts = {
+ onCopied?: () => void;
+ onError?: (error: unknown) => void;
+ onClick?: (event: React.MouseEvent) => void;
+ timeout?: number;
+};
+
+export function useCopier(
+ contents: (() => string | undefined) | string,
+ opts?: UseCopierOpts,
+): {
+ copy: (event: React.MouseEvent) => void;
+ recentlySuccessful: boolean;
+};
+```
+
+## When to Use
+
+- **Code blocks** - Copy source code, commands, or configurations
+- **Documentation** - Copy API endpoints, package names, or examples
+- **Interactive demos** - Copy generated output or current state
+- **Forms** - Copy generated URLs, tokens, or formatted data
+- **Any UI element** - That benefits from copy-to-clipboard functionality
+
+## Browser Support
+
+Uses the `clipboard-copy` library which provides:
+
+- Modern `navigator.clipboard.writeText()` API when available
+- Fallback to `document.execCommand('copy')` for older browsers
+- Works in both secure (HTTPS) and insecure contexts
+
+## Related
+
+- [`CodeHighlighter`](../../components/code-highlighter/page.mdx) - Often used together for code copy functionality
+- [clipboard-copy](https://www.npmjs.com/package/clipboard-copy) - Underlying clipboard library
diff --git a/docs/app/docs-infra/hooks/use-demo/page.mdx b/docs/app/docs-infra/hooks/use-demo/page.mdx
new file mode 100644
index 000000000..be968e4fd
--- /dev/null
+++ b/docs/app/docs-infra/hooks/use-demo/page.mdx
@@ -0,0 +1,609 @@
+# useDemo Hook
+
+The `useDemo` hook extends `useCode` functionality to provide a complete demo rendering solution that combines component previews with code display. It's specifically designed for creating interactive demonstrations where users can see both working React components and their source code.
+
+Like `useCode`, it implements the [Props Context Layering pattern](../../patterns/props-context-layering/page.mdx) for seamless server-client compatibility.
+
+## Overview
+
+Built on top of the `useCode` hook, `useDemo` adds demo-specific functionality like name and slug management while inheriting all code management capabilities. This makes it the go-to choice for creating rich interactive demos within the `CodeHighlighter` ecosystem.
+
+**Inheritance**: Since `useDemo` extends `useCode`, it automatically includes all URL management and file navigation features, including URL hash routing for deep-linking to specific files.
+
+## Key Features
+
+- **Complete code management** via `useCode` integration with URL hash routing
+- **Component rendering** alongside code display
+- **Demo identification** with automatic name and slug generation from URLs
+- **Variant switching** for different implementation approaches
+- **Transform support** for language conversions (TypeScript to JavaScript)
+- **File navigation** with deep-linking support for multi-file demos
+
+## Basic Usage
+
+```tsx
+import { useDemo } from '@mui/internal-docs-infra';
+
+export function DemoContent(props) {
+ const demo = useDemo(props, { preClassName: styles.codeBlock });
+
+ return (
+
+ {/* Component Preview Section */}
+
{demo.component}
+
+ {/* Code Section */}
+
+
+ );
+}
+```
+
+## Advanced Usage
+
+### Full Interactive Demo Interface
+
+```tsx
+export function DemoContent(props) {
+ const demo = useDemo(props, { preClassName: styles.codeBlock });
+
+ const hasJsTransform = demo.availableTransforms.includes('js');
+ const isJsSelected = demo.selectedTransform === 'js';
+
+ const labels = { false: 'TS', true: 'JS' };
+ const toggleJs = React.useCallback(
+ (checked: boolean) => {
+ demo.selectTransform(checked ? 'js' : null);
+ },
+ [demo],
+ );
+
+ const tabs = React.useMemo(
+ () => demo.files.map(({ name }) => ({ id: name, name })),
+ [demo.files],
+ );
+
+ const variants = React.useMemo(
+ () =>
+ demo.variants.map((variant) => ({
+ value: variant,
+ label: variantNames[variant] || variant,
+ })),
+ [demo.variants],
+ );
+
+ return (
+
+ {/* Demo Preview */}
+
{demo.component}
+
+ {/* Code Section */}
+
+
+
+ {/* File Tabs */}
+
+ {demo.files.length > 1 ? (
+
+ ) : (
+ {demo.selectedFileName}
+ )}
+
+
+ {/* Actions */}
+
+
+
+ {/* Variant Selector */}
+ {demo.variants.length > 1 && (
+
+ )}
+
+ {/* Transform Toggle */}
+ {hasJsTransform && (
+
+
+
+ )}
+
+
+
+
+ {/* Code Display */}
+
{demo.selectedFile}
+
+
+ );
+}
+```
+
+### Editable Demo (Live Editing)
+
+```tsx
+export function DemoLiveContent(props) {
+ const preRef = React.useRef(null);
+ const demo = useDemo(props, { preClassName: styles.codeBlock, preRef });
+
+ const hasJsTransform = demo.availableTransforms.includes('js');
+ const isJsSelected = demo.selectedTransform === 'js';
+
+ const labels = { false: 'TS', true: 'JS' };
+ const toggleJs = React.useCallback(
+ (checked: boolean) => {
+ demo.selectTransform(checked ? 'js' : null);
+ },
+ [demo],
+ );
+
+ const tabs = React.useMemo(
+ () => demo.files.map(({ name }) => ({ id: name, name })),
+ [demo.files],
+ );
+
+ const variants = React.useMemo(
+ () =>
+ demo.variants.map((variant) => ({
+ value: variant,
+ label: variantNames[variant] || variant,
+ })),
+ [demo.variants],
+ );
+
+ // Set up editable functionality
+ const onChange = React.useCallback((text: string) => {
+ demo.setSource?.(text);
+ }, []);
+ useEditable(preRef, onChange, {
+ indentation: 2,
+ disabled: !demo.setSource,
+ });
+
+ return (
+
+ {/* Live Demo Preview */}
+
{demo.component}
+
+ {/* Editable Code Section */}
+
+
+
+
+ {demo.files.length > 1 ? (
+
+ ) : (
+ {demo.selectedFileName}
+ )}
+
+
+ {demo.variants.length > 1 && (
+
+ )}
+ {hasJsTransform && (
+
+
+
+ )}
+
+
+
+
+ {/* Editable Code Block */}
+
{demo.selectedFile}
+
+
+ );
+}
+```
+
+## API Reference
+
+### Parameters
+
+#### `contentProps: ContentProps`
+
+The content properties passed from your parent component - these should always be passed directly to `useDemo()` without accessing them directly:
+
+```tsx
+export function DemoContent(props: ContentProps<{}>) {
+ const demo = useDemo(props); // Pass props directly to useDemo
+
+ // Never access props.name, props.code, etc. directly
+ // Use demo.name, demo.selectedFile, etc. instead
+}
+```
+
+#### `opts?: UseDemoOpts`
+
+Optional configuration object:
+
+```tsx
+interface UseDemoOpts {
+ defaultOpen?: boolean; // Whether to start expanded
+ copy?: UseCopierOpts; // Copy functionality options
+ githubUrlPrefix?: string; // GitHub URL prefix for links
+ codeSandboxUrlPrefix?: string; // CodeSandbox URL prefix
+ stackBlitzPrefix?: string; // StackBlitz URL prefix
+ initialVariant?: string; // Initially selected variant
+ initialTransform?: string; // Initially selected transform
+}
+```
+
+### Return Value
+
+The hook returns all `useCode` properties plus demo-specific additions:
+
+#### Demo Properties
+
+- **`component: React.ReactNode`** - The React component for the current variant (for live preview)
+- **`ref: React.RefObject`** - Ref for the demo container element
+- **`resetFocus: () => void`** - Function to reset focus to the demo container
+- **`name: string | undefined`** - Display name for the demo (auto-generated from URL if not provided)
+- **`slug: string | undefined`** - URL-friendly identifier (auto-generated from URL if not provided)
+
+#### Inherited from useCode
+
+All properties from `useCode` are available with exact types:
+
+- **`variants: string[]`**, **`selectedVariant: string`**, **`selectVariant: React.Dispatch>`**
+- **`files: Array<{ name: string; slug?: string; component: React.ReactNode }>`** - Files with URL hash slugs for deep-linking
+- **`selectedFile: React.ReactNode`**, **`selectedFileName: string | undefined`**, **`selectFileName: (fileName: string) => void`** - File selection with automatic URL hash updates
+- **`expanded: boolean`**, **`expand: () => void`**, **`setExpanded: React.Dispatch>`**
+- **`copy: (event: React.MouseEvent) => Promise`**
+- **`availableTransforms: string[]`**, **`selectedTransform: string | null | undefined`**, **`selectTransform: (transformName: string | null) => void`**
+- **`setSource?: (source: string) => void`** (when editing is available)
+- **`userProps: UserProps`** - Generated user properties including name, slug, and custom props
+
+See [useCode documentation](../use-code/page.mdx) for detailed information about inherited functionality.
+
+## URL Management and Deep-Linking
+
+Since `useDemo` extends `useCode`, it automatically inherits all URL hash management capabilities. This enables powerful deep-linking features for demos with multiple files:
+
+### Automatic URL Hash Generation
+
+```tsx
+// Demo with URL-based name/slug generation
+const ButtonDemo = createDemo({
+ url: 'file:///components/demos/interactive-button/index.ts',
+ code: buttonCode,
+ components: { Default: ButtonComponent },
+ Content: DemoContent,
+});
+
+// Automatically generates:
+// - name: "Interactive Button"
+// - slug: "interactive-button"
+// - File URLs: #interactive-button:button.tsx, #interactive-button:styles.css
+```
+
+### Deep-Linking to Specific Files
+
+Users can bookmark and share links to specific files within your demos:
+
+```
+# Links to main file in default variant
+https://yoursite.com/demos/button#interactive-button:button.tsx
+
+# Links to specific file in TypeScript variant
+https://yoursite.com/demos/button#interactive-button:typescript:button.tsx
+
+# Links to styling file
+https://yoursite.com/demos/button#interactive-button:styles.css
+```
+
+### URL Management Behavior
+
+- **Initial Load**: If URL hash matches a file, that file is auto-selected
+- **File Selection**: Clicking file tabs updates URL without browser history pollution
+- **Variant Changes**: URL automatically updates to reflect current variant
+- **Component Integration**: Works seamlessly with demo component rendering
+
+For complete URL hash patterns and troubleshooting, see the [useCode URL Hash Patterns](../use-code/page.mdx#url-hash-patterns) section.
+
+## Integration Patterns
+
+### Standard Usage Pattern
+
+The most common pattern is to use `useDemo` in a content component that receives props from the demo factory:
+
+```tsx
+// In your demo's Content component
+export function DemoContent(props: ContentProps<{}>) {
+ const demo = useDemo(props, { preClassName: styles.codeBlock }); // Always pass props directly
+
+ return (
+
+
{demo.component}
+
+
{demo.selectedFile}
+
+ );
+}
+```
+
+### Demo Factory Integration
+
+This content component is used with the demo factory pattern, not as a direct child of `CodeHighlighter`:
+
+```tsx
+// [✓] Correct - Demo factory usage
+const ButtonDemo = createDemo({
+ name: 'Button Demo',
+ code: buttonCode,
+ components: { Default: ButtonComponent },
+ Content: DemoContent,
+});
+
+// [x] Incorrect - Never use DemoContent as a direct child
+
+ {/* This won't work */}
+ ;
+```
+
+## Best Practices
+
+### 1. Always Pass Props Directly
+
+```tsx
+export function DemoContent(props: ContentProps<{}>) {
+ const demo = useDemo(props, { preClassName: styles.codeBlock }); // [✓] Pass props directly
+
+ // [x] Never access props.name, props.code, etc.
+ // [✓] Use demo.name, demo.selectedFile, etc.
+
+ return (
+
+
{demo.component}
+
{demo.selectedFile}
+
+ );
+}
+```
+
+### 2. Conditional UI Elements
+
+```tsx
+export function DemoContent(props) {
+ const demo = useDemo(props, { preClassName: styles.codeBlock });
+
+ return (
+
+ {demo.component}
+
+ {/* Only show file tabs if multiple files */}
+ {demo.files.length > 1 ? (
+ ({ id: f.name, name: f.name }))}
+ selectedTabId={demo.selectedFileName}
+ onTabSelect={demo.selectFileName}
+ />
+ ) : (
+ {demo.selectedFileName}
+ )}
+
+ {demo.selectedFile}
+
+ );
+}
+```
+
+### 3. Simple Transform Toggle
+
+```tsx
+export function DemoContent(props) {
+ const demo = useDemo(props, { preClassName: styles.codeBlock });
+
+ const hasTransforms = demo.availableTransforms.length > 0;
+ const isJsSelected = demo.selectedTransform === 'js';
+
+ return (
+
+ {demo.component}
+
+ {hasTransforms && (
+ demo.selectTransform(isJsSelected ? null : 'js')}>
+ {isJsSelected ? 'Show TS' : 'Show JS'}
+
+ )}
+
+ {demo.selectedFile}
+
+ );
+}
+```
+
+### 4. Leverage URL-Based Demo Properties
+
+```tsx
+// Recommended: Let useDemo generate name/slug from URL
+const ButtonDemo = createDemo({
+ url: 'file:///components/demos/advanced-button/index.ts',
+ code: buttonCode,
+ components: { Default: ButtonComponent },
+ Content: DemoContent,
+});
+
+function DemoContent(props) {
+ const demo = useDemo(props);
+
+ return (
+
+
{demo.name} {/* "Advanced Button" */}
+
+ {' '}
+ {/* "advanced-button" */}
+ {demo.component}
+
+ {/* File navigation with automatic URL hash management */}
+ {demo.files.map((file) => (
+
demo.selectFileName(file.name)}
+ data-file-slug={file.slug} // For analytics/debugging
+ >
+ {file.name}
+
+ ))}
+ {demo.selectedFile}
+
+ );
+}
+```
+
+## Common Patterns
+
+### Simple Demo Display
+
+The most basic pattern for showing a component with its code:
+
+```tsx
+export function DemoContent(props) {
+ const demo = useDemo(props, { preClassName: styles.codeBlock });
+
+ return (
+
+
{demo.component}
+
{demo.selectedFile}
+
+ );
+}
+```
+
+### Demo with File Navigation
+
+When you have multiple files to show (includes automatic URL hash management):
+
+```tsx
+export function MultiFileDemoContent(props) {
+ const demo = useDemo(props, { preClassName: styles.codeBlock });
+
+ return (
+
+
{demo.component}
+
+
+ {demo.files.length > 1 && (
+
+ {/* File selection automatically updates URL hash for deep-linking */}
+ {demo.files.map((file) => (
+ demo.selectFileName(file.name)} // Updates URL hash
+ className={demo.selectedFileName === file.name ? styles.active : ''}
+ title={`View ${file.name} (URL: #${file.slug})`}
+ >
+ {file.name}
+
+ ))}
+
+ )}
+
+ {demo.selectedFile}
+
+
+ );
+}
+```
+
+### Demo with Language Toggle
+
+For demos that support TypeScript/JavaScript switching:
+
+```tsx
+export function DemoWithLanguageToggle(props) {
+ const demo = useDemo(props, { preClassName: styles.codeBlock });
+
+ const canToggleJs = demo.availableTransforms.includes('js');
+ const showingJs = demo.selectedTransform === 'js';
+
+ return (
+
+
{demo.component}
+
+
+ {canToggleJs && (
+
+ demo.selectTransform(showingJs ? null : 'js')}>
+ {showingJs ? 'TypeScript' : 'JavaScript'}
+
+
+ )}
+
+ {demo.selectedFile}
+
+
+ );
+}
+```
+
+## Performance Considerations
+
+- **Component memoization**: Components are automatically memoized by the demo factory
+- **Code lazy loading**: Inherited from `useCode` - syntax highlighting can be deferred
+- **Transform caching**: Transform results are cached for quick switching
+- **File switching**: File navigation is optimized for instant switching
+
+## Error Handling
+
+`useDemo` inherits error handling from `useCode` and adds demo-specific safeguards:
+
+- **Missing components**: Gracefully handles when components aren't available for a variant
+- **Invalid names/slugs**: Provides fallback values for missing identification
+- **Component render errors**: Use React Error Boundaries to catch component-specific issues
+
+## Troubleshooting
+
+### Component Not Rendering
+
+- Verify the component is passed in the `components` prop
+- Check that the variant name matches between `code` and `components`
+- Ensure the component doesn't have render-blocking errors
+
+### Code Not Showing
+
+- See [useCode troubleshooting](../use-code/page.mdx#troubleshooting) for code-related issues
+- Verify `contentProps` contains the expected code structure
+
+### Name/Slug Not Generated
+
+- Ensure a valid `url` property is provided in demo creation or available in context
+- Provide explicit `name` or `slug` properties as fallbacks if URL parsing fails
+- Check that the URL follows a recognizable pattern for automatic generation
+
+### URL Hash Issues
+
+- **Deep-linking not working**: See [useCode URL troubleshooting](../use-code/page.mdx#url-hash-not-working) for detailed debugging steps
+- **File navigation not updating URL**: Ensure component is running in browser environment (not SSR)
+- **Hash conflicts**: Check that demo slugs are unique across your application
+
+## Related
+
+- **[useCode](../use-code/page.mdx)**: The underlying hook for code management (see [useCode documentation](../use-code/page.mdx))
+- **[CodeHighlighter](../../components/code-highlighter/page.mdx)**: The main component that provides context for demos
+- **[abstractCreateDemo](../../functions/abstract-create-demo/page.mdx)**: Factory function for creating precomputed demos
diff --git a/docs/app/docs-infra/hooks/use-errors/page.mdx b/docs/app/docs-infra/hooks/use-errors/page.mdx
new file mode 100644
index 000000000..3266d9480
--- /dev/null
+++ b/docs/app/docs-infra/hooks/use-errors/page.mdx
@@ -0,0 +1,426 @@
+# useErrors
+
+The `useErrors` hook provides access to error state in an isomorphic error handling system. It implements the [Props Context Layering pattern](../../patterns/props-context-layering/page.mdx) to work seamlessly across server and client boundaries, making it ideal for code highlighting and interactive demos that need robust error handling.
+
+## Features
+
+- **Props Context Layering** - Implements the isomorphic pattern for React Server Components
+- **Context integration** - Accesses errors from `CodeErrorsContext`
+- **Graceful fallbacks** - Handles cases where context is unavailable
+- **Runtime error updates** - Errors can be updated dynamically via context
+- **Simple API** - Automatically merges props and context
+
+## API
+
+```typescript
+const { errors } = useErrors(props?);
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+| --------- | ---------------------- | -------------------------------------------------------------- |
+| `props` | `{ errors?: Error[] }` | Optional props containing fallback errors (typically from SSR) |
+
+### Returns
+
+| Property | Type | Description |
+| -------- | ---------------------- | ------------------------------------------------------------------- |
+| `errors` | `Error[] \| undefined` | Array of current errors (context errors take precedence over props) |
+
+## Usage Examples
+
+### Basic Error Display
+
+```tsx
+import { useErrors } from '@mui/internal-docs-infra/useErrors';
+
+function DemoErrorHandler({ errors: propErrors }: { errors?: Error[] }) {
+ const { errors } = useErrors({ errors: propErrors });
+
+ if (!errors || errors.length === 0) {
+ return null; // No errors to display
+ }
+
+ return (
+
+
Errors occurred:
+ {errors.map((error, index) => (
+
+ {error.message}
+
+ ))}
+
+ );
+}
+```
+
+### Isomorphic Error Handler
+
+```tsx
+import { useErrors } from '@mui/internal-docs-infra/useErrors';
+
+interface ErrorHandlerProps {
+ errors?: Error[]; // Props-based errors for SSR
+}
+
+function CodeErrorHandler({ errors }: ErrorHandlerProps) {
+ const { errors: resolvedErrors } = useErrors({ errors });
+
+ if (!resolvedErrors || resolvedErrors.length === 0) {
+ return An error occurred, but details were not provided.
;
+ }
+
+ return (
+
+
Error occurred when highlighting code:
+ {resolvedErrors.map((error, index) => (
+
+ {error.message}
+
+ ))}
+
+ );
+}
+```
+
+### Conditional Rendering Based on Errors
+
+```tsx
+import { useErrors } from '@mui/internal-docs-infra/useErrors';
+import { CodeHighlighter } from '@mui/internal-docs-infra/CodeHighlighter';
+
+function SafeCodeDemo({
+ code,
+ language,
+ errors: propErrors,
+}: {
+ code: string;
+ language: string;
+ errors?: Error[];
+}) {
+ const { errors } = useErrors({ errors: propErrors });
+
+ return (
+
+ {errors && errors.length > 0 ? (
+
+
Failed to render code demo:
+
+ {errors.map((error, index) => (
+ {error.message}
+ ))}
+
+
+ Raw Code
+ {code}
+
+
+ ) : (
+
+ )}
+
+ );
+}
+```
+
+### Custom Error Formatting
+
+```tsx
+import { useErrors } from '@mui/internal-docs-infra/useErrors';
+
+function FormattedErrorDisplay({ errors: propErrors }: { errors?: Error[] }) {
+ const { errors } = useErrors({ errors: propErrors });
+
+ if (!errors || errors.length === 0) {
+ return null;
+ }
+
+ const formatError = (error: Error) => {
+ // Extract useful information from error
+ const { message, name, stack } = error;
+
+ return {
+ title: name || 'Error',
+ description: message || 'An unknown error occurred',
+ details: stack?.split('\n').slice(0, 3).join('\n'), // First 3 lines of stack
+ };
+ };
+
+ return (
+
+ {errors.map((error, index) => {
+ const formatted = formatError(error);
+ return (
+
+
{formatted.title}
+
{formatted.description}
+ {formatted.details && (
+
+ Stack trace
+ {formatted.details}
+
+ )}
+
+ );
+ })}
+
+ );
+}
+```
+
+## How It Works
+
+### Props Context Layering Pattern
+
+This hook implements the [Props Context Layering pattern](../../patterns/props-context-layering/page.mdx) for React Server Components:
+
+1. **Components render before crossing client boundary**: Error handler components must render on the server before client-side props are available
+2. **Props used first, context layers on top**: Initial server-side errors come via props, client-side updates come via context
+3. **Custom hook merges props and context**: `useErrors({ errors })` automatically handles the precedence logic
+4. **Seamless server/client transition**: Users get consistent error handling regardless of where errors occur
+
+### Isomorphic Design
+
+The hook works across server-side and client-side environments:
+
+1. **Server-side rendering**: Errors are passed as props to components during SSR
+2. **Client-side updates**: Errors are provided via `CodeErrorsContext` when processing occurs
+3. **Automatic precedence**: Context errors override props errors, ensuring latest state is shown
+
+### The Problem This Solves
+
+In isomorphic applications with code highlighting and live demos, errors can occur at different layers:
+
+1. **Server-side errors**: Occur during initial server rendering (e.g., syntax parsing failures)
+2. **Client-side errors**: Occur during client-side post-processing (e.g., live demo execution, dynamic transformations)
+
+**Without `useErrors()`:** If error handlers only accept props, client-side errors appear as generic errors with no useful information.
+
+**With `useErrors()`:** Both server and client errors are handled uniformly, providing detailed error information regardless of where the error occurred.
+
+### Architectural Pattern
+
+This follows the Props Context Layering pattern for isomorphic components:
+
+```tsx
+// ❌ Props-only approach - breaks on client-side updates
+function BadErrorHandler({ errors }: { errors?: Error[] }) {
+ // Only sees server-side errors passed as props
+ // Client-side errors are lost or appear generic
+ return errors ? : null;
+}
+
+// ✅ Props Context Layering - works server and client
+function GoodErrorHandler({ errors }: { errors?: Error[] }) {
+ const { errors: resolvedErrors } = useErrors({ errors });
+
+ // Hook implements: contextErrors || props.errors
+ // Server: uses props.errors (context undefined)
+ // Client: uses context.errors (takes precedence)
+ return resolvedErrors ? : null;
+}
+
+// 🎯 Server Component - no client code needed
+function ServerOnlyErrorHandler({ errors }: { errors?: Error[] }) {
+ // When no client-side processing is needed, can stay server component
+ return errors ? : null;
+}
+```
+
+### Implementation Pattern
+
+```tsx
+'use client';
+
+import { useContext } from 'react';
+import { CodeErrorsContext } from './ErrorsContext';
+
+const useErrors = (props?: { errors?: Error[] }) => {
+ const contextErrors = useContext(CodeErrorsContext);
+ // Context takes precedence over props
+ return { errors: contextErrors?.errors || props?.errors };
+};
+
+const ErrorHandler = (props: { errors?: Error[] }) => {
+ const { errors } = useErrors(props);
+ return ;
+};
+```
+
+### Multi-Layer Error Flow
+
+1. **Component renders on server**: May encounter parsing/highlighting errors → passed as props
+2. **Component hydrates on client**: Server errors available via props
+3. **Client-side processing occurs**: New errors → updated in context via `CodeErrorsContext.Provider`
+4. **`useErrors({ errors })` provides latest state**: Context errors override props automatically, ensuring users see the most relevant error
+
+This pattern ensures that users get detailed error information regardless of whether the error occurred during:
+
+- Initial server-side code parsing
+- Client-side syntax highlighting
+- Live demo compilation
+- Dynamic code transformation
+- Runtime execution
+
+The hook handles the precedence logic internally, so error handler components simply pass their props and get back the most relevant errors.
+
+### Why This Pattern Is Essential
+
+The Props Context Layering pattern solves critical React Server Component challenges:
+
+1. **Server Component Constraints**: Components must render before crossing client boundaries
+2. **Early Rendering**: Error handlers render before client-side processing provides updated errors
+3. **Async Component Isolation**: Heavy error processing can be client-side only when needed
+4. **Seamless Updates**: Context layers client updates over server-rendered props
+
+Without this pattern, you'd need separate components for:
+
+- Server-side rendering errors
+- Client-side hydration errors
+- Runtime processing errors
+- Live demo execution errors
+
+Instead, `useErrors({ errors })` provides a unified interface that automatically handles:
+
+- **Server rendering**: Uses props.errors when context is unavailable
+- **Client updates**: Context errors override props when processing occurs
+- **Error boundaries**: Consistent error display regardless of error source
+- **Bundle optimization**: Heavy error processing stays client-side only
+
+This ensures consistent error reporting and user experience while respecting React Server Component boundaries.
+
+### Context Integration
+
+The hook connects to the `CodeErrorsContext` which is provided by components like `CodeHighlighterClient`:
+
+```tsx
+import { CodeErrorsContext } from '@mui/internal-docs-infra/useErrors/ErrorsContext';
+
+function ErrorProvider({ children, errors }: { children: React.ReactNode; errors?: Error[] }) {
+ const errorsContextValue = React.useMemo(() => ({ errors }), [errors]);
+
+ return (
+ {children}
+ );
+}
+```
+
+### Error Flow
+
+1. **Initial render**: Errors may be undefined or passed as props
+2. **Error occurrence**: Context is updated with new errors
+3. **Hook response**: `useErrors()` returns the latest error state
+4. **Component update**: UI updates to reflect current error state
+
+## Integration with CodeHighlighter
+
+The hook is commonly used with code highlighting components:
+
+```tsx
+import { useErrors } from '@mui/internal-docs-infra/useErrors';
+import { CodeHighlighterClient } from '@mui/internal-docs-infra/CodeHighlighter';
+
+function CodeDemo() {
+ return (
+
+
+
+ );
+}
+
+function CodeDemoContent() {
+ const { errors } = useErrors();
+
+ // Component has access to errors from CodeHighlighterClient context
+ return errors ? : ;
+}
+```
+
+## Error Context Type
+
+```typescript
+export interface CodeErrorsContext {
+ errors?: Error[];
+}
+
+export function useErrors(props?: { errors?: Error[] }): { errors?: Error[] } {
+ const context = useErrorsContext();
+
+ // Context errors take precedence over prop errors
+ const errors = context?.errors || props?.errors;
+
+ return { errors };
+}
+```
+
+## Best Practices
+
+### 1. Always Check for Errors
+
+```tsx
+const { errors } = useErrors({ errors: propErrors });
+
+// Always check if errors exist and have length
+if (!errors || errors.length === 0) {
+ return ;
+}
+
+return ;
+```
+
+### 2. Provide Meaningful Fallbacks
+
+```tsx
+const { errors } = useErrors({ errors: propErrors });
+
+if (errors && errors.length > 0) {
+ return (
+
+
Something went wrong. Here's the raw content:
+
{rawContent}
+
+ );
+}
+```
+
+### 3. Use in Error Boundaries
+
+```tsx
+class CodeErrorBoundary extends React.Component {
+ state = { hasError: false, error: null };
+
+ static getDerivedStateFromError(error: Error) {
+ return { hasError: true, error };
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return (
+
+
+
+ );
+ }
+
+ return this.props.children;
+ }
+}
+```
+
+## When to Use
+
+- **Code highlighting components** - Display errors when syntax highlighting fails
+- **Interactive demos** - Show errors from code execution
+- **Dynamic content rendering** - Handle errors in real-time content updates
+- **Isomorphic applications** - Need error handling that works server and client-side
+- **Error boundaries** - Provide context for error display components
+
+## Related
+
+- [**Props Context Layering**](../../patterns/props-context-layering/page.mdx) - The architectural pattern this hook implements
+- [`CodeHighlighter`](../../components/code-highlighter/page.mdx) - Uses this hook for error display
+- [`CodeErrorHandler`](../../components/code-error-handler/page.mdx) - Built-in error handler component
+- [`CodeErrorsContext`](../../contexts/code-errors-context/page.mdx) - The underlying context
diff --git a/docs/app/docs-infra/hooks/use-local-storage-state/page.mdx b/docs/app/docs-infra/hooks/use-local-storage-state/page.mdx
new file mode 100644
index 000000000..5ebfd76f5
--- /dev/null
+++ b/docs/app/docs-infra/hooks/use-local-storage-state/page.mdx
@@ -0,0 +1,297 @@
+# useLocalStorageState
+
+The `useLocalStorageState` hook provides persistent state management using localStorage with cross-tab synchronization, server-side rendering support, and a useState-like API. It's designed for user preferences, demo state, and any data that should persist across browser sessions.
+
+## Features
+
+- **localStorage persistence** - Automatically saves and loads state from localStorage
+- **Cross-tab synchronization** - State updates sync across browser tabs and windows
+- **SSR-safe** - Works with server-side rendering and hydration
+- **useState-compatible API** - Drop-in replacement for useState with persistence
+- **Function updates** - Supports functional state updates like `setState(prev => prev + 1)`
+- **Null key support** - Disables persistence when key is null (useful for conditional persistence)
+- **Initializer functions** - Lazy initialization support
+- **Error handling** - Gracefully handles localStorage unavailability
+
+## API
+
+```typescript
+const [value, setValue] = useLocalStorageState(key, initializer);
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+| ------------- | ------------------------------------------ | -------------------------------------------------- |
+| `key` | `string \| null` | localStorage key. If null, persistence is disabled |
+| `initializer` | `string \| null \| (() => string \| null)` | Initial value or function returning initial value |
+
+### Returns
+
+| Property | Type | Description |
+| ---------- | ------------------------------------------------------ | ------------------------------------------------ |
+| `value` | `string \| null` | Current value from localStorage or initial value |
+| `setValue` | `React.Dispatch>` | Function to update the value |
+
+## Usage Examples
+
+### Basic Persistence
+
+```tsx
+import useLocalStorageState from '@mui/internal-docs-infra/useLocalStorageState';
+
+function ThemeToggle() {
+ const [theme, setTheme] = useLocalStorageState('theme', () => 'light');
+
+ const toggleTheme = () => {
+ setTheme(theme === 'light' ? 'dark' : 'light');
+ };
+
+ return Current theme: {theme} (Click to toggle) ;
+}
+```
+
+### Function Updates
+
+```tsx
+import useLocalStorageState from '@mui/internal-docs-infra/useLocalStorageState';
+
+function Counter() {
+ const [count, setCount] = useLocalStorageState('counter', () => '0');
+
+ const increment = () => {
+ setCount((prev) => String(Number(prev || '0') + 1));
+ };
+
+ const decrement = () => {
+ setCount((prev) => String(Number(prev || '0') - 1));
+ };
+
+ return (
+
+
Count: {count}
+
+
+
-
+
+ );
+}
+```
+
+### Conditional Persistence
+
+```tsx
+import useLocalStorageState from '@mui/internal-docs-infra/useLocalStorageState';
+
+function UserSettings({ enablePersistence }: { enablePersistence: boolean }) {
+ // When enablePersistence is false, key is null and state isn't persisted
+ const [settings, setSettings] = useLocalStorageState(
+ enablePersistence ? 'user-settings' : null,
+ () => 'default-settings',
+ );
+
+ return (
+
+
Settings: {settings}
+
Persistence: {enablePersistence ? 'enabled' : 'disabled'}
+
setSettings('custom-settings')}>Update Settings
+
+ );
+}
+```
+
+### Demo Code Editor
+
+```tsx
+import useLocalStorageState from '@mui/internal-docs-infra/useLocalStorageState';
+
+function CodeEditor({ demoId }: { demoId: string }) {
+ const [code, setCode] = useLocalStorageState(
+ `demo-code-${demoId}`,
+ () => `// Default code for ${demoId}\nconsole.log('Hello World');`,
+ );
+
+ return (
+
+ );
+}
+```
+
+### User Preferences Panel
+
+```tsx
+import useLocalStorageState from '@mui/internal-docs-infra/useLocalStorageState';
+
+function PreferencesPanel() {
+ const [language, setLanguage] = useLocalStorageState('ui-language', () => 'en');
+ const [fontSize, setFontSize] = useLocalStorageState('font-size', () => 'medium');
+ const [autoSave, setAutoSave] = useLocalStorageState('auto-save', () => 'true');
+
+ return (
+
+
User Preferences
+
+
+ Language:
+ setLanguage(e.target.value)}>
+ English
+ Spanish
+ French
+
+
+
+
+ Font Size:
+ setFontSize(e.target.value)}>
+ Small
+ Medium
+ Large
+
+
+
+
+ setAutoSave(e.target.checked ? 'true' : 'false')}
+ />
+ Auto-save
+
+
+ );
+}
+```
+
+## How It Works
+
+### Server-Side Rendering
+
+The hook handles SSR by:
+
+1. **Server**: Returns `[null, () => {}]` - no localStorage access
+2. **Client hydration**: Uses `useSyncExternalStore` with server snapshot returning `null`
+3. **Post-hydration**: Switches to actual localStorage values
+
+This prevents hydration mismatches while providing immediate localStorage access after hydration.
+
+### Cross-Tab Synchronization
+
+```typescript
+// Internal event system for same-tab updates
+const currentTabChangeListeners = new Map void>>();
+
+// Listens to both:
+// 1. `storage` events (for other tabs)
+// 2. Custom events (for current tab)
+function subscribe(area: Storage, key: string | null, callback: () => void) {
+ const storageHandler = (event: StorageEvent) => {
+ if (event.storageArea === area && event.key === key) {
+ callback(); // Other tabs changed this key
+ }
+ };
+ window.addEventListener('storage', storageHandler);
+ onCurrentTabStorageChange(key, callback); // Same tab changes
+ // ...
+}
+```
+
+### Value Management
+
+**Setting Values:**
+
+```typescript
+// Supports both direct values and function updates
+setValue('new-value');
+setValue((prev) => `${prev}-updated`);
+
+// null removes the item and falls back to initial value
+setValue(null);
+```
+
+**Storage Operations:**
+
+- `setValue(value)` → `localStorage.setItem(key, value)`
+- `setValue(null)` → `localStorage.removeItem(key)` + fallback to initial
+- Error handling for storage quota/permissions issues
+
+### Null Key Behavior
+
+When `key` is `null`:
+
+- No localStorage operations occur
+- Hook behaves like regular `useState`
+- Useful for conditional persistence
+
+```tsx
+const [value, setValue] = useLocalStorageState(shouldPersist ? 'my-key' : null, () => 'default');
+```
+
+## Error Handling
+
+The hook gracefully handles:
+
+- **localStorage unavailable** (private browsing, disabled)
+- **Storage quota exceeded**
+- **Permission errors**
+- **Invalid JSON** (though this hook only handles strings)
+
+All errors are caught and ignored, falling back to non-persistent behavior.
+
+## TypeScript Support
+
+```typescript
+// Hook signature
+function useLocalStorageState(
+ key: string | null,
+ initializer?: string | null | (() => string | null),
+): [string | null, React.Dispatch>];
+
+// Example usage
+const [value, setValue] = useLocalStorageState('key', () => 'initial');
+// ^string | null ^React.Dispatch>
+```
+
+## Performance Considerations
+
+- **useSyncExternalStore**: Uses React 18's external store API for optimal performance
+- **Event-driven updates**: Only re-renders when localStorage actually changes
+- **Lazy initialization**: Initializer functions called only once
+- **Memory efficient**: Automatic cleanup of event listeners
+
+## Browser Support
+
+- **Modern browsers**: Full functionality with localStorage and storage events
+- **Legacy browsers**: Falls back to non-persistent useState behavior
+- **SSR environments**: Safe server-side rendering with hydration support
+
+## When to Use
+
+- **User preferences** - Theme, language, UI settings
+- **Demo state** - Code editor content, configuration
+- **Form data** - Draft content, auto-save functionality
+- **UI state** - Sidebar collapsed, tabs selected
+- **Cache** - Non-critical data that can be lost
+- **Cross-tab sync** - When state should sync across browser tabs
+
+## Limitations
+
+- **String values only** - Use JSON.stringify/parse for objects
+- **Storage quota** - localStorage has size limits (~5-10MB)
+- **Same-origin only** - Can't share data across different domains
+- **Client-side only** - No server-side persistence
+
+## Related
+
+- [`usePreference`](../use-preference/page.mdx) - Higher-level preference management
+- [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API) - Underlying browser API
diff --git a/docs/app/docs-infra/hooks/use-preference/page.mdx b/docs/app/docs-infra/hooks/use-preference/page.mdx
new file mode 100644
index 000000000..e698c1a89
--- /dev/null
+++ b/docs/app/docs-infra/hooks/use-preference/page.mdx
@@ -0,0 +1,370 @@
+# usePreference
+
+The `usePreference` hook provides specialized preference management for code demo variants and transformations. It builds on `useLocalStorageState` with intelligent storage key generation, prefix support, and automatic optimization for single-option scenarios.
+
+## Features
+
+- **Specialized for demos** - Designed for variant and transform preferences
+- **Intelligent storage** - Only persists when there are multiple options to choose from
+- **Automatic key generation** - Creates storage keys from variant/transform names
+- **Prefix support** - Configurable prefixes via PreferencesProvider
+- **localStorage persistence** - Built on useLocalStorageState for cross-tab sync
+- **Smart optimization** - Skips storage for single-option scenarios
+
+## API
+
+```typescript
+const [preference, setPreference] = usePreference(type, name, initializer);
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+| ------------- | ------------------------------------------ | ------------------------------------------------------- |
+| `type` | `'variant' \| 'transform'` | Type of preference (affects storage prefix) |
+| `name` | `string \| string[]` | Variant/transform name(s). Array gets sorted and joined |
+| `initializer` | `string \| null \| (() => string \| null)` | Initial value or function |
+
+### Returns
+
+| Property | Type | Description |
+| --------------- | ------------------------------------------------------ | ----------------------------- |
+| `preference` | `string \| null` | Current preference value |
+| `setPreference` | `React.Dispatch>` | Function to update preference |
+
+## Usage Examples
+
+### Variant Preferences
+
+```tsx
+import { usePreference } from '@mui/internal-docs-infra/usePreference';
+
+function ButtonVariantSelector() {
+ // For multiple variants - will persist to localStorage
+ const [variant, setVariant] = usePreference(
+ 'variant',
+ ['contained', 'outlined', 'text'], // Multiple options
+ () => 'contained',
+ );
+
+ return (
+
+
Current variant: {variant}
+ {['contained', 'outlined', 'text'].map((option) => (
+
setVariant(option)}
+ style={{
+ fontWeight: variant === option ? 'bold' : 'normal',
+ }}
+ >
+ {option}
+
+ ))}
+
+ );
+}
+```
+
+### Transform Preferences
+
+```tsx
+import { usePreference } from '@mui/internal-docs-infra/usePreference';
+
+function CodeLanguageSelector() {
+ const [language, setLanguage] = usePreference(
+ 'transform',
+ ['typescript', 'javascript'], // Multiple languages available
+ () => 'typescript',
+ );
+
+ return (
+
+
+ setLanguage('typescript')}
+ />
+ TypeScript
+
+
+ setLanguage('javascript')}
+ />
+ JavaScript
+
+
+ );
+}
+```
+
+### Single Option (No Persistence)
+
+```tsx
+import { usePreference } from '@mui/internal-docs-infra/usePreference';
+
+function SingleVariantDemo() {
+ // Only one variant - no localStorage persistence (key will be null)
+ const [variant, setVariant] = usePreference(
+ 'variant',
+ ['contained'], // Single option
+ () => 'contained',
+ );
+
+ // This behaves like useState since there's no choice to remember
+ return Only variant available: {variant}
;
+}
+```
+
+### With Custom Prefix
+
+```tsx
+import { usePreference, PreferencesProvider } from '@mui/internal-docs-infra/usePreference';
+
+function CustomPrefixDemo() {
+ return (
+
+
+
+ );
+}
+
+function ComponentWithPreferences() {
+ // Storage key will be: "my-app_variant:contained:outlined:text"
+ const [variant, setVariant] = usePreference(
+ 'variant',
+ ['contained', 'outlined', 'text'],
+ () => 'contained',
+ );
+
+ return (
+ setVariant(e.target.value)}>
+ Contained
+ Outlined
+ Text
+
+ );
+}
+```
+
+### Demo Configuration Panel
+
+```tsx
+import { usePreference } from '@mui/internal-docs-infra/usePreference';
+
+function DemoConfigPanel() {
+ const [size, setSize] = usePreference('variant', ['small', 'medium', 'large'], () => 'medium');
+
+ const [color, setColor] = usePreference(
+ 'variant',
+ ['primary', 'secondary', 'error'],
+ () => 'primary',
+ );
+
+ const [format, setFormat] = usePreference(
+ 'transform',
+ ['typescript', 'javascript'],
+ () => 'typescript',
+ );
+
+ return (
+
+
Demo Configuration
+
+
+ Size:
+ setSize(e.target.value)}>
+ Small
+ Medium
+ Large
+
+
+
+
+ Color:
+ setColor(e.target.value)}>
+ Primary
+ Secondary
+ Error
+
+
+
+
+ Code Format:
+ setFormat(e.target.value)}>
+ TypeScript
+ JavaScript
+
+
+
+ );
+}
+```
+
+## How It Works
+
+### Storage Key Generation
+
+The hook generates localStorage keys based on the type and name parameters:
+
+```typescript
+// For variant preferences
+// Single string name
+usePreference('variant', 'contained');
+// → Storage key: "_docs_variant_pref:contained"
+
+// Array of names (sorted and joined)
+usePreference('variant', ['outlined', 'contained', 'text']);
+// → Storage key: "_docs_variant_pref:contained:outlined:text"
+
+// For transform preferences
+usePreference('transform', ['typescript', 'javascript']);
+// → Storage key: "_docs_transform_pref:javascript:typescript"
+```
+
+### Intelligent Optimization
+
+```typescript
+const key = React.useMemo(() => {
+ if (!Array.isArray(name)) {
+ return name; // Single string always persists
+ }
+
+ if (name.length <= 1) {
+ return null; // Single option - no need to persist choice
+ }
+
+ return [...name].sort().join(':'); // Multiple options - create stable key
+}, [name]);
+```
+
+### Prefix System
+
+```typescript
+// Default prefixes
+const defaultPrefixes = {
+ variant: '_docs_variant_pref',
+ transform: '_docs_transform_pref',
+};
+
+// With custom prefix from context
+// usePreference('variant', ['a', 'b']) with prefix "my-app"
+// → Storage key: "my-app_variant:a:b"
+```
+
+### Context Integration
+
+```tsx
+export interface PreferencesContext {
+ prefix?: string;
+}
+
+// Usage
+
+
+ ;
+```
+
+## Storage Key Examples
+
+| Type | Name | Generated Key |
+| ------------------- | ----------------------- | ---------------------------------- |
+| `variant` | `'contained'` | `_docs_variant_pref:contained` |
+| `variant` | `['text', 'outlined']` | `_docs_variant_pref:outlined:text` |
+| `variant` | `['single']` | `null` (no persistence) |
+| `transform` | `['ts', 'js']` | `_docs_transform_pref:js:ts` |
+| With prefix `'app'` | `variant`, `['a', 'b']` | `app_variant:a:b` |
+
+## Integration with CodeHighlighter
+
+```tsx
+import { usePreference } from '@mui/internal-docs-infra/usePreference';
+import { CodeHighlighter } from '@mui/internal-docs-infra/CodeHighlighter';
+
+function CodeDemo({ availableVariants, availableTransforms }) {
+ const [selectedVariant, setSelectedVariant] = usePreference(
+ 'variant',
+ availableVariants,
+ () => availableVariants[0],
+ );
+
+ const [selectedTransform, setSelectedTransform] = usePreference(
+ 'transform',
+ availableTransforms,
+ () => availableTransforms[0],
+ );
+
+ return (
+
+ {/* Variant selector only shows if multiple variants */}
+ {availableVariants.length > 1 && (
+
+ )}
+
+ {/* Transform selector only shows if multiple transforms */}
+ {availableTransforms.length > 1 && (
+
+ )}
+
+
+
+ );
+}
+```
+
+## Performance Optimizations
+
+1. **Memoized key generation** - Storage key only recalculated when name changes
+2. **Conditional persistence** - Single options skip localStorage entirely
+3. **Stable array keys** - Sorted arrays ensure consistent storage keys
+4. **Built on useLocalStorageState** - Inherits cross-tab sync and SSR safety
+
+## When to Use
+
+- **Demo variant selection** - When users can choose between component variants
+- **Code transform preferences** - TypeScript/JavaScript, different syntax styles
+- **Multi-option scenarios** - Any case where users choose from multiple options
+- **Persistent UI state** - Remember user choices across sessions
+
+## When NOT to Use
+
+- **Single options** - Hook automatically optimizes this case
+- **Non-demo preferences** - Use `useLocalStorageState` directly for general preferences
+- **Complex objects** - This hook is designed for string preferences
+
+## TypeScript Support
+
+```typescript
+// Hook signature
+function usePreference(
+ type: 'variant' | 'transform',
+ name: string | string[],
+ initializer?: string | null | (() => string | null),
+): [string | null, React.Dispatch>];
+
+// Context types
+interface PreferencesContext {
+ prefix?: string;
+}
+```
+
+## Related
+
+- [`useLocalStorageState`](../use-local-storage-state/page.mdx) - Underlying persistence mechanism
+- [`PreferencesProvider`](../../components/preferences-provider/page.mdx) - Context provider for custom prefixes
+- [`CodeHighlighter`](../../components/code-highlighter/page.mdx) - Common use case for variant preferences
diff --git a/docs/app/docs-infra/hooks/use-url-hash-state/page.mdx b/docs/app/docs-infra/hooks/use-url-hash-state/page.mdx
new file mode 100644
index 000000000..2f7c3b472
--- /dev/null
+++ b/docs/app/docs-infra/hooks/use-url-hash-state/page.mdx
@@ -0,0 +1,290 @@
+# useUrlHashState
+
+The `useUrlHashState` hook provides a simple way to synchronize component state with the URL hash fragment, enabling deep linking, state persistence, and browser navigation support in documentation and demo pages.
+
+## Features
+
+- **URL Synchronization**: Automatically syncs state with the URL hash fragment
+- **Deep Linking**: Enables direct links to specific application states
+- **Browser Navigation**: Supports back/forward navigation with history API
+- **SSR Safe**: Handles server-side rendering gracefully
+- **Simple API**: Returns `[hash, setHash]` tuple similar to `useState`
+
+## API
+
+```tsx
+const [hash, setHash] = useUrlHashState();
+```
+
+### Returns
+
+A tuple containing:
+
+| Index | Type | Description |
+| ----- | ---------------------------------------------------- | ------------------------------------------- |
+| `0` | `string \| null` | Current hash value (without the '#' prefix) |
+| `1` | `(value: string \| null, replace?: boolean) => void` | Function to update hash and URL |
+
+### setHash Parameters
+
+| Parameter | Type | Default | Description |
+| --------- | ---------------- | ------- | ----------------------------------------------------------- |
+| `value` | `string \| null` | - | New hash value to set, or `null` to clear the hash |
+| `replace` | `boolean` | `true` | Whether to use `replaceState` (true) or `pushState` (false) |
+
+## Usage
+
+### Basic Usage
+
+```tsx
+import { useUrlHashState } from '@mui/internal-docs-infra/useUrlHashState';
+
+function TabNavigation() {
+ const [hash, setHash] = useUrlHashState();
+
+ return (
+
+ setHash('overview')} className={hash === 'overview' ? 'active' : ''}>
+ Overview
+
+ setHash('details')} className={hash === 'details' ? 'active' : ''}>
+ Details
+
+ setHash(null)}>Clear Hash
+
+ );
+}
+```
+
+### History Management
+
+```tsx
+function NavigationExample() {
+ const [hash, setHash] = useUrlHashState();
+
+ const goToSection = (section: string, addToHistory = false) => {
+ // Use replace=false to add entry to browser history
+ // Use replace=true (default) to replace current entry
+ setHash(section, !addToHistory);
+ };
+
+ return (
+
+ goToSection('intro', true)}>Go to Intro (new history entry)
+ goToSection('content')}>Go to Content (replace current)
+
+ );
+}
+```
+
+### Responding to Hash Changes
+
+```tsx
+function SectionNavigator() {
+ const [hash, setHash] = useUrlHashState();
+
+ // React to hash changes (including browser back/forward)
+ React.useEffect(() => {
+ if (hash) {
+ const element = document.getElementById(hash);
+ if (element) {
+ element.scrollIntoView({ behavior: 'smooth' });
+ }
+ }
+ }, [hash]);
+
+ return (
+
+
Current section: {hash || 'none'}
+
setHash('section1')}>Go to Section 1
+
setHash('section2')}>Go to Section 2
+
+ );
+}
+```
+
+## Implementation Details
+
+The hook uses the following strategy:
+
+1. **Initial Reading**: On mount, reads the current hash from `window.location.hash`
+2. **State Management**: Uses `useSyncExternalStore` to synchronize with URL changes
+3. **URL Updates**: Uses `history.replaceState()` or `history.pushState()` to update URL
+4. **Change Detection**: Listens to `hashchange` events for browser navigation
+5. **SSR Handling**: Returns `null` and skips URL operations on the server
+6. **Hash Parsing**: Automatically removes the '#' prefix from hash values
+
+## Common Patterns
+
+### Documentation Sections
+
+```tsx
+function DocumentationPage() {
+ const [hash, setHash] = useUrlHashState();
+
+ const sections = ['introduction', 'api', 'examples', 'troubleshooting'];
+ const activeSection = hash || 'introduction';
+
+ return (
+
+
+ {sections.map((section) => (
+ setHash(section)}
+ className={activeSection === section ? 'active' : ''}
+ >
+ {section}
+
+ ))}
+
+
+ {activeSection === 'introduction' && }
+ {activeSection === 'api' && }
+ {activeSection === 'examples' && }
+ {activeSection === 'troubleshooting' && }
+
+
+ );
+}
+```
+
+### Demo State Persistence
+
+```tsx
+function DemoPage() {
+ const [hash, setHash] = useUrlHashState();
+
+ // Parse hash as JSON for complex state
+ const demoState = React.useMemo(() => {
+ if (!hash) return { variant: 'default', size: 'medium' };
+ try {
+ return JSON.parse(decodeURIComponent(hash));
+ } catch {
+ return { variant: 'default', size: 'medium' };
+ }
+ }, [hash]);
+
+ const updateDemoState = (updates: Partial) => {
+ const newState = { ...demoState, ...updates };
+ setHash(encodeURIComponent(JSON.stringify(newState)));
+ };
+
+ return (
+
+
+
+
+ );
+}
+```
+
+### Modal Deep Linking
+
+```tsx
+function ModalExample() {
+ const [hash, setHash] = useUrlHashState();
+ const isModalOpen = hash === 'modal';
+
+ const openModal = () => setHash('modal');
+ const closeModal = () => setHash(null);
+
+ return (
+
+
Open Modal
+ {isModalOpen && (
+
+ Modal content - shareable URL!
+
+ )}
+
+ );
+}
+```
+
+## When to Use
+
+- **Deep Linking**: Enable direct links to specific states or sections
+- **State Persistence**: Preserve application state across page reloads
+- **Browser Navigation**: Support back/forward navigation for state changes
+- **Shareable URLs**: Create URLs that capture current application state
+- **Documentation Navigation**: Navigate between sections with persistent URLs
+- **Demo Configuration**: Persist demo settings in URL for sharing
+
+## Fragment Structure and Delimiters
+
+This hook works with any hash format, but for consistency across MUI documentation, consider using the [fragment delimiter convention](../../conventions/fragment-delimeters/page.mdx). This convention uses `:` to express hierarchy in URL fragments:
+
+```tsx
+function HierarchicalNavigation() {
+ const [hash, setHash] = useUrlHashState();
+
+ // Parse hierarchical fragments like "api:props" or "demos:button:outlined"
+ const segments = hash?.split(':') || [];
+ const [section, subsection, variant] = segments;
+
+ return (
+
+ {/* Primary navigation */}
+
+ setHash('api')} className={section === 'api' ? 'active' : ''}>
+ API
+
+ setHash('demos')} className={section === 'demos' ? 'active' : ''}>
+ Demos
+
+
+
+ {/* Secondary navigation within API section */}
+ {section === 'api' && (
+
+ setHash('api:props')}
+ className={subsection === 'props' ? 'active' : ''}
+ >
+ Props
+
+ setHash('api:methods')}
+ className={subsection === 'methods' ? 'active' : ''}
+ >
+ Methods
+
+
+ )}
+
+ {/* Demo variants */}
+ {section === 'demos' && subsection === 'button' && (
+
+ setHash('demos:button:outlined')}
+ className={variant === 'outlined' ? 'active' : ''}
+ >
+ Outlined
+
+ setHash('demos:button:contained')}
+ className={variant === 'contained' ? 'active' : ''}
+ >
+ Contained
+
+
+ )}
+
+ );
+}
+```
+
+Benefits of using structured fragments:
+
+- **Consistent navigation**: Follows established patterns across MUI docs
+- **Deep linking**: Users can link directly to specific sections and variants
+- **History behavior**: Back button navigates between major sections, not every variant change
+- **Accessibility**: Screen readers can better understand content structure
+
+## When Not to Use
+
+- **Sensitive Data**: Don't store sensitive information in URL hashes
+- **Large State Objects**: Avoid storing complex state that makes URLs unwieldy
+- **Frequently Changing State**: Don't sync rapidly changing state that would spam browser history
+- **Private State**: Use for state that's appropriate to be visible in URLs
diff --git a/docs/app/docs-infra/layout.tsx b/docs/app/docs-infra/layout.tsx
new file mode 100644
index 000000000..332d5b025
--- /dev/null
+++ b/docs/app/docs-infra/layout.tsx
@@ -0,0 +1,24 @@
+import * as React from 'react';
+import type { Metadata } from 'next';
+import Link from 'next/link';
+import styles from '../layout.module.css';
+
+export const metadata: Metadata = {
+ title: 'MUI Docs Infra Documentation',
+ description: 'How to use the MUI Docs-Infra package',
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+ MUI Docs Infra
+
+
{children}
+
+ );
+}
diff --git a/docs/app/docs-infra/page.mdx b/docs/app/docs-infra/page.mdx
new file mode 100644
index 000000000..f016e113c
--- /dev/null
+++ b/docs/app/docs-infra/page.mdx
@@ -0,0 +1,72 @@
+# Docs Infra
+
+This is the documentation for the MUI Internal Docs Infra package. It provides components and utilities for building documentation sites.
+
+You can install this package using:
+
+```bash variant=pnpm
+pnpm install @mui/internal-docs-infra
+```
+
+```bash variant=yarn
+yarn add @mui/internal-docs-infra
+```
+
+```bash variant=npm
+npm install @mui/internal-docs-infra
+```
+
+# Components
+
+These usually do not add additional HTML elements to the page, but rather provide functionality to wrap developer provided components.
+
+Some fundamental components are:
+
+- [`CodeHighlighter`](./components/code-highlighter/page.mdx): A component for displaying and rendering code snippets with syntax highlighting.
+
+See the full list of components in the [Components section](./components/page.mdx).
+
+# Hooks
+
+These are React hooks that provide client-side functionality for building documentation sites.
+
+Some fundamental hooks are:
+
+- [`useCode`](./hooks/use-code/page.mdx): A hook for rendering code blocks
+- [`useDemo`](./hooks/use-demo/page.mdx): A hook for rendering demos
+
+See the full list of hooks in the [Hooks section](./hooks/page.mdx).
+
+# Functions
+
+These are utility functions that provide functionality outside of React components.
+
+Some fundamental functions are:
+
+- [`abstractCreateDemo`](./functions/abstract-create-demo/page.mdx) - Factory utilities for creating structured demos that work with CodeHighlighter
+- [`loadPrecomputedCodeHighlighter`](./functions/load-precomputed-code-highlighter/page.mdx) - Webpack loader for build-time demo optimization
+- [`transformMarkdownCode`](./functions/transform-markdown-code/page.mdx) - Remark plugin for transforming markdown code blocks with variants into HTML structures
+- [`transformMarkdownRelativePaths`](./transform-markdown-relative-paths/page.mdx) - Remark plugin for transforming relative markdown links to absolute paths
+
+See the full list of functions in the [Functions section](./functions/page.mdx).
+
+# Patterns
+
+These are architectural patterns and best practices for building documentation infrastructure.
+
+Some key patterns are:
+
+- [`Built Factories`](./patterns/built-factories/page.mdx): A pattern for creating factory functions that use `import.meta.url` as a starting point for operations.
+- [`Props Context Layering`](./patterns/props-context-layering/page.mdx): A pattern for creating isomorphic components with React Server Components using context layering.
+
+See the full list of patterns in the [Patterns section](./patterns/page.mdx).
+
+# Contributing
+
+If you want to contribute to the MUI Docs Infra project, please read the [Contributing guide](./contributing/page.mdx).
+
+# License & Use
+
+This project is licensed under the [MIT License](https://github.com/mui/mui-public/blob/master/LICENSE).
+
+This is an internal project, and is not intended for public use. No support or stability guarantees are provided. Use at your own risk.
diff --git a/docs/app/docs-infra/patterns/built-factories/page.mdx b/docs/app/docs-infra/patterns/built-factories/page.mdx
new file mode 100644
index 000000000..9a4ba1f12
--- /dev/null
+++ b/docs/app/docs-infra/patterns/built-factories/page.mdx
@@ -0,0 +1,250 @@
+# Built Factories Pattern
+
+At its core, a factory only needs a URL. In TypeScript we can rely on `import.meta.url` as the starting point for any operation.
+
+```ts
+// src/objects/object.ts
+import { createObject } from '../createObject';
+
+export const object = createObject(import.meta.url);
+```
+
+We treat `file:///src/objects/object.ts` as the factory's input.
+By using the module URL, we can lean on filesystem-based routing.
+
+Because we defined `createObject`, we control how it works.
+We know that object names follow the pattern `file:///src/objects/(?[^/]+)\.ts`.
+From any layer (build time, server runtime, or client render), we can derive the object name `object`.
+
+We can perform any async task to produce the `object` instance: read the filesystem, query a database, or call an external API.
+
+But the index file for this object stays the same. This lets you change `createObject` without affecting potentially hundreds of index files.
+
+The index file can be copy-pasted many times; only customized factories differ.
+
+## Build
+
+During build time, a loader can access `import.meta.url` and know which object is being created. It can then inject the object into an options parameter:
+
+```ts
+// src/objects/object.ts
+import { createObject } from '../createObject';
+
+export const object = createObject(import.meta.url, { precompute: {} });
+```
+
+The loader returns this after creating the object instance. `createObject` can then skip async work and return the precomputed object.
+
+This is powerful for filesystem‑derived objects, because loaders can declare dependent files. When those change, the cache is busted.
+
+So if you had a store at `data/objects/object.json`, you could read it during build. If it changes, the object is re-created.
+
+This especially helps when objects are expensive to create.
+
+Not all objects need build-time creation. Some can't be precomputed. The index file shouldn't need to change if you later decide to create objects during a client render.
+
+## Options
+
+Factories can take options to augment behavior. For example, you might override the derived name:
+
+```ts
+// src/objects/object.ts
+import { createObject } from '../createObject';
+
+export const object = createObject(import.meta.url, { name: 'CustomObjectName' });
+```
+
+They can also accept flags:
+
+```ts
+export const object = createObject(import.meta.url, { isVisible: true });
+```
+
+Or functions:
+
+```ts
+export const object = createObject(import.meta.url, { getName: () => 'CustomObjectName' });
+```
+
+Or additional data:
+
+```ts
+export const object = createObject(import.meta.url, { data: { key: 'value' } });
+```
+
+Anything useful for creating the object can go in options.
+
+## Imported Sources
+
+Sometimes the object is created using an importable source.
+
+A simple example: a code snippet from an external module:
+
+```ts
+// src/externalModule.ts
+// This is an external function
+export const externalFunction = () => {
+ // Some implementation
+};
+```
+
+```ts
+// src/objects/object.ts
+import { createSnippet } from '../createSnippet';
+import { externalFunction } from '../externalModule';
+
+export const snippet = createSnippet(import.meta.url, externalFunction);
+```
+
+At build time the snippet could be injected:
+
+```ts
+// src/objects/object.ts
+import { createSnippet } from '../createSnippet'
+import { externalFunction } from '../externalModule'
+
+export const snippet = createSnippet(import.meta.url, externalFunction, precompute: `// This is an external function
+export const externalFunction = () => {
+ // Some implementation
+}
+`)
+```
+
+Then `createSnippet` has both the executable function and its source text.
+
+Sometimes objects have variations users can choose from, each imported as a source.
+
+```ts
+// src/objects/object.ts
+import { createSnippet } from '../createSnippet';
+import { externalFunctionA } from '../externalModuleA';
+import { externalFunctionB } from '../externalModuleB';
+
+export const snippet = createSnippet(import.meta.url, {
+ A: externalFunctionA,
+ B: externalFunctionB,
+});
+```
+
+which could be precomputed as:
+
+```ts
+// src/objects/object.ts
+import { createSnippet } from '../createSnippet';
+import { externalFunctionA } from '../externalModuleA';
+import { externalFunctionB } from '../externalModuleB';
+
+export const snippet = createSnippet(
+ import.meta.url,
+ { A: externalFunctionA, B: externalFunctionB },
+ {
+ precompute: {
+ A: `// This is an external function A
+export const externalFunctionA = () => {
+ // Some implementation
+}
+`,
+ B: `// This is an external function B
+export const externalFunctionB = () => {
+ // Some implementation
+}
+`,
+ },
+ },
+);
+```
+
+You can also add options when using imported sources:
+
+```ts
+export const snippet = createSnippet(
+ import.meta.url,
+ { A: externalFunctionA, B: externalFunctionB },
+ { stripComments: true },
+);
+```
+
+If the snippet isn't generated at build time, we still have enough info to load the code on the server or client.
+
+## Strong Typing
+
+Next.js has challenges defining TypeScript types for exports in `page.tsx` files.
+
+This pattern avoids them because it mandates a factory function that supplies types.
+
+## Centralized Configuration
+
+The actual `createObject` factory centralizes shared behavior across all objects created with it.
+
+For example:
+
+```ts
+// src/createObject.ts
+const DEBUG = true;
+
+export const createObject = (url: string, options: any) => {
+ const { object, headers } = fetch(url.replace('file:///', 'http://example.com/'));
+ if (DEBUG) {
+ return { object, headers };
+ }
+
+ return { object };
+};
+```
+
+Changing the config inside `createObject` affects all objects created with it.
+
+This is instrumental in a library that provides abstract factories:
+
+```ts
+// src/createObject.ts
+import abstractCreateObject from 'lib/abstractCreateObject';
+
+export const createObject = abstractCreateObject({
+ debug: true,
+});
+```
+
+You can migrate between an abstract factory maintained elsewhere and a custom implementation without moving the config.
+
+You can also pass one factory's result into another; each can be cached by its own dependency graph.
+
+## Aligned with Next.js App Router
+
+```
+/app/component/page.tsx <-- 'component' page
+/app/component/layout.tsx <-- 'component' layout
+/app/component/object.ts <-- 'component' object (using createObject factory)
+/app/component/snippets/simple/index.ts <-- 'component' snippet 'simple' (using createSnippet factory)
+/app/component/snippets/simple/page.tsx <-- 'component' snippet 'simple' page using ./index
+```
+
+Names come from the filesystem instead of being hardcoded twice in factory params.
+
+This pattern can extend Next.js filesystem-based routing.
+
+## Essential Implementation Notes
+
+Keep the mental model simple:
+
+**Call shape:** `createX(import.meta.url, variants?, options?)`. The URL is the identity. Variants are optional. Options are optional.
+
+**Variants:** A single component becomes the `Default` variant. An object literal lists named variants (`{ TypeScript, JavaScript }`). That's all most cases need.
+
+**One per file:** Use exactly one factory call per file. Loaders enforce this for deterministic transforms.
+
+**Precompute:** Build tooling can replace `precompute: true` (or an existing object) with generated data (`precompute: { ... }`). Server precompute injects variant code metadata; client precompute injects only an externals map. Runtime code doesn't change—only the factory implementation or loader evolves.
+
+**Options:** Pass metadata (`name`, `slug`, flags). Unknown keys are fine; they flow through. Use `skipPrecompute: true` to leave the call untouched.
+
+**Server vs Client:** A server factory (no `'use client'`) declares variants and can precompute heavy code metadata. A separate client factory file (with `'use client'`) has no variants—tooling looks at the sibling server file to know them—and only injects the externals it truly needs.
+
+**Composition:** You can expose a lightweight client wrapper as a variant inside the server factory to strictly control what reaches the client bundle.
+
+**Benefit:** Precomputation removes runtime syntax highlighting + dependency resolution cost, shrinking client work.
+
+These basics are enough to adopt the pattern. For implementation details, see the related function docs above.
+
+### Server / Client Boundary Constraint
+
+A single factory call runs entirely on either the server or the client—never both. If you need specific imports or data to ship to the client bundle you must define a _separate_ client factory file (with `'use client'`). The server factory can reference that client factory (e.g. as a variant or option), but it cannot implicitly “bridge” code across the boundary. This explicit duplication (one factory per boundary) guarantees predictable bundle contents.
diff --git a/docs/app/docs-infra/patterns/page.mdx b/docs/app/docs-infra/patterns/page.mdx
new file mode 100644
index 000000000..0c142846e
--- /dev/null
+++ b/docs/app/docs-infra/patterns/page.mdx
@@ -0,0 +1,4 @@
+# Patterns
+
+- [`Built Factories`](./built-factories/page.mdx): A pattern for creating factory functions that use `import.meta.url` as a starting point for operations.
+- [`Props Context Layering`](./props-context-layering/page.mdx): A pattern for creating isomorphic components with React Server Components using context layering.
diff --git a/docs/app/docs-infra/patterns/props-context-layering/page.mdx b/docs/app/docs-infra/patterns/props-context-layering/page.mdx
new file mode 100644
index 000000000..a7db196da
--- /dev/null
+++ b/docs/app/docs-infra/patterns/props-context-layering/page.mdx
@@ -0,0 +1,301 @@
+# Props Context Layering
+
+**Purpose**: Enable isomorphic components to work seamlessly with React Server Components while maintaining a single, ergonomic API.
+
+**Core Strategy**: Render early with initial props, then progressively enhance via context when additional data/functions become available on the client.
+
+This pattern solves a fundamental challenge: components need to render before the server-client boundary is crossed, but the complete data they need might only be available after client-side processing.
+
+## Server-Client Boundary Constraints
+
+A key driver for this pattern is React Server Components' serialization limitations:
+
+**Cannot pass across server-client boundary:**
+
+- Functions (event handlers, utilities, transformers)
+- Class instances (complex objects, parsed data structures)
+- Non-serializable values (Date objects, Map/Set, symbols)
+
+**Can pass across server-client boundary:**
+
+- Primitive values (strings, numbers, booleans)
+- Plain objects and arrays
+- **React Nodes** (pre-rendered JSX elements)
+
+**Example of the constraint:**
+
+```tsx
+// This won't work - functions can't serialize
+ console.log(data)}
+ parser={parseComplexData}
+/>
+
+// This works - React Nodes can serialize
+
+
+
+
+```
+
+**Why Props Context Layering helps:**
+
+- **Props**: Carry serializable data and pre-rendered React Nodes from server
+- **Context**: Provide functions and complex objects on the client side
+- **Result**: Same API works in both environments without serialization issues
+
+---
+
+## 1. Early rendering with fallback values
+
+Components must render on the server before crossing the client boundary. When users pass components as props to server components, those child components are immediately rendered—often before all ideal props are available.
+
+**The challenge**: You need to display something useful immediately while waiting for enhanced data.
+
+**The solution**: Accept whatever props are available initially, then seamlessly upgrade via context without changing the component's API.
+
+**Implementation pattern**: Create a custom hook that merges props (immediate) with context (enhanced) values.
+
+## 2. Conditional async operations
+
+**The problem**: The same component may run in either server or client environments. Async server components will throw errors when executed on the client.
+
+**The solution**: Guard async operations behind conditions: only execute them when the data is actually needed and the environment supports it.
+
+**When to defer async work**:
+
+- Props already contain the required data → skip async fetching
+- Heavy functions are missing → assume they'll be provided later via context
+- `forceClient` flag is set → defer to `useEffect` instead of server async
+
+**Example scenarios**:
+
+```tsx
+// IsomorphicData decides whether to fetch on the server or defer to the client.
+// 1. If data already provided -> render immediately.
+// 2. If data missing & we can run async on the server (not forceClient) -> render an async loader.
+// 3. Otherwise -> return a client component that will load data after hydration.
+
+// Synchronous decision component (never async itself)
+function IsomorphicData({ data, url, forceClient = false }) {
+ if (data) return ; // Already have data
+ if (!forceClient && url) return ; // Let server do async
+ return ; // Defer to client
+}
+
+// Async server-capable loader (can be an RSC async component)
+async function ServerDataLoader({ url }) {
+ const res = await fetch(url);
+ const fetched = await res.json();
+ return ;
+}
+
+// Client-only loader (separate module/file marked with 'use client')
+// File: ClientDataLoader.tsx
+// 'use client';
+import React, { useEffect, useState } from 'react';
+
+function ClientDataLoader({ initialData, url }) {
+ const [loaded, setLoaded] = useState(initialData);
+
+ useEffect(() => {
+ if (!loaded && url) {
+ fetch(url)
+ .then((r) => r.json())
+ .then(setLoaded)
+ .catch(() => {}); // swallow / handle errors as desired
+ }
+ }, [loaded, url]);
+
+ return ;
+}
+
+// Usage - the same props API; behavior chosen automatically
+ ;
+```
+
+Heavy functions can be provided either as props (server-side) or context (client-side). When using the [Built Factories pattern](../built-factories/page.mdx), expensive operations can even run at build time with caching.
+
+## 3. Props-first, context-enhanced hooks
+
+**The pattern**: Create hooks that accept props and automatically layer context updates on top. This hides the complexity from consumers while enabling progressive enhancement.
+
+**Real-world example** from this codebase:
+
+```ts
+// useErrors hook - implements Props → Context Layering
+function useErrors(props?: { errors?: Error[] }) {
+ const context = useErrorsContext();
+
+ // Context errors override props errors (latest wins)
+ const errors = context?.errors || props?.errors;
+
+ return { errors };
+}
+
+// Usage in components
+function ErrorHandler({ errors }: { errors?: Error[] }) {
+ const { errors: effectiveErrors } = useErrors({ errors });
+
+ if (!effectiveErrors?.length) return null;
+ return ;
+}
+```
+
+**Another example** - the `useCode` hook:
+
+```ts
+function useCode(contentProps, opts) {
+ const context = useCodeHighlighterContextOptional();
+
+ // Context code overrides contentProps code when available
+ const effectiveCode = context?.code || contentProps.code || {};
+
+ // Context URL overrides contentProps URL
+ const effectiveUrl = context?.url || contentProps.url;
+
+ // ... rest of implementation
+}
+```
+
+**Benefits**:
+
+- **Server components** can pass data via props normally
+- **Client components** get enhanced data via context automatically
+- **Same API** works in both environments
+- **No mental overhead** for consumers—they just pass props
+
+**Component flexibility**: The same component can perform their async task as either a server or client component:
+
+```ts
+// Server component version (no client code needed)
+function ObjectHandler({ object }: { object: any }) {
+ return ;
+}
+
+// Client component version (with context enhancement)
+'use client';
+function ObjectHandler({ object }: { object: any }) {
+ const { object: effectiveObject } = useObject({ object });
+ return ;
+}
+```
+
+Both preserve the same API shape and timing semantics.
+
+## 4. Lazy-load heavy functions
+
+**The goal**: Avoid shipping expensive functions to the client unless actually needed.
+
+**The strategy**:
+
+- Heavy functions are **imported conditionally**—if not imported, they're not bundled
+- Provide them via props (server-side) or context (client-side) only when needed
+- Keep the core component logic lightweight
+
+**Example**:
+
+```tsx
+// Three deployment modes for heavy functions (parsers, transformers, etc.)
+
+// 1. SERVER RENDERING: Import and pass directly as props (never reaches client bundle).
+// File: ServerPage.tsx (React Server Component)
+import 'server-only';
+
+import { expensiveParser, complexTransformer } from './heavyUtils';
+
+export function ServerRenderedFeature({ source }) {
+ return ;
+}
+
+// 2. CLIENT RENDERING: Provide lazily through a Provider that only loads after hydration.
+// File: HeavyFunctionsProvider.tsx
+('use client');
+import React, { useCallback, useState, useEffect } from 'react';
+
+const HeavyFunctionsContext = React.createContext(null);
+export function useHeavyFunctions() {
+ return React.useContext(HeavyFunctionsContext);
+}
+
+export function HeavyFunctionsProvider({ children, preload = false }) {
+ const [fns, setFns] = useState(null);
+
+ const ensureLoaded = useCallback(async () => {
+ if (!fns) {
+ const mod = await import('./heavyUtils'); // code-split boundary
+ setFns({ parser: mod.expensiveParser, transform: mod.complexTransformer });
+ }
+ }, [fns]);
+
+ // Optional eager load (e.g., user interacted or heuristic)
+ useEffect(() => {
+ if (preload) ensureLoaded();
+ }, [preload, ensureLoaded]);
+
+ return (
+
+ {children}
+
+ );
+}
+
+// Usage inside a client component
+function ClientFeature({ source }) {
+ const heavy = useHeavyFunctions();
+ // Trigger load only if user expands advanced panel, etc.
+ const onDemand = () => heavy?.ensureLoaded();
+ const parsed = heavy?.parser ? heavy.parser(source) : null; // fallback UI until ready
+ return ;
+}
+
+// 3. BUILD-TIME RENDERING (no runtime import):
+// In a build script or static generation step you run heavy logic once and serialize results.
+// File: build/generate-data.ts (executed at build time, not bundled for runtime)
+import { expensiveParser, complexTransformer } from '../src/heavyUtils';
+import fs from 'node:fs';
+const raw = fs.readFileSync('content.txt', 'utf8');
+const parsed = complexTransformer(expensiveParser(raw));
+fs.writeFileSync('dist/precomputed.json', JSON.stringify(parsed));
+
+// File: PrecomputedFeature.tsx (RSC or client) - ONLY loads JSON, not heavy functions.
+import precomputed from '../../dist/precomputed.json';
+export function PrecomputedFeature() {
+ return ; // heavy functions never shipped
+}
+```
+
+**Tip - Works with Built Factories**: This build-time path composes directly with the [Built Factories pattern](../built-factories/page.mdx). Instead of hand‑writing a separate script you can let a factory call (`createX(import.meta.url, variants?, options?)`) produce and cache the heavy result via a `precompute` injection. Tooling (loader / build step) replaces the original call with one that includes `precompute: { ... }`, so runtime code:
+
+- Keeps the one-line factory contract (identity = `import.meta.url`)
+- Ships only the precomputed data object (no parser / transformer code)
+- Lets a server factory precompute rich metadata while a sibling client factory only receives the minimal externals map
+- Still supports progressive enhancement: props carry precomputed output early, context can re-load heavy functions later (client provider with dynamic `import()`) for live editing or re-parsing
+- Avoids dynamic `import()` entirely when live mutation isn't needed; add it back only in the client enhancement path
+
+If requirements change (need variants, extra metadata, live editing), you update the shared factory implementation—call sites and component APIs stay stable, and Props Context layering continues to deliver the upgraded client experience without breaking server renders.
+
+**Outcome**: Minimal initial bundle, rich functionality loads on-demand.
+
+---
+
+## Implementation Checklist
+
+When implementing Props → Context Layering:
+
+- **Create a merging hook** that accepts props and checks context
+- **Context values override props** (latest data wins)
+- **Handle undefined context gracefully** (server/client compatibility)
+- **Guard async operations** behind conditions
+- **Heavy functions via dynamic imports** + context providers
+- **Same component API** works in server and client environments
+- **Progressive enhancement** without breaking changes
+
+## Real-World Usage
+
+This pattern is used throughout the docs-infra system:
+
+- **[`useErrors`](../../hooks/use-errors/page.mdx)**: Server-side syntax errors → client-side runtime errors
+- **[`useCode`](../../hooks/use-code/page.mdx)**: Static code props → dynamic context code
+- **[`useDemo`](../../hooks/use-demo/page.mdx)**: Build-time demos → interactive client demos
+- **[`CodeHighlighter`](../../components/code-highlighter/page.mdx)**: Server highlighting → client enhancement
diff --git a/docs/app/layout.module.css b/docs/app/layout.module.css
new file mode 100644
index 000000000..0b3593047
--- /dev/null
+++ b/docs/app/layout.module.css
@@ -0,0 +1,105 @@
+.body {
+ font-family: var(--font-geist-sans);
+ margin: 0;
+ margin-bottom: 20px;
+ background: color(display-p3 0.995 0.988 0.996);
+ background: #fefcfe;
+}
+
+.container {
+ max-width: 792px;
+ margin: 0 auto;
+ padding: 0 20px 12px 20px;
+}
+
+.header {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 60px;
+ background-color: color(display-p3 0.983 0.971 0.993);
+ background-color: #fbf7fe;
+ border-bottom: 1px solid color(display-p3 0.86 0.774 0.942);
+ border-bottom: 1px solid #e0c4f4;
+}
+
+.header a {
+ font-size: 1.5rem;
+ font-weight: bold;
+ color: color(display-p3 0.234 0.132 0.363);
+ color: #402060;
+ text-decoration: none;
+}
+
+.body code {
+ font-family: var(--font-geist-mono);
+ font-size: 14px;
+ background: color(display-p3 0.995 0.971 0.974);
+ background: #fff7f8;
+}
+
+.body code {
+ color: color(display-p3 0.728 0.211 0.311);
+ color: #ca244d;
+ text-decoration-color: color(display-p3 0.728 0.211 0.311);
+ text-decoration-color: #ca244d;
+}
+
+.body a:has(code) {
+ text-decoration-color: color(display-p3 0.728 0.211 0.311);
+ text-decoration-color: #ca244d;
+}
+
+.body a:visited code {
+ background: color(display-p3 0.966 0.983 0.964);
+ background: #f5fbf5;
+ color: color(display-p3 0.263 0.488 0.261);
+ color: #2a7e3b;
+}
+
+.body a:visited:has(code) {
+ text-decoration-color: color(display-p3 0.263 0.488 0.261);
+ text-decoration-color: #2a7e3b;
+}
+
+.body a {
+ color: color(display-p3 0.15 0.44 0.84);
+ color: #0d74ce;
+}
+
+.body a:visited {
+ color: color(display-p3 0.473 0.281 0.687);
+ color: #8145b5;
+}
+
+.body {
+ color: color(display-p3 0.128 0.122 0.147);
+ color: #211f26;
+}
+
+.body h1,
+.body h2,
+.body h3,
+.body h4,
+.body h5,
+.body h6 {
+ color: color(display-p3 0.473 0.281 0.687);
+ color: #8145b5;
+}
+
+.body h1 > a,
+.body h2 > a,
+.body h3 > a,
+.body h4 > a,
+.body h5 > a,
+.body h6 > a {
+ color: color(display-p3 0.473 0.281 0.687);
+ color: #8145b5;
+ text-decoration-color: color(display-p3 0.473 0.281 0.687);
+ text-decoration-color: #8145b5;
+}
+
+.body pre > code {
+ color: initial;
+ background: initial;
+}
diff --git a/docs/app/layout.tsx b/docs/app/layout.tsx
new file mode 100644
index 000000000..fec2e5c06
--- /dev/null
+++ b/docs/app/layout.tsx
@@ -0,0 +1,33 @@
+import * as React from 'react';
+import type { Metadata } from 'next';
+import { Geist, Geist_Mono } from 'next/font/google';
+import styles from './layout.module.css';
+
+const geistSans = Geist({
+ variable: '--font-geist-sans',
+ subsets: ['latin'],
+});
+
+const geistMono = Geist_Mono({
+ variable: '--font-geist-mono',
+ subsets: ['latin'],
+});
+
+export const metadata: Metadata = {
+ title: 'MUI Infra Documentation',
+ description: 'How to use the MUI Infra packages',
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/docs/components/Blockquote/Blockquote.module.css b/docs/components/Blockquote/Blockquote.module.css
new file mode 100644
index 000000000..b8f5537d7
--- /dev/null
+++ b/docs/components/Blockquote/Blockquote.module.css
@@ -0,0 +1,67 @@
+.root {
+ color: #211f26;
+ border-left: 4px solid #d0cdd7;
+ padding: 8px 16px;
+ margin: 0 0 16px 0;
+}
+
+.root p {
+ margin: 0;
+}
+
+.root .title {
+ font-weight: 600;
+ margin-bottom: 8px;
+}
+
+.title > svg {
+ width: 16px;
+ height: 16px;
+ margin-right: 8px;
+ vertical-align: -2px;
+}
+
+.root[data-callout-type='note'] {
+ border-left-color: color(display-p3 0.247 0.556 0.969);
+ border-left-color: #0090ff;
+}
+.root[data-callout-type='note'] .title {
+ color: color(display-p3 0.15 0.44 0.84);
+ color: #0d74ce;
+}
+
+.root[data-callout-type='tip'] {
+ border-left-color: color(display-p3 0.38 0.647 0.378);
+ border-left-color: #46a758;
+}
+.root[data-callout-type='tip'] .title {
+ color: color(display-p3 0.263 0.488 0.261);
+ color: #2a7e3b;
+}
+
+.root[data-callout-type='important'] {
+ border-left-color: color(display-p3 0.523 0.318 0.751);
+ border-left-color: #8e4ec6;
+}
+.root[data-callout-type='important'] .title {
+ color: color(display-p3 0.473 0.281 0.687);
+ color: #8145b5;
+}
+
+.root[data-callout-type='warning'] {
+ border-left-color: color(display-p3 1 0.92 0.22);
+ border-left-color: #ffe629;
+}
+.root[data-callout-type='warning'] .title {
+ color: color(display-p3 0.6 0.44 0);
+ color: #9e6c00;
+}
+
+.root[data-callout-type='caution'] {
+ border-left-color: color(display-p3 0.831 0.345 0.231);
+ border-left-color: #e54d2e;
+}
+.root[data-callout-type='caution'] .title {
+ color: color(display-p3 0.755 0.259 0.152);
+ color: #d13415;
+}
diff --git a/docs/components/Blockquote/Blockquote.tsx b/docs/components/Blockquote/Blockquote.tsx
new file mode 100644
index 000000000..830eb0215
--- /dev/null
+++ b/docs/components/Blockquote/Blockquote.tsx
@@ -0,0 +1,74 @@
+import * as React from 'react';
+import styles from './Blockquote.module.css';
+
+type BlockquoteProps = {
+ children: React.ReactNode;
+ [key: string]: unknown; // Allow additional props
+};
+
+const svg: Record = {
+ note: (
+
+
+
+ ),
+ tip: (
+
+
+
+ ),
+ important: (
+
+
+
+ ),
+ warning: (
+
+
+
+ ),
+ caution: (
+
+
+
+ ),
+};
+
+export default function Blockquote(props: BlockquoteProps) {
+ const { children, ...otherProps } = props;
+
+ let calloutType =
+ typeof props['data-callout-type'] === 'string' ? props['data-callout-type'] : undefined;
+ const icon = calloutType && svg[calloutType];
+
+ if (calloutType) {
+ calloutType = `${calloutType.charAt(0).toUpperCase()}${calloutType.slice(1)}`;
+ }
+
+ return (
+
+ {calloutType && (
+
+ {icon}
+ {calloutType}
+
+ )}
+ {children}
+
+ );
+}
diff --git a/docs/components/Blockquote/index.ts b/docs/components/Blockquote/index.ts
new file mode 100644
index 000000000..f5825f9d8
--- /dev/null
+++ b/docs/components/Blockquote/index.ts
@@ -0,0 +1 @@
+export { default as Blockquote } from './Blockquote';
diff --git a/docs/components/Checkbox/index.module.css b/docs/components/Checkbox/index.module.css
new file mode 100644
index 000000000..784751c84
--- /dev/null
+++ b/docs/components/Checkbox/index.module.css
@@ -0,0 +1,61 @@
+.checkbox {
+ position: relative;
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+}
+
+.checkbox input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+
+.checkbox .checkmark {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 20px;
+ height: 20px;
+ background-color: #eee;
+ border-radius: 4px;
+ border: 2px solid #ddd;
+ transition: all 0.2s ease;
+}
+
+.checkbox input:checked + .checkmark {
+ background-color: #9a5cd0;
+ border-color: #9a5cd0;
+}
+
+.checkbox .checkmark:after {
+ content: '';
+ position: absolute;
+ display: none;
+}
+
+.checkbox input:checked + .checkmark:after {
+ display: block;
+}
+
+.checkbox .checkmark:after {
+ left: 7px;
+ top: 3px;
+ width: 4px;
+ height: 9px;
+ border: solid white;
+ border-width: 0 2px 2px 0;
+ transform: rotate(45deg);
+}
+
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
diff --git a/docs/components/Checkbox/index.tsx b/docs/components/Checkbox/index.tsx
new file mode 100644
index 000000000..722154fb4
--- /dev/null
+++ b/docs/components/Checkbox/index.tsx
@@ -0,0 +1,35 @@
+'use client';
+
+import * as React from 'react';
+import styles from './index.module.css';
+
+type CheckboxProps = {
+ defaultChecked: boolean;
+ name?: string;
+ className?: string;
+ style?: React.CSSProperties;
+};
+
+// This component mainly serves as a mock for a Checkbox component used in demos.
+
+export function Checkbox({ defaultChecked, name = 'checkbox', className, style }: CheckboxProps) {
+ const [checked, setChecked] = React.useState(defaultChecked);
+ const onChange = React.useCallback(() => {
+ setChecked((prev) => !prev);
+ }, []);
+
+ return (
+
+
+
+ Checkbox
+
+ );
+}
diff --git a/docs/components/Code/index.ts b/docs/components/Code/index.ts
new file mode 100644
index 000000000..41abdcbb0
--- /dev/null
+++ b/docs/components/Code/index.ts
@@ -0,0 +1,2 @@
+// dog-food the demo content
+export * from '../../app/docs-infra/components/code-highlighter/demos/Code';
diff --git a/docs/components/CodeContent/index.ts b/docs/components/CodeContent/index.ts
new file mode 100644
index 000000000..bb68605f9
--- /dev/null
+++ b/docs/components/CodeContent/index.ts
@@ -0,0 +1,2 @@
+// dog-food the demo content
+export * from '../../app/docs-infra/components/code-highlighter/demos/CodeContent';
diff --git a/docs/components/CopyButton/CopyButton.module.css b/docs/components/CopyButton/CopyButton.module.css
new file mode 100644
index 000000000..14c50de17
--- /dev/null
+++ b/docs/components/CopyButton/CopyButton.module.css
@@ -0,0 +1,25 @@
+.copyButton {
+ background: none;
+ border: none;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 6px;
+ border-radius: 12px;
+ color: #0400119c;
+ transition: background-color 0.2s ease;
+}
+
+.copyButton:hover:not(:disabled) {
+ background-color: #30004010;
+}
+
+.copyButton:active:not(:disabled) {
+ background-color: #20003820;
+}
+
+.copyButton:disabled {
+ cursor: default;
+ color: #4caf50;
+}
diff --git a/docs/components/CopyButton/CopyButton.tsx b/docs/components/CopyButton/CopyButton.tsx
new file mode 100644
index 000000000..5514a2738
--- /dev/null
+++ b/docs/components/CopyButton/CopyButton.tsx
@@ -0,0 +1,44 @@
+import * as React from 'react';
+import styles from './CopyButton.module.css';
+
+type CopyButtonProps = {
+ copy: (event: React.MouseEvent) => Promise;
+ copyDisabled?: boolean;
+};
+
+export function CopyButton({ copy, copyDisabled }: CopyButtonProps) {
+ return (
+
+ {copyDisabled ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+ );
+}
diff --git a/docs/components/CopyButton/index.ts b/docs/components/CopyButton/index.ts
new file mode 100644
index 000000000..367e65cb2
--- /dev/null
+++ b/docs/components/CopyButton/index.ts
@@ -0,0 +1 @@
+export * from './CopyButton';
diff --git a/docs/components/DemoContent/index.ts b/docs/components/DemoContent/index.ts
new file mode 100644
index 000000000..d775afea9
--- /dev/null
+++ b/docs/components/DemoContent/index.ts
@@ -0,0 +1,2 @@
+// dog-food the demo content
+export * from '../../app/docs-infra/components/code-highlighter/demos/DemoContent';
diff --git a/docs/components/FileConventions/FileConventions.tsx b/docs/components/FileConventions/FileConventions.tsx
new file mode 100644
index 000000000..f78ac039a
--- /dev/null
+++ b/docs/components/FileConventions/FileConventions.tsx
@@ -0,0 +1,18 @@
+import * as React from 'react';
+import { getFileConventions } from '@mui/internal-docs-infra/pipeline/getFileConventions';
+import Link from 'next/link';
+
+export async function FileConventions() {
+ const conventions = await getFileConventions();
+
+ return (
+
+ {conventions.map((convention, i) => (
+
+ {convention.rule}
-{' '}
+ {convention.loader}
+
+ ))}
+
+ );
+}
diff --git a/docs/components/FileConventions/index.ts b/docs/components/FileConventions/index.ts
new file mode 100644
index 000000000..00b819f8c
--- /dev/null
+++ b/docs/components/FileConventions/index.ts
@@ -0,0 +1 @@
+export * from './FileConventions';
diff --git a/docs/components/LabeledSwitch/LabeledSwitch.module.css b/docs/components/LabeledSwitch/LabeledSwitch.module.css
new file mode 100644
index 000000000..f5c2bb047
--- /dev/null
+++ b/docs/components/LabeledSwitch/LabeledSwitch.module.css
@@ -0,0 +1,67 @@
+.root {
+ display: flex;
+ width: 88px;
+ height: 28px;
+}
+
+.indicator {
+ position: absolute;
+ height: 28px;
+ width: 44px;
+ background-color: color(display-p3 0.57 0.373 0.791);
+ background-color: #9a5cd0;
+ z-index: -100;
+ border-radius: 12px 0 0 12px;
+ transition: all 0.2s ease;
+}
+
+.indicator.checked {
+ transform: translateX(45px);
+ border-radius: 0 12px 12px 0;
+}
+
+.segment {
+ flex: 1;
+ padding: 0;
+ font-size: 1rem;
+ line-height: 1;
+ border: 1px solid #10003332;
+ border: 1px solid color(display-p3 0.067 0.008 0.184 / 0.197);
+ background-color: transparent;
+ color: #0400119c;
+ color: color(display-p3 0.016 0 0.059 / 0.612);
+ cursor: pointer;
+ transition:
+ background 0.2s,
+ color 0.2s,
+ border-color 0.2s;
+}
+
+.segment + .segment {
+ margin-left: -1px;
+ border-left: none;
+}
+
+.segment:nth-child(2) {
+ border-radius: 12px 0 0 12px;
+}
+.segment:nth-child(2).active {
+ border-radius: 12px 0 0 12px;
+}
+.segment:last-child {
+ border-radius: 0 12px 12px 0;
+}
+
+.segment.active {
+ background-color: transparent;
+ border-color: color(display-p3 0.57 0.373 0.791);
+ border-color: #9a5cd0;
+ color: #fff;
+}
+
+.segment:hover {
+ background-color: #30004010;
+ background-color: color(display-p3 0.129 0.008 0.255 / 0.063);
+ border-color: #30004010;
+ border-color: color(display-p3 0.129 0.008 0.255 / 0.063);
+}
diff --git a/docs/components/LabeledSwitch/LabeledSwitch.tsx b/docs/components/LabeledSwitch/LabeledSwitch.tsx
new file mode 100644
index 000000000..b5e6a47ec
--- /dev/null
+++ b/docs/components/LabeledSwitch/LabeledSwitch.tsx
@@ -0,0 +1,66 @@
+import * as React from 'react';
+import { Toggle } from '@base-ui-components/react/toggle';
+import { ToggleGroup } from '@base-ui-components/react/toggle-group';
+import styles from './LabeledSwitch.module.css';
+
+/**
+ * A two-option switch with labels.
+ * @param checked the currently selected value
+ * @param onCheckedChange called when the value changes
+ * @param labels to show for each option, e.g. { false: 'TS', true: 'JS' }
+ * @param defaultChecked the initial value when the component mounts
+ */
+export function LabeledSwitch({
+ checked,
+ onCheckedChange,
+ labels,
+ defaultChecked,
+}: {
+ checked: boolean | undefined;
+ onCheckedChange: (checked: boolean) => void;
+ labels: { false: string; true: string };
+ defaultChecked?: boolean;
+}) {
+ const handleChange = React.useCallback(
+ (value: string[]) => {
+ if (value.length === 0) {
+ return;
+ }
+
+ if (value.length === 1) {
+ const newChecked = value[0] === 'true';
+ onCheckedChange(newChecked);
+ } else {
+ const newChecked = !checked;
+ onCheckedChange(newChecked);
+ }
+ },
+ [checked, onCheckedChange],
+ );
+
+ return (
+
+
+
+ {labels.false}
+
+
+ {labels.true}
+
+
+ );
+}
diff --git a/docs/components/LabeledSwitch/index.ts b/docs/components/LabeledSwitch/index.ts
new file mode 100644
index 000000000..b4442fc70
--- /dev/null
+++ b/docs/components/LabeledSwitch/index.ts
@@ -0,0 +1 @@
+export * from './LabeledSwitch';
diff --git a/docs/components/Pre/Pre.module.css b/docs/components/Pre/Pre.module.css
new file mode 100644
index 000000000..2c4c6dfc9
--- /dev/null
+++ b/docs/components/Pre/Pre.module.css
@@ -0,0 +1,13 @@
+.root {
+ font-family: var(--font-geist-mono);
+ font-size: 13px;
+ border: 1px solid #d0cdd7;
+ padding: 8px;
+ border-radius: 8px;
+}
+
+.root pre {
+ overflow-x: auto;
+ padding: 8px;
+ margin: 0;
+}
diff --git a/docs/components/Pre/Pre.tsx b/docs/components/Pre/Pre.tsx
new file mode 100644
index 000000000..b8fecf583
--- /dev/null
+++ b/docs/components/Pre/Pre.tsx
@@ -0,0 +1,25 @@
+import * as React from 'react';
+import { CodeHighlighter } from '@mui/internal-docs-infra/CodeHighlighter';
+import type { CodeHighlighterProps } from '@mui/internal-docs-infra/CodeHighlighter/types';
+import { CodeContent } from '../CodeContent';
+
+type PreProps = {
+ 'data-precompute'?: string;
+};
+
+export function Pre(props: PreProps) {
+ if (!props['data-precompute']) {
+ return (
+
+ Expected precompute data to be provided. Ensure that transformHtmlCode rehype plugin is
+ used.
+
+ );
+ }
+
+ const precompute = JSON.parse(
+ props['data-precompute'],
+ ) as CodeHighlighterProps['precompute'];
+
+ return ;
+}
diff --git a/docs/components/Pre/index.ts b/docs/components/Pre/index.ts
new file mode 100644
index 000000000..876a84b3e
--- /dev/null
+++ b/docs/components/Pre/index.ts
@@ -0,0 +1 @@
+export * from './Pre';
diff --git a/docs/components/Select/Select.module.css b/docs/components/Select/Select.module.css
new file mode 100644
index 000000000..a16920154
--- /dev/null
+++ b/docs/components/Select/Select.module.css
@@ -0,0 +1,217 @@
+.Select {
+ box-sizing: border-box;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ height: 1.75rem;
+ padding-left: 12px;
+ padding-right: 10px;
+ margin: 0;
+ outline: 0;
+ border: 1px solid #d0cdd7;
+ border-radius: 12px;
+ font-family: inherit;
+ font-size: 1rem;
+ line-height: 1.5rem;
+ color: #65636d;
+ cursor: default;
+ user-select: none;
+ min-width: 140px;
+
+ @media (hover: hover) {
+ &:hover {
+ background-color: #f2eff3;
+ }
+ }
+
+ &:active {
+ background-color: #f2eff3;
+ }
+
+ &[data-popup-open] {
+ background-color: #f2eff3;
+ }
+
+ &:focus-visible {
+ outline: 2px solid color(display-p3 0.523 0.318 0.751);
+ outline: 2px solid #8e4ec6;
+ outline-offset: -1px;
+ }
+}
+
+.Select[aria-disabled='true'] {
+ opacity: 0.7;
+ cursor: progress;
+}
+
+.SelectIcon {
+ display: flex;
+}
+
+.Popup {
+ box-sizing: border-box;
+ padding-block: 0.25rem;
+ border-radius: 0.375rem;
+ background-color: canvas;
+ color: #65636d;
+ transform-origin: var(--transform-origin);
+ transition:
+ transform 150ms,
+ opacity 150ms;
+ overflow-y: auto;
+ max-height: var(--available-height);
+
+ &[data-starting-style],
+ &[data-ending-style] {
+ opacity: 0;
+ transform: scale(0.9);
+ }
+
+ &[data-side='none'] {
+ transition: none;
+ transform: none;
+ opacity: 1;
+ }
+
+ @media (prefers-color-scheme: light) {
+ outline: 1px solid #d0cdd7;
+ box-shadow:
+ 0 10px 15px -3px #d0cdd7,
+ 0 4px 6px -4px #d0cdd7;
+ }
+
+ @media (prefers-color-scheme: dark) {
+ outline: 1px solid var(--color-gray-300);
+ outline-offset: -1px;
+ }
+}
+
+.Arrow {
+ display: flex;
+
+ &[data-side='top'] {
+ bottom: -8px;
+ rotate: 180deg;
+ }
+
+ &[data-side='bottom'] {
+ top: -8px;
+ rotate: 0deg;
+ }
+
+ &[data-side='left'] {
+ right: -13px;
+ rotate: 90deg;
+ }
+
+ &[data-side='right'] {
+ left: -13px;
+ rotate: -90deg;
+ }
+}
+
+.ArrowFill {
+ fill: canvas;
+}
+
+.ArrowOuterStroke {
+ @media (prefers-color-scheme: light) {
+ fill: #d0cdd7;
+ }
+}
+
+.ArrowInnerStroke {
+ @media (prefers-color-scheme: dark) {
+ fill: #d0cdd7;
+ }
+}
+
+.Item {
+ box-sizing: border-box;
+ outline: 0;
+ font-size: 0.875rem;
+ line-height: 1rem;
+ padding-block: 0.5rem;
+ padding-left: 0.625rem;
+ padding-right: 1rem;
+ min-width: var(--anchor-width);
+ display: grid;
+ gap: 0.5rem;
+ align-items: center;
+ grid-template-columns: 0.75rem 1fr;
+ cursor: default;
+ user-select: none;
+ scroll-margin-block: 1rem;
+
+ [data-side='none'] & {
+ font-size: 1rem;
+ padding-right: 3rem;
+ min-width: calc(var(--anchor-width) + 1rem);
+ }
+
+ &[data-highlighted] {
+ z-index: 0;
+ position: relative;
+ }
+
+ &[data-highlighted]::before {
+ content: '';
+ z-index: -1;
+ position: absolute;
+ inset-block: 0;
+ inset-inline: 0.25rem;
+ border-radius: 12px;
+ background-color: #f2eff3;
+ }
+}
+
+.ItemIndicator {
+ grid-column-start: 1;
+}
+
+.ItemIndicatorIcon {
+ display: block;
+ width: 0.75rem;
+ height: 0.75rem;
+}
+
+.ItemText {
+ grid-column-start: 2;
+}
+
+.ScrollArrow {
+ width: 100%;
+ background: canvas;
+ z-index: 1;
+ text-align: center;
+ cursor: default;
+ border-radius: 0.375rem;
+ height: 1rem;
+ font-size: 0.75rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &::before {
+ content: '';
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ left: 0;
+ }
+
+ &[data-direction='up'] {
+ &::before {
+ top: -100%;
+ }
+ }
+
+ &[data-direction='down'] {
+ bottom: 0;
+
+ &::before {
+ bottom: -100%;
+ }
+ }
+}
diff --git a/docs/components/Select/Select.tsx b/docs/components/Select/Select.tsx
new file mode 100644
index 000000000..9927b4b84
--- /dev/null
+++ b/docs/components/Select/Select.tsx
@@ -0,0 +1,64 @@
+import * as React from 'react';
+import { Select as SelectParts } from '@base-ui-components/react/select';
+import styles from './Select.module.css';
+
+export interface Props {
+ items: { label: string; value: string }[];
+ value?: string;
+ onValueChange?: (value: string) => void;
+ disabled?: boolean;
+}
+
+export function Select({ items, value, onValueChange, disabled }: Props) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {items.map(({ label, value: itemValue }) => (
+
+
+
+
+ {label}
+
+ ))}
+
+
+
+
+
+ );
+}
+
+function ChevronUpDownIcon(props: React.ComponentProps<'svg'>) {
+ return (
+
+
+
+
+ );
+}
+
+function CheckIcon(props: React.ComponentProps<'svg'>) {
+ return (
+
+
+
+ );
+}
diff --git a/docs/components/Select/index.ts b/docs/components/Select/index.ts
new file mode 100644
index 000000000..7868ecbae
--- /dev/null
+++ b/docs/components/Select/index.ts
@@ -0,0 +1 @@
+export * from './Select';
diff --git a/docs/components/Tabs/Tabs.module.css b/docs/components/Tabs/Tabs.module.css
new file mode 100644
index 000000000..f0241d28d
--- /dev/null
+++ b/docs/components/Tabs/Tabs.module.css
@@ -0,0 +1,113 @@
+.tabsRoot {
+ display: flex;
+}
+
+.tabsList {
+ display: flex;
+ gap: 0px;
+ overflow-x: auto;
+ max-width: 100%;
+ padding-top: 14px;
+}
+
+.name {
+ display: flex;
+ align-items: center;
+ height: 48px;
+}
+
+.name:hover {
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.name span {
+ color: #65636d;
+ padding: 8px 12px;
+ font-size: 12px;
+ font-family: var(--font-geist-mono);
+}
+
+.tab {
+ padding: 10px 8px 8px 8px;
+ border: 1px solid #d0cdd7;
+ border-bottom: none;
+ background-color: transparent;
+ color: #65636d;
+ cursor: pointer;
+ font-size: 12px;
+ font-family: var(--font-geist-mono);
+ transition: all 0.2s ease;
+ position: relative;
+ z-index: 1;
+ margin-left: 0;
+ margin-right: 0;
+ white-space: nowrap;
+}
+
+.tab:hover:not(.tabSelected) {
+ background-color: #30004010;
+}
+
+.tab:active:not(.tabSelected) {
+ background-color: #20003820;
+}
+
+.tab[aria-disabled='true'] {
+ opacity: 0.7;
+ cursor: progress;
+}
+
+.tabSelected {
+ border-radius: 12px 12px 0 0;
+ background-color: color(display-p3 0.523 0.318 0.751);
+ background-color: #8e4ec6;
+ border-color: color(display-p3 0.523 0.318 0.751);
+ border-color: #8e4ec6;
+ color: #ffffff;
+ z-index: 2;
+}
+
+.tabSelected:active {
+ border-radius: 16px 16px 0 0;
+}
+
+.tabFirst {
+ border-radius: 12px 0 0 0;
+}
+
+.tabLast {
+ border-radius: 0 12px 0 0;
+}
+
+.tabMiddle {
+ border-radius: 0;
+}
+
+.tabNextSelected {
+ padding: 10px 20px 8px 8px;
+ margin-right: -12px;
+}
+
+.tabPrevSelected {
+ padding: 10px 8px 8px 20px;
+ margin-left: -12px;
+}
+
+.tabNoBorderRight {
+ border-right: 1px solid transparent;
+}
+
+.tabNoBorderRight:not(.tabNextSelected) {
+ margin-right: -1px;
+}
+
+.tabWithBorderRight {
+ border-right: 1px solid #d0cdd7;
+}
+
+.tabSelected.tabWithBorderRight {
+ border-right: 1px solid #8e4ec6;
+ border-color: color(display-p3 0.523 0.318 0.751);
+ border-color: #8e4ec6;
+}
diff --git a/docs/components/Tabs/Tabs.tsx b/docs/components/Tabs/Tabs.tsx
new file mode 100644
index 000000000..a6f5dadf9
--- /dev/null
+++ b/docs/components/Tabs/Tabs.tsx
@@ -0,0 +1,83 @@
+import * as React from 'react';
+import { Tabs as TabsParts } from '@base-ui-components/react/tabs';
+import styles from './Tabs.module.css';
+
+export interface Tab {
+ name: string;
+ id: string;
+}
+
+export interface TabsProps {
+ tabs: Tab[];
+ selectedTabId?: string;
+ onTabSelect: (tabId: string) => void;
+ disabled?: boolean;
+}
+
+export function Tabs({ tabs, selectedTabId, onTabSelect, disabled }: TabsProps) {
+ const clickName = React.useCallback(() => {
+ onTabSelect(tabs[0].id);
+ }, [onTabSelect, tabs]);
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === 'Enter' || event.key === ' ') {
+ event.preventDefault();
+ onTabSelect(tabs[0].id);
+ }
+ },
+ [onTabSelect, tabs],
+ );
+
+ if (tabs.length <= 1) {
+ return tabs.length === 1 ? (
+
+ {tabs[0].name}
+
+ ) : null;
+ }
+
+ return (
+
+
+ {tabs.map((tab, index) => {
+ const isSelected = selectedTabId ? tab.id === selectedTabId : index === 0;
+ const isFirst = index === 0;
+ const isLast = index === tabs.length - 1;
+ const nextTabSelected = index < tabs.length - 1 && tabs[index + 1].id === selectedTabId;
+ const prevTabSelected = index > 0 && tabs[index - 1].id === selectedTabId;
+
+ const tabClasses = [
+ styles.tab,
+ isSelected && styles.tabSelected,
+ !isSelected && isFirst && styles.tabFirst,
+ !isSelected && isLast && styles.tabLast,
+ !isSelected && !isFirst && !isLast && styles.tabMiddle,
+ nextTabSelected && styles.tabNextSelected,
+ prevTabSelected && styles.tabPrevSelected,
+ isLast || isSelected ? styles.tabWithBorderRight : styles.tabNoBorderRight,
+ ]
+ .filter(Boolean)
+ .join(' ');
+
+ return (
+
+ {tab.name}
+
+ );
+ })}
+
+
+ );
+}
diff --git a/docs/components/Tabs/index.ts b/docs/components/Tabs/index.ts
new file mode 100644
index 000000000..d87151ff6
--- /dev/null
+++ b/docs/components/Tabs/index.ts
@@ -0,0 +1 @@
+export { Tabs, type Tab, type TabsProps } from './Tabs';
diff --git a/docs/components/index.ts b/docs/components/index.ts
new file mode 100644
index 000000000..d87151ff6
--- /dev/null
+++ b/docs/components/index.ts
@@ -0,0 +1 @@
+export { Tabs, type Tab, type TabsProps } from './Tabs';
diff --git a/docs/functions/createDemo/index.ts b/docs/functions/createDemo/index.ts
new file mode 100644
index 000000000..988a50dea
--- /dev/null
+++ b/docs/functions/createDemo/index.ts
@@ -0,0 +1,2 @@
+// dog-food the demo content
+export * from '../../app/docs-infra/components/code-highlighter/demos/createDemo';
diff --git a/docs/mdx-components.tsx b/docs/mdx-components.tsx
new file mode 100644
index 000000000..677af8091
--- /dev/null
+++ b/docs/mdx-components.tsx
@@ -0,0 +1,11 @@
+import type { MDXComponents } from 'mdx/types';
+import Blockquote from './components/Blockquote/Blockquote';
+import { Pre } from './components/Pre';
+
+export function useMDXComponents(components: MDXComponents): MDXComponents {
+ return {
+ ...components,
+ blockquote: Blockquote,
+ pre: Pre,
+ };
+}
diff --git a/docs/next.config.mjs b/docs/next.config.mjs
new file mode 100644
index 000000000..4cc6bdd82
--- /dev/null
+++ b/docs/next.config.mjs
@@ -0,0 +1,33 @@
+import createMDX from '@next/mdx';
+import { withDocsInfra, getDocsInfraMdxOptions } from '@mui/internal-docs-infra/withDocsInfra';
+import bundleAnalyzer from '@next/bundle-analyzer';
+
+const withBundleAnalyzer = bundleAnalyzer({
+ enabled: process.env.ANALYZE === 'true',
+});
+
+// Create MDX with docs-infra configuration
+const withMDX = createMDX({
+ options: getDocsInfraMdxOptions({
+ additionalRemarkPlugins: [],
+ additionalRehypePlugins: [],
+ }),
+});
+
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ // Your custom configuration here
+ // The withDocsInfra plugin will add the necessary docs infrastructure setup
+};
+
+export default withBundleAnalyzer(
+ withDocsInfra({
+ // Add demo-* patterns specific to this docs site
+ additionalDemoPatterns: {
+ // Note: The demo-* pattern below is specific to our internal docs structure
+ // where we create "demos of demos". This is not a typical use case.
+ index: ['./app/**/demos/*/demo-*/index.ts'],
+ client: ['./app/**/demos/*/demo-*/client.ts'],
+ },
+ })(withMDX(nextConfig)),
+);
diff --git a/docs/package.json b/docs/package.json
new file mode 100644
index 000000000..776bf047f
--- /dev/null
+++ b/docs/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "@mui/internal-infra-docs",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev --turbopack",
+ "build": "next build",
+ "build-exp": "next build --turbopack",
+ "start": "serve ./out"
+ },
+ "dependencies": {
+ "@base-ui-components/react": "1.0.0-beta.1",
+ "@mdx-js/loader": "^3.1.0",
+ "@mdx-js/react": "^3.1.0",
+ "@mui/internal-docs-infra": "workspace:^",
+ "@next/mdx": "^15.3.4",
+ "@types/mdx": "^2.0.13",
+ "next": "v15.5.2",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-runner": "^1.0.5",
+ "remark-gfm": "^4.0.1",
+ "server-only": "^0.0.1",
+ "use-editable": "^2.3.3",
+ "vscode-oniguruma": "^2.0.1"
+ },
+ "devDependencies": {
+ "@next/bundle-analyzer": "^15.5.2",
+ "@types/node": "^20",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "@wooorm/starry-night": "^3.8.0",
+ "serve": "^14.2.5",
+ "typescript": "^5"
+ }
+}
diff --git a/docs/tsconfig.json b/docs/tsconfig.json
new file mode 100644
index 000000000..6b1070751
--- /dev/null
+++ b/docs/tsconfig.json
@@ -0,0 +1,28 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 5b87289ca..a4286fc1c 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
import {
createBaseConfig,
createTestConfig,
+ createDocsConfig,
EXTENSION_TEST_FILE,
EXTENSION_TS,
} from '@mui/internal-code-infra/eslint';
@@ -46,6 +47,20 @@ export default defineConfig(
],
extends: createTestConfig(),
},
+ {
+ files: ['docs/**/*'],
+ extends: createDocsConfig(),
+ settings: {
+ 'import/resolver': {
+ typescript: {
+ project: ['docs/tsconfig.json'],
+ },
+ },
+ },
+ rules: {
+ '@next/next/no-img-element': 'off',
+ },
+ },
{
files: [`apps/**/*.${EXTENSION_TS}`],
rules: {
diff --git a/package.json b/package.json
index 3370f2a5d..eda2de76a 100644
--- a/package.json
+++ b/package.json
@@ -17,8 +17,9 @@
"size:snapshot": "pnpm -F ./test/bundle-size check",
"size:why": "pnpm size:snapshot --analyze",
"clean": "pnpm -r exec rm -rf build",
- "docs:build": "pnpm -F \"./packages/docs-infra\" run build",
- "docs:test": "pnpm -F \"./packages/docs-infra\" run test"
+ "docs:dev": "pnpm -F \"./docs\" run dev",
+ "docs:build": "pnpm -F \"./docs\" run build",
+ "docs:start": "pnpm -F \"./docs\" run start"
},
"pnpm": {
"packageExtensions": {
diff --git a/packages/code-infra/README.md b/packages/code-infra/README.md
index 18583a4d6..2ebac752b 100644
--- a/packages/code-infra/README.md
+++ b/packages/code-infra/README.md
@@ -1,3 +1,10 @@
# @mui/internal-code-infra
Scripts and configs to be used across MUI repos.
+
+## Documentation
+
+This is stored in the `docs` top-level directory.
+
+[Read in Markdown](../../docs/app/code-infra/page.mdx)
+[Read in Browser](https://infra.mui.com/code-infra)
diff --git a/packages/docs-infra/README.md b/packages/docs-infra/README.md
index 66c3188f1..455f7700a 100644
--- a/packages/docs-infra/README.md
+++ b/packages/docs-infra/README.md
@@ -4,6 +4,7 @@ This package hosts the tools that help create the documentation.
## Documentation
-This is stored in the `docs` directory.
+This is stored in the `docs` top-level directory.
-[Read More](../../docs/app/docs-infra/page.mdx)
+[Read in Markdown](../../docs/app/docs-infra/page.mdx)
+[Read in Browser](https://infra.mui.com/docs-infra)
diff --git a/packages/docs-infra/package.json b/packages/docs-infra/package.json
index f60013012..111e7f088 100644
--- a/packages/docs-infra/package.json
+++ b/packages/docs-infra/package.json
@@ -22,6 +22,10 @@
"./usePreference": "./src/usePreference/index.ts",
"./useUrlHashState": "./src/useUrlHashState/index.ts",
"./withDocsInfra": "./src/withDocsInfra/index.ts",
+ "./pipeline/getFileConventions": "./src/pipeline/getFileConventions/index.ts",
+ "./pipeline/transformMarkdownBlockquoteCallouts": "./src/pipeline/transformMarkdownBlockquoteCallouts/index.ts",
+ "./pipeline/transformMarkdownDemoLinks": "./src/pipeline/transformMarkdownDemoLinks/index.ts",
+ "./pipeline/transformMarkdownRelativePaths": "./src/pipeline/transformMarkdownRelativePaths/index.ts",
"./pipeline/hastUtils": "./src/pipeline/hastUtils/index.ts",
"./pipeline/loaderUtils": "./src/pipeline/loaderUtils/index.ts",
"./pipeline/loadPrecomputedCodeHighlighter": "./src/pipeline/loadPrecomputedCodeHighlighter/index.ts",
@@ -55,7 +59,7 @@
"release": "pnpm build && pnpm publish --no-git-checks",
"test": "pnpm -w test --project @mui/internal-docs-infra",
"test:watch": "pnpm -w test:watch --project @mui/internal-docs-infra",
- "test:coverage": "pnpm -w test --project @mui/internal-docs-infra --coverage --coverage.include=packages/docs-infra --coverage.exclude=packages/docs-infra/build --coverage.exclude=packages/docs-infra/scripts",
+ "test:coverage": "pnpm -w test --project @mui/internal-docs-infra --coverage --coverage.include=packages/docs-infra --coverage.exclude=packages/docs-infra/docs --coverage.exclude=packages/docs-infra/build --coverage.exclude=packages/docs-infra/scripts",
"typescript": "tsc -p tsconfig.json"
},
"dependencies": {
diff --git a/packages/docs-infra/src/pipeline/getFileConventions/fileConventions.ts b/packages/docs-infra/src/pipeline/getFileConventions/fileConventions.ts
new file mode 100644
index 000000000..9bdcb6c68
--- /dev/null
+++ b/packages/docs-infra/src/pipeline/getFileConventions/fileConventions.ts
@@ -0,0 +1,3 @@
+export const fileConventions = [
+ { rule: './app/**/demos/*/index.ts', loader: 'loadPrecomputedCodeHighlighter' },
+];
diff --git a/packages/docs-infra/src/pipeline/getFileConventions/getFileConventions.ts b/packages/docs-infra/src/pipeline/getFileConventions/getFileConventions.ts
new file mode 100644
index 000000000..3c6a98831
--- /dev/null
+++ b/packages/docs-infra/src/pipeline/getFileConventions/getFileConventions.ts
@@ -0,0 +1,5 @@
+import { fileConventions } from './fileConventions';
+
+export async function getFileConventions() {
+ return fileConventions; // TODO: Parse the next.config.js file to get convention overrides.
+}
diff --git a/packages/docs-infra/src/pipeline/getFileConventions/index.ts b/packages/docs-infra/src/pipeline/getFileConventions/index.ts
new file mode 100644
index 000000000..3bef9febc
--- /dev/null
+++ b/packages/docs-infra/src/pipeline/getFileConventions/index.ts
@@ -0,0 +1 @@
+export * from './getFileConventions';
diff --git a/packages/docs-infra/src/pipeline/transformMarkdownBlockquoteCallouts/index.ts b/packages/docs-infra/src/pipeline/transformMarkdownBlockquoteCallouts/index.ts
new file mode 100644
index 000000000..4c42b26c6
--- /dev/null
+++ b/packages/docs-infra/src/pipeline/transformMarkdownBlockquoteCallouts/index.ts
@@ -0,0 +1,5 @@
+// This is the export format expected by a remark plugin.
+
+import { transformMarkdownBlockquoteCallouts } from './transformMarkdownBlockquoteCallouts';
+
+export default transformMarkdownBlockquoteCallouts;
diff --git a/packages/docs-infra/src/pipeline/transformMarkdownBlockquoteCallouts/integration.test.ts b/packages/docs-infra/src/pipeline/transformMarkdownBlockquoteCallouts/integration.test.ts
new file mode 100644
index 000000000..41d80fa0a
--- /dev/null
+++ b/packages/docs-infra/src/pipeline/transformMarkdownBlockquoteCallouts/integration.test.ts
@@ -0,0 +1,56 @@
+import { describe, it, expect } from 'vitest';
+import { unified } from 'unified';
+import remarkParse from 'remark-parse';
+import remarkRehype from 'remark-rehype';
+import rehypeStringify from 'rehype-stringify';
+import { transformMarkdownBlockquoteCallouts } from './transformMarkdownBlockquoteCallouts';
+
+describe('remarkBlockquoteCallouts integration', () => {
+ it('should work in a complete pipeline like Next.js MDX', async () => {
+ const processor = unified()
+ .use(remarkParse)
+ .use(transformMarkdownBlockquoteCallouts)
+ .use(remarkRehype)
+ .use(rehypeStringify);
+
+ const markdown = `
+# Test Document
+
+> [!NOTE]
+> This is a note callout with **bold text**.
+
+> [!TIP]
+> This is a tip with a [link](https://example.com).
+
+> Regular blockquote without callouts.
+
+> [!WARNING]
+> Multi-line warning
+>
+> With multiple paragraphs.
+`;
+
+ const result = await processor.process(markdown);
+ const html = result.toString();
+
+ // Check that callouts are processed correctly
+ expect(html).toContain('');
+ expect(html).toContain('');
+ expect(html).toContain('');
+
+ // Check that regular blockquote doesn't have data attribute
+ expect(html).toContain('\nRegular blockquote');
+
+ // Check that callout markers are removed
+ expect(html).not.toContain('[!NOTE]');
+ expect(html).not.toContain('[!TIP]');
+ expect(html).not.toContain('[!WARNING]');
+
+ // Check that markdown content is still processed
+ expect(html).toContain('bold text ');
+ expect(html).toContain('link ');
+
+ // Check that the HTML structure is correct
+ expect(html).toContain('
Test Document ');
+ });
+});
diff --git a/packages/docs-infra/src/pipeline/transformMarkdownBlockquoteCallouts/transformMarkdownBlockquoteCallouts.test.ts b/packages/docs-infra/src/pipeline/transformMarkdownBlockquoteCallouts/transformMarkdownBlockquoteCallouts.test.ts
new file mode 100644
index 000000000..a562c9a1f
--- /dev/null
+++ b/packages/docs-infra/src/pipeline/transformMarkdownBlockquoteCallouts/transformMarkdownBlockquoteCallouts.test.ts
@@ -0,0 +1,161 @@
+import { describe, it, expect } from 'vitest';
+import { unified } from 'unified';
+import remarkParse from 'remark-parse';
+import remarkRehype from 'remark-rehype';
+import rehypeStringify from 'rehype-stringify';
+import type { Blockquote } from 'mdast';
+import { transformMarkdownBlockquoteCallouts } from './transformMarkdownBlockquoteCallouts';
+
+describe('transformMarkdownBlockquoteCallouts', () => {
+ const createProcessor = () => {
+ return unified()
+ .use(remarkParse)
+ .use(transformMarkdownBlockquoteCallouts)
+ .use(remarkRehype)
+ .use(rehypeStringify);
+ };
+
+ const processMarkdown = async (markdown: string) => {
+ const processor = createProcessor();
+ const result = await processor.process(markdown);
+ return result.toString();
+ };
+
+ const getAstFromMarkdown = async (markdown: string) => {
+ const processor = unified().use(remarkParse).use(transformMarkdownBlockquoteCallouts);
+ const tree = await processor.run(processor.parse(markdown));
+ return tree as any;
+ };
+
+ it('should add data-callout-type attribute for NOTE callout', async () => {
+ const markdown = '> [!NOTE]\n> This is a note.';
+ const html = await processMarkdown(markdown);
+
+ expect(html).toContain('data-callout-type="note"');
+ expect(html).toContain('This is a note.
');
+ expect(html).not.toContain('[!NOTE]');
+ });
+
+ it('should add data-callout-type attribute for TIP callout', async () => {
+ const markdown = '> [!TIP]\n> This is a tip.';
+ const html = await processMarkdown(markdown);
+
+ expect(html).toContain('data-callout-type="tip"');
+ expect(html).toContain('This is a tip.
');
+ expect(html).not.toContain('[!TIP]');
+ });
+
+ it('should add data-callout-type attribute for IMPORTANT callout', async () => {
+ const markdown = '> [!IMPORTANT]\n> This is important.';
+ const html = await processMarkdown(markdown);
+
+ expect(html).toContain('data-callout-type="important"');
+ expect(html).toContain('This is important.
');
+ expect(html).not.toContain('[!IMPORTANT]');
+ });
+
+ it('should add data-callout-type attribute for WARNING callout', async () => {
+ const markdown = '> [!WARNING]\n> This is a warning.';
+ const html = await processMarkdown(markdown);
+
+ expect(html).toContain('data-callout-type="warning"');
+ expect(html).toContain('This is a warning.
');
+ expect(html).not.toContain('[!WARNING]');
+ });
+
+ it('should add data-callout-type attribute for CAUTION callout', async () => {
+ const markdown = '> [!CAUTION]\n> This is a caution.';
+ const html = await processMarkdown(markdown);
+
+ expect(html).toContain('data-callout-type="caution"');
+ expect(html).toContain('This is a caution.
');
+ expect(html).not.toContain('[!CAUTION]');
+ });
+
+ it('should handle callouts with extra whitespace', async () => {
+ const markdown = '> [!NOTE] This is a note with extra spaces.';
+ const html = await processMarkdown(markdown);
+
+ expect(html).toContain('data-callout-type="note"');
+ expect(html).toContain('This is a note with extra spaces.
');
+ expect(html).not.toContain('[!NOTE]');
+ });
+
+ it('should handle callouts on same line as text', async () => {
+ const markdown = '> [!NOTE] This is a note on the same line.';
+ const html = await processMarkdown(markdown);
+
+ expect(html).toContain('data-callout-type="note"');
+ expect(html).toContain('This is a note on the same line.
');
+ expect(html).not.toContain('[!NOTE]');
+ });
+
+ it('should not modify blockquotes without callout markers', async () => {
+ const markdown = '> This is a regular blockquote.';
+ const html = await processMarkdown(markdown);
+
+ expect(html).not.toContain('data-callout-type');
+ expect(html).toContain('This is a regular blockquote.
');
+ });
+
+ it('should not modify blockquotes with invalid callout types', async () => {
+ const markdown = '> [!INVALID] This is an invalid callout.';
+ const html = await processMarkdown(markdown);
+
+ expect(html).not.toContain('data-callout-type');
+ expect(html).toContain('[!INVALID] This is an invalid callout.
');
+ });
+
+ it('should handle blockquotes with multiple paragraphs', async () => {
+ const markdown = '> [!NOTE] This is a note.\n>\n> This is another paragraph.';
+ const html = await processMarkdown(markdown);
+
+ expect(html).toContain('data-callout-type="note"');
+ expect(html).toContain('This is a note.
');
+ expect(html).toContain('This is another paragraph.
');
+ expect(html).not.toContain('[!NOTE]');
+ });
+
+ it('should handle empty blockquotes', async () => {
+ const markdown = '>';
+ const html = await processMarkdown(markdown);
+
+ expect(html).not.toContain('data-callout-type');
+ });
+
+ it('should handle callouts that make the text node empty', async () => {
+ const markdown = '> [!NOTE]';
+ const tree = await getAstFromMarkdown(markdown);
+
+ const blockquote = tree.children[0] as Blockquote;
+ expect((blockquote.data as any)?.hProperties?.['data-callout-type']).toBe('note');
+
+ // The paragraph should still exist but be empty
+ expect(blockquote.children).toHaveLength(1);
+ expect(blockquote.children[0].type).toBe('paragraph');
+ expect((blockquote.children[0] as any).children).toHaveLength(0);
+ });
+
+ it('should preserve other blockquote content when processing callouts', async () => {
+ const markdown =
+ '> [!NOTE] Important note\n>\n> Additional content\n>\n> - List item\n> - Another item';
+ const html = await processMarkdown(markdown);
+
+ expect(html).toContain('data-callout-type="note"');
+ expect(html).toContain('Important note
');
+ expect(html).toContain('Additional content
');
+ expect(html).toContain('List item ');
+ expect(html).toContain('Another item ');
+ expect(html).not.toContain('[!NOTE]');
+ });
+
+ it('should work with nested blockquotes', async () => {
+ const markdown = '> [!NOTE] Outer note\n>\n> > [!TIP] Inner tip';
+ const html = await processMarkdown(markdown);
+
+ expect(html).toContain('data-callout-type="note"');
+ expect(html).toContain('data-callout-type="tip"');
+ expect(html).toContain('Outer note
');
+ expect(html).toContain('Inner tip
');
+ });
+});
diff --git a/packages/docs-infra/src/pipeline/transformMarkdownBlockquoteCallouts/transformMarkdownBlockquoteCallouts.ts b/packages/docs-infra/src/pipeline/transformMarkdownBlockquoteCallouts/transformMarkdownBlockquoteCallouts.ts
new file mode 100644
index 000000000..ab4980c49
--- /dev/null
+++ b/packages/docs-infra/src/pipeline/transformMarkdownBlockquoteCallouts/transformMarkdownBlockquoteCallouts.ts
@@ -0,0 +1,64 @@
+import { visit } from 'unist-util-visit';
+import type { Plugin } from 'unified';
+import type { Blockquote, Text } from 'mdast';
+
+/**
+ * Remark plugin that extracts GitHub-style callouts from blockquotes and injects them into data attributes.
+ *
+ * Transforms blockquotes like:
+ * > [!NOTE]
+ * > This is a note.
+ *
+ * Into blockquotes with a custom data attribute that will be preserved when converted to HTML:
+ *
+ * This is a note.
+ *
+ *
+ * Supported callout types: NOTE, TIP, IMPORTANT, WARNING, CAUTION
+ */
+export const transformMarkdownBlockquoteCallouts: Plugin = () => {
+ return (tree) => {
+ visit(tree, 'blockquote', (node: Blockquote) => {
+ // Find the first paragraph in the blockquote
+ const firstChild = node.children[0];
+ if (!firstChild || firstChild.type !== 'paragraph') {
+ return;
+ }
+
+ // Find the first text node in the paragraph
+ const firstTextNode = firstChild.children[0];
+ if (!firstTextNode || firstTextNode.type !== 'text') {
+ return;
+ }
+
+ const textNode = firstTextNode as Text;
+ const calloutPattern = /^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*/;
+ const match = textNode.value.match(calloutPattern);
+
+ if (match) {
+ const calloutType = match[1].toLowerCase();
+
+ // Remove the callout marker from the text
+ const newText = textNode.value.replace(calloutPattern, '');
+
+ if (newText.trim() === '') {
+ // Remove the text node if it becomes empty
+ firstChild.children.shift();
+ } else {
+ // Update the text content
+ textNode.value = newText;
+ }
+
+ // Add the data attribute to the blockquote
+ // This creates a custom property that will be preserved when converting to HTML
+ if (!node.data) {
+ node.data = {};
+ }
+ if (!(node.data as any).hProperties) {
+ (node.data as any).hProperties = {};
+ }
+ (node.data as any).hProperties['data-callout-type'] = calloutType;
+ }
+ });
+ };
+};
diff --git a/packages/docs-infra/src/pipeline/transformMarkdownDemoLinks/index.ts b/packages/docs-infra/src/pipeline/transformMarkdownDemoLinks/index.ts
new file mode 100644
index 000000000..ffa3e53b3
--- /dev/null
+++ b/packages/docs-infra/src/pipeline/transformMarkdownDemoLinks/index.ts
@@ -0,0 +1,5 @@
+// This is the export format expected by a remark plugin.
+
+import { transformMarkdownDemoLinks } from './transformMarkdownDemoLinks';
+
+export default transformMarkdownDemoLinks;
diff --git a/packages/docs-infra/src/pipeline/transformMarkdownDemoLinks/transformMarkdownDemoLinks.test.ts b/packages/docs-infra/src/pipeline/transformMarkdownDemoLinks/transformMarkdownDemoLinks.test.ts
new file mode 100644
index 000000000..28774163a
--- /dev/null
+++ b/packages/docs-infra/src/pipeline/transformMarkdownDemoLinks/transformMarkdownDemoLinks.test.ts
@@ -0,0 +1,653 @@
+import { unified } from 'unified';
+import remarkParse from 'remark-parse';
+import remarkRehype from 'remark-rehype';
+import rehypeStringify from 'rehype-stringify';
+import { describe, it, expect } from 'vitest';
+import transformMarkdownDemoLinks from './index.js';
+
+// Processor for testing AST structure
+const astProcessor = unified().use(remarkParse).use(transformMarkdownDemoLinks);
+
+// End-to-end processor for testing final HTML output
+const e2eProcessor = unified()
+ .use(remarkParse)
+ .use(transformMarkdownDemoLinks)
+ .use(remarkRehype, { allowDangerousHtml: true })
+ .use(rehypeStringify, { allowDangerousHtml: true });
+
+describe('transformMarkdownDemoLinks', () => {
+ describe('AST Structure Tests', () => {
+ it('should remove "[See Demo]" link and horizontal rule after Demo component', () => {
+ const markdown = `
+
+
+[See Demo](./demos/code/)
+
+---
+
+## Next Section
+`;
+
+ const ast = astProcessor.runSync(astProcessor.parse(markdown)) as any;
+
+ // Should have 2 children: the demo paragraph and the heading
+ expect(ast.children).toHaveLength(2);
+
+ // First child should be the demo html node
+ const demoHtml = ast.children[0];
+ expect(demoHtml.type).toBe('html');
+ expect(demoHtml.value).toBe(' ');
+
+ // Second child should be the heading (the link and separator should be removed)
+ const heading = ast.children[1];
+ expect(heading.type).toBe('heading');
+ expect(heading.children[0].value).toBe('Next Section');
+ });
+
+ it('should handle multiple Demo patterns in the same document', () => {
+ const markdown = `
+
+
+[See Demo](./demos/first/)
+
+---
+
+Some content in between.
+
+
+
+[See Demo](./demos/second/)
+
+---
+
+Final content.
+`;
+
+ const ast = astProcessor.runSync(astProcessor.parse(markdown)) as any;
+
+ // Should have 3 children: demo1, content paragraph, demo2, final content
+ expect(ast.children).toHaveLength(4);
+
+ // Check first demo
+ const firstDemo = ast.children[0];
+ expect(firstDemo.type).toBe('html');
+ expect(firstDemo.value).toBe(' ');
+
+ // Check content paragraph
+ const contentPara = ast.children[1];
+ expect(contentPara.type).toBe('paragraph');
+ expect(contentPara.children[0].value).toBe('Some content in between.');
+
+ // Check second demo
+ const secondDemo = ast.children[2];
+ expect(secondDemo.type).toBe('html');
+ expect(secondDemo.value).toBe(' ');
+
+ // Check final content
+ const finalContent = ast.children[3];
+ expect(finalContent.type).toBe('paragraph');
+ expect(finalContent.children[0].value).toBe('Final content.');
+ });
+
+ it('should NOT remove pattern when Demo is just the .Title', () => {
+ const markdown = `
+
+
+[See Demo](./demos/code/)
+
+---
+
+## Next Section
+`;
+
+ const ast = astProcessor.runSync(astProcessor.parse(markdown)) as any;
+
+ // Should have 4 children: demo, link, separator, heading (nothing removed)
+ expect(ast.children).toHaveLength(4);
+
+ // Check that all original elements are preserved
+ expect(ast.children[0].type).toBe('paragraph'); // Demo
+ expect(ast.children[1].type).toBe('paragraph'); // Link
+ expect(ast.children[2].type).toBe('thematicBreak'); // Separator
+ expect(ast.children[3].type).toBe('heading'); // Heading
+ });
+
+ it('should remove "[See Demo]" link even when there is no horizontal rule', () => {
+ const markdown = `
+
+
+[See Demo](./demos/code/)
+
+## Next Section
+`;
+
+ const ast = astProcessor.runSync(astProcessor.parse(markdown)) as any;
+
+ // Should have 2 children: demo and heading (link removed even without separator)
+ expect(ast.children).toHaveLength(2);
+
+ expect(ast.children[0].type).toBe('html'); // Demo
+ expect(ast.children[1].type).toBe('heading'); // Heading
+ });
+
+ it('should remove both "[See Demo]" link and horizontal rule when both are present', () => {
+ const markdown = `
+
+
+[See Demo](./demos/code/)
+
+---
+
+## Next Section
+`;
+
+ const ast = astProcessor.runSync(astProcessor.parse(markdown)) as any;
+
+ // Should have 2 children: demo and heading (both link and separator removed)
+ expect(ast.children).toHaveLength(2);
+
+ expect(ast.children[0].type).toBe('html'); // Demo
+ expect(ast.children[1].type).toBe('heading'); // Heading
+ });
+
+ it('should NOT remove pattern when there is no "[See Demo]" link', () => {
+ const markdown = `
+
+
+[Different Link](./demos/code/)
+
+---
+
+## Next Section
+`;
+
+ const ast = astProcessor.runSync(astProcessor.parse(markdown)) as any;
+
+ // Should have 3 children: demo, link, heading (HR removed even without "See Demo" link)
+ expect(ast.children).toHaveLength(3);
+
+ expect(ast.children[0].type).toBe('html'); // Demo
+ expect(ast.children[1].type).toBe('paragraph'); // Link (preserved)
+ expect(ast.children[2].type).toBe('heading'); // Heading
+ });
+
+ it('should handle Demo components mixed with other content', () => {
+ const markdown = `
+Some text before
+
+[See Demo](./demos/code/)
+
+---
+
+## Next Section
+`;
+
+ const ast = astProcessor.runSync(astProcessor.parse(markdown)) as any;
+
+ // Should process Demo even when mixed with other content - remove See Demo link and HR
+ expect(ast.children).toHaveLength(2);
+
+ expect(ast.children[0].type).toBe('paragraph'); // Text with Demo (preserved)
+ expect(ast.children[1].type).toBe('heading'); // Heading
+ });
+
+ it('should handle Demo components with props', () => {
+ const markdown = `
+
+
+[See Demo](./demos/with-props/)
+
+---
+
+## Next Section
+`;
+
+ const ast = astProcessor.runSync(astProcessor.parse(markdown)) as any;
+
+ // Should have 2 children: demo and heading (link and separator removed)
+ expect(ast.children).toHaveLength(2);
+
+ const demoHtml = ast.children[0];
+ expect(demoHtml.type).toBe('html');
+ expect(demoHtml.value).toBe(' ');
+ });
+
+ it('should handle self-closing and non-self-closing Demo components', () => {
+ const markdown = `
+
+
+[See Demo](./demos/self-closing/)
+
+---
+
+Content
+
+[See Demo](./demos/with-children/)
+
+---
+
+Final content.
+`;
+
+ const ast = astProcessor.runSync(astProcessor.parse(markdown)) as any;
+
+ // Should have 3 children: demo1, demo2, final content
+ expect(ast.children).toHaveLength(3);
+
+ // Check first demo (self-closing)
+ const firstDemo = ast.children[0];
+ expect(firstDemo.type).toBe('html');
+ expect(firstDemo.value).toBe(' ');
+
+ // Check second demo (with children) - this stays as a paragraph structure
+ const secondDemo = ast.children[1];
+ expect(secondDemo.type).toBe('paragraph');
+ expect(secondDemo.children).toHaveLength(3); // opening tag, content, closing tag
+ expect(secondDemo.children[0].type).toBe('html');
+ expect(secondDemo.children[0].value).toBe('');
+ expect(secondDemo.children[1].type).toBe('text');
+ expect(secondDemo.children[1].value).toBe('Content');
+ expect(secondDemo.children[2].type).toBe('html');
+ expect(secondDemo.children[2].value).toBe(' ');
+ });
+
+ it('should handle empty Demo components', () => {
+ const markdown = `
+
+
+[See Demo](./demos/empty/)
+
+---
+
+## Next Section
+`;
+
+ const ast = astProcessor.runSync(astProcessor.parse(markdown)) as any;
+
+ // Should have 2 children: demo and heading
+ expect(ast.children).toHaveLength(2);
+
+ const demoHtml = ast.children[0];
+ expect(demoHtml.type).toBe('html');
+ expect(demoHtml.value).toBe(' ');
+ });
+
+ it('should NOT process non-Demo HTML elements', () => {
+ const markdown = `
+Some content
+
+[See Demo](./demos/div/)
+
+---
+
+## Next Section
+`;
+
+ const ast = astProcessor.runSync(astProcessor.parse(markdown)) as any;
+
+ // Should have 4 children (nothing removed because it's not a Demo component)
+ expect(ast.children).toHaveLength(4);
+
+ expect(ast.children[0].type).toBe('html'); // div
+ expect(ast.children[1].type).toBe('paragraph'); // Link
+ expect(ast.children[2].type).toBe('thematicBreak'); // Separator
+ expect(ast.children[3].type).toBe('heading'); // Heading
+ });
+ });
+
+ describe('End-to-End HTML Output Tests', () => {
+ it('should produce clean HTML output with Demo component only', () => {
+ const markdown = `
+
+[See Demo](./demos/code/)
+
+---
+
+## Next Section`;
+
+ const result = e2eProcessor.processSync(markdown).toString();
+
+ // Demo component is preserved as raw HTML, but links and HR are removed
+ expect(result).toEqual(' \nNext Section ');
+ });
+
+ it('should handle multiple Demo patterns correctly in HTML output', () => {
+ const markdown = `
+
+[See Demo](./demos/first/)
+
+---
+
+Some content between demos.
+
+
+
+[See Demo](./demos/second/)
+
+---
+
+Final content.`;
+
+ const result = e2eProcessor.processSync(markdown).toString();
+
+ // Demo components preserved as raw HTML, links and HR removed
+ expect(result).toEqual(
+ ' \nSome content between demos.
\n \nFinal content.
',
+ );
+ });
+
+ it('should preserve Demo components with .Title in HTML output', () => {
+ const markdown = `
+
+[See Demo](./demos/code/)
+
+---
+
+## Next Section`;
+
+ const result = e2eProcessor.processSync(markdown).toString();
+
+ // All elements should be preserved since Demo.Title should NOT be processed
+ expect(result).toEqual(
+ '<DemoCodeHighlighter.Title />
\nSee Demo
\n \nNext Section ',
+ );
+ });
+
+ it('should handle Demo components with complex props in HTML output', () => {
+ const markdown = ` console.log('test')}
+/>
+
+[See Demo](./demos/advanced/)
+
+---
+
+## Next Section`;
+
+ const result = e2eProcessor.processSync(markdown).toString();
+
+ // Complex JSX syntax in multiline Demo components sometimes gets escaped by remark-rehype
+ // When this happens, the plugin doesn't process it (since it's text, not HTML), so everything is preserved
+ expect(result).toEqual(
+ '<DemoAdvanced\nvariant="complex"\ndata={{"key": "value"}}\nonEvent={() => console.log(\'test\')}\n/>
\nSee Demo
\n \nNext Section ',
+ );
+ });
+
+ it('should preserve other links that are not "See Demo"', () => {
+ const markdown = `
+
+[Different Link](./demos/code/)
+
+---
+
+## Next Section`;
+
+ const result = e2eProcessor.processSync(markdown).toString();
+
+ // Link should be preserved since it's not "See Demo", but HR is now removed after Demo components
+ // With allowDangerousHtml, Demo component appears as raw HTML even when not processed
+ expect(result).toEqual(
+ ' \nDifferent Link
\nNext Section ',
+ );
+ });
+
+ it('should remove "[See Demo]" link even without horizontal rule in HTML output', () => {
+ const markdown = `
+
+[See Demo](./demos/code/)
+
+## Next Section`;
+
+ const result = e2eProcessor.processSync(markdown).toString();
+
+ // Demo component preserved, See Demo link removed (no HR to remove)
+ expect(result).toEqual(' \nNext Section ');
+ });
+
+ it('should handle mixed content with demos and regular content', () => {
+ const markdown = `# Documentation
+
+Some introduction text.
+
+
+
+[See Demo](./demos/basic/)
+
+---
+
+## Features
+
+More documentation content.
+
+
+
+[See Demo](./demos/advanced/)
+
+---
+
+## Conclusion
+
+Final thoughts.`;
+
+ const result = e2eProcessor.processSync(markdown).toString();
+
+ // Demo components preserved as raw HTML, See Demo links and HR removed
+ expect(result).toEqual(
+ 'Documentation \nSome introduction text.
\n \nFeatures \nMore documentation content.
\n \nConclusion \nFinal thoughts.
',
+ );
+ });
+
+ it('should handle edge case with empty See Demo link', () => {
+ const markdown = `
+
+[See Demo]()
+
+---
+
+## Next Section`;
+
+ const result = e2eProcessor.processSync(markdown).toString();
+
+ // Should still process even if the link is empty
+ expect(result).toEqual(' \nNext Section ');
+ });
+
+ it('should handle Demo components nested in other HTML', () => {
+ const markdown = `
+
+[See Demo](./demos/nested/)
+
+---
+
+## Next Section`;
+
+ const result = e2eProcessor.processSync(markdown).toString();
+
+ // The nested Demo SHOULD be processed since it's in a valid HTML node that contains \nNext Section ');
+ });
+
+ it('should handle Demo components with line breaks', () => {
+ const markdown = `
+
+[See Demo](./demos/multiline/)
+
+---
+
+## Next Section`;
+
+ const result = e2eProcessor.processSync(markdown).toString();
+
+ // Multiline Demo component remains in paragraph tags, but See Demo link and HR are removed
+ expect(result).toMatch(/ <\/p>\n
Next Section<\/h2>/);
+ });
+ });
+
+ describe('Edge Cases', () => {
+ it('should handle document with only Demo pattern', () => {
+ const markdown = `
+
+[See Demo](./demos/only/)
+
+---`;
+
+ const ast = astProcessor.runSync(astProcessor.parse(markdown)) as any;
+
+ // Should have only 1 child: the demo html node
+ expect(ast.children).toHaveLength(1);
+ expect(ast.children[0].type).toBe('html');
+ expect(ast.children[0].value).toBe(' ');
+ });
+
+ it('should handle malformed Demo tags gracefully', () => {
+ const markdown = ` {
+ const markdown = `
+
+[See Demo](./demos/case/)
+
+---
+
+## Next Section`;
+
+ const ast = astProcessor.runSync(astProcessor.parse(markdown)) as any;
+
+ // Should NOT process because it's lowercase 'demo', not 'Demo'
+ expect(ast.children).toHaveLength(4);
+ });
+
+ it('should handle Demo.Title vs DemoTitle correctly', () => {
+ const markdown1 = `
+
+[See Demo](./demos/dot-title/)
+
+---`;
+
+ const markdown2 = `
+
+[See Demo](./demos/title/)
+
+---`;
+
+ const ast1 = astProcessor.runSync(astProcessor.parse(markdown1)) as any;
+ const ast2 = astProcessor.runSync(astProcessor.parse(markdown2)) as any;
+
+ // First should NOT be processed (contains .Title)
+ expect(ast1.children).toHaveLength(3);
+
+ // Second SHOULD be processed (DemoTitle, not Demo.Title)
+ expect(ast2.children).toHaveLength(1);
+ expect(ast2.children[0].type).toBe('html');
+ expect(ast2.children[0].value).toBe(' ');
+ });
+
+ it('should handle multiple horizontal rules correctly', () => {
+ const markdown = `
+
+[See Demo](./demos/test/)
+
+---
+
+---
+
+## Next Section`;
+
+ const ast = astProcessor.runSync(astProcessor.parse(markdown)) as any;
+
+ // Should process the first pattern (Demo + See Demo link + first HR) and preserve the second HR
+ expect(ast.children).toHaveLength(3);
+ expect(ast.children[0].type).toBe('html'); // Demo
+ expect(ast.children[1].type).toBe('thematicBreak'); // Second HR (preserved)
+ expect(ast.children[2].type).toBe('heading'); // Heading
+ });
+
+ it('should successfully process imported Demo components', () => {
+ // This simulates how imported MDX components appear in markdown
+ const markdown = `import { DemoCodeHighlighterCode } from './demos/code';
+
+
+
+[See Demo](./demos/code/)
+
+---
+
+## Next Section`;
+
+ const ast = astProcessor.runSync(astProcessor.parse(markdown)) as any;
+
+ // Plugin should work! It removes the "[See Demo]" link and HR
+ expect(ast.children).toHaveLength(3);
+
+ expect(ast.children[0].type).toBe('paragraph'); // import statement
+ expect(ast.children[1].type).toBe('html'); // becomes html node
+ expect(ast.children[1].value).toBe(' ');
+ expect(ast.children[2].type).toBe('heading'); // heading remains
+ });
+
+ it('should handle MDX JSX flow elements (simulated)', () => {
+ // This simulates an MDX JSX flow element directly in the AST
+ const mockAst = {
+ type: 'root',
+ children: [
+ {
+ type: 'mdxJsxFlowElement',
+ name: 'DemoCodeHighlighter',
+ attributes: [],
+ children: [],
+ },
+ {
+ type: 'paragraph',
+ children: [
+ {
+ type: 'link',
+ url: './demos/code/',
+ children: [{ type: 'text', value: 'See Demo' }],
+ },
+ ],
+ },
+ {
+ type: 'thematicBreak',
+ },
+ {
+ type: 'heading',
+ depth: 2,
+ children: [{ type: 'text', value: 'Next Section' }],
+ },
+ ],
+ };
+
+ // Create processor with our plugin and apply it
+ const processor = unified().use(transformMarkdownDemoLinks);
+ const result = processor.runSync(mockAst as any) as any;
+
+ // Should remove the link and HR, leaving just the Demo and heading
+ expect(result.children).toHaveLength(2);
+ expect(result.children[0].type).toBe('mdxJsxFlowElement');
+ expect(result.children[1].type).toBe('heading');
+ });
+ });
+});
diff --git a/packages/docs-infra/src/pipeline/transformMarkdownDemoLinks/transformMarkdownDemoLinks.ts b/packages/docs-infra/src/pipeline/transformMarkdownDemoLinks/transformMarkdownDemoLinks.ts
new file mode 100644
index 000000000..2f016ea59
--- /dev/null
+++ b/packages/docs-infra/src/pipeline/transformMarkdownDemoLinks/transformMarkdownDemoLinks.ts
@@ -0,0 +1,136 @@
+import type { Plugin } from 'unified';
+import type { Parent, PhrasingContent, Html, Paragraph } from 'mdast';
+
+// MDX JSX types
+interface MdxJsxFlowElement {
+ type: 'mdxJsxFlowElement';
+ name: string | null;
+ attributes: Array;
+ children: Array;
+}
+
+/**
+ * Remark plugin that cleans up demo patterns in markdown.
+ *
+ * Looks for patterns where a Demo component is followed by a "[See Demo]" link
+ * and optionally a horizontal rule (---). When found, removes the link and
+ * any following horizontal rule.
+ *
+ * This is useful for markdown that will be converted to HTML where the link
+ * and separator are distracting on the page.
+ *
+ * Pattern it matches:
+ * ```
+ *
+ *
+ * [See Demo](./demos/base/)
+ *
+ * --- (optional)
+ * ```
+ *
+ * Gets transformed to:
+ * ```
+ *
+ * ```
+ */
+export const transformMarkdownDemoLinks: Plugin = () => {
+ return (tree) => {
+ const parent = tree as Parent;
+ const children = parent.children;
+
+ for (let i = 0; i < children.length - 1; i += 1) {
+ const current = children[i];
+ const next = children[i + 1];
+ const separator = children[i + 2]; // May not exist
+
+ let hasDemo = false;
+
+ // Check if current node is an HTML element containing a Demo component without .Title
+ if (current?.type === 'html') {
+ const htmlNode = current as Html;
+ hasDemo = htmlNode.value.includes('= 2 &&
+ paragraphNode.children[0].type === 'html' &&
+ paragraphNode.children[paragraphNode.children.length - 1].type === 'html'
+ ) {
+ // Check if this looks like a Demo component with opening and closing tags
+ const openingTag = paragraphNode.children[0] as Html;
+ const closingTag = paragraphNode.children[paragraphNode.children.length - 1] as Html;
+
+ if (
+ openingTag.value.includes(' {
+ return (
+ child.type === 'html' &&
+ child.value.includes(' {
+ return (
+ child.type === 'link' &&
+ child.children.some(
+ (linkChild) => linkChild.type === 'text' && linkChild.value === 'See Demo',
+ )
+ );
+ });
+
+ // Check if there's also a thematic break (---) after the paragraph
+ const hasThematicBreak = separator?.type === 'thematicBreak';
+
+ if (hasSeeDemo) {
+ // Remove the "See Demo" paragraph and any following thematic break
+ if (hasThematicBreak) {
+ // Remove both the "See Demo" paragraph and the thematic break
+ children.splice(i + 1, 2);
+ removedSomething = true;
+ } else {
+ // Remove only the "See Demo" paragraph
+ children.splice(i + 1, 1);
+ removedSomething = true;
+ }
+ } else if (hasThematicBreak) {
+ // No "See Demo" link, but there's a thematic break after the paragraph - remove just the HR
+ children.splice(i + 2, 1);
+ removedSomething = true;
+ }
+ }
+
+ // If we removed something, adjust the loop index to prevent skipping
+ if (removedSomething) {
+ i -= 1;
+ }
+ }
+ };
+};
diff --git a/packages/docs-infra/src/pipeline/transformMarkdownRelativePaths/index.ts b/packages/docs-infra/src/pipeline/transformMarkdownRelativePaths/index.ts
new file mode 100644
index 000000000..45a9967c2
--- /dev/null
+++ b/packages/docs-infra/src/pipeline/transformMarkdownRelativePaths/index.ts
@@ -0,0 +1,5 @@
+// This is the export format expected by a remark plugin.
+
+import { transformMarkdownRelativePaths } from './transformMarkdownRelativePaths';
+
+export default transformMarkdownRelativePaths;
diff --git a/packages/docs-infra/src/pipeline/transformMarkdownRelativePaths/transformMarkdownRelativePaths.ts b/packages/docs-infra/src/pipeline/transformMarkdownRelativePaths/transformMarkdownRelativePaths.ts
new file mode 100644
index 000000000..0ff97452a
--- /dev/null
+++ b/packages/docs-infra/src/pipeline/transformMarkdownRelativePaths/transformMarkdownRelativePaths.ts
@@ -0,0 +1,41 @@
+// webpack does not like node: imports
+// eslint-disable-next-line n/prefer-node-protocol
+import path from 'path';
+
+import { visit } from 'unist-util-visit';
+import type { Plugin } from 'unified';
+import type { Link } from 'mdast';
+
+/**
+ * Remark plugin that strips page file extensions from URLs.
+ * Removes /page.tsx, /page.jsx, /page.js, /page.mdx, /page.md from both absolute and relative URLs.
+ * For relative URLs (both ./ and ../), converts them to absolute paths based on the current file's location.
+ *
+ * Examples:
+ * - /components/page.tsx -> /components
+ * - ./code-highlighter/page.mdx -> /components/code-highlighter (when processed from /components/page.mdx)
+ * - ../code-highlighter/page.tsx -> /code-highlighter (when processed from /components/button/page.mdx)
+ * This allows URLs to resolve when reading in VSCode and Github
+ */
+export const transformMarkdownRelativePaths: Plugin = () => {
+ return (tree, file) => {
+ visit(tree, 'link', (node: Link) => {
+ if (node.url) {
+ node.url = node.url.replace(/\/page\.(tsx|jsx|js|mdx|md)$/g, '');
+ node.url = node.url.replace(/\/page\.(tsx|jsx|js|mdx|md)(\?[^#]*)?(#.*)?$/g, '$2$3');
+
+ if ((node.url.startsWith('./') || node.url.startsWith('../')) && file.path) {
+ const currentDir = path.dirname(file.path);
+ const appIndex = currentDir.indexOf('/app/');
+ const baseDir = appIndex !== -1 ? currentDir.substring(appIndex + 4) : '/';
+
+ // Resolve the relative path from the current directory
+ const resolvedPath = path.resolve('/', baseDir, node.url);
+ node.url = resolvedPath;
+ }
+
+ node.url = node.url.replace(/\/$/, '');
+ }
+ });
+ };
+};
diff --git a/packages/docs-infra/src/useCode/Pre.tsx b/packages/docs-infra/src/useCode/Pre.tsx
index 80f1d64ee..b7658bab2 100644
--- a/packages/docs-infra/src/useCode/Pre.tsx
+++ b/packages/docs-infra/src/useCode/Pre.tsx
@@ -162,6 +162,10 @@ export function Pre({
const frames = React.useMemo(() => {
return hast?.children.map((child, index) => {
if (child.type !== 'element') {
+ if (child.type === 'text') {
+ return {child.value} ;
+ }
+
return null;
}
@@ -190,7 +194,7 @@ export function Pre({
return (
- {typeof children === 'string' ? children : frames}
+ {typeof children === 'string' ? children : frames}
);
}
diff --git a/packages/docs-infra/src/useErrors/useErrors.ts b/packages/docs-infra/src/useErrors/useErrors.ts
index c79015fb7..2b21bead7 100644
--- a/packages/docs-infra/src/useErrors/useErrors.ts
+++ b/packages/docs-infra/src/useErrors/useErrors.ts
@@ -4,8 +4,12 @@ type Errors = {
errors?: Error[];
};
-export function useErrors(): Errors {
+export function useErrors(props?: Errors): Errors {
const context = useErrorsContext();
- return { errors: context?.errors };
+ // Context errors take precedence over prop errors
+ // This ensures client-side errors override server-side errors
+ const errors = context?.errors || props?.errors;
+
+ return { errors };
}
diff --git a/packages/docs-infra/src/withDocsInfra/withDocsInfra.test.ts b/packages/docs-infra/src/withDocsInfra/withDocsInfra.test.ts
index 488c4edf8..7f5e5a23a 100644
--- a/packages/docs-infra/src/withDocsInfra/withDocsInfra.test.ts
+++ b/packages/docs-infra/src/withDocsInfra/withDocsInfra.test.ts
@@ -664,7 +664,10 @@ describe('getDocsInfraMdxOptions', () => {
expect(result.remarkPlugins).toEqual([
['remark-gfm'],
+ ['@mui/internal-docs-infra/pipeline/transformMarkdownRelativePaths'],
+ ['@mui/internal-docs-infra/pipeline/transformMarkdownBlockquoteCallouts'],
['@mui/internal-docs-infra/pipeline/transformMarkdownCode'],
+ ['@mui/internal-docs-infra/pipeline/transformMarkdownDemoLinks'],
]);
expect(result.rehypePlugins).toEqual([
@@ -680,7 +683,10 @@ describe('getDocsInfraMdxOptions', () => {
expect(result.remarkPlugins).toEqual([
['remark-gfm'],
+ ['@mui/internal-docs-infra/pipeline/transformMarkdownRelativePaths'],
+ ['@mui/internal-docs-infra/pipeline/transformMarkdownBlockquoteCallouts'],
['@mui/internal-docs-infra/pipeline/transformMarkdownCode'],
+ ['@mui/internal-docs-infra/pipeline/transformMarkdownDemoLinks'],
['remark-emoji'],
]);
diff --git a/packages/docs-infra/src/withDocsInfra/withDocsInfra.ts b/packages/docs-infra/src/withDocsInfra/withDocsInfra.ts
index 395f83044..1e210cb5f 100644
--- a/packages/docs-infra/src/withDocsInfra/withDocsInfra.ts
+++ b/packages/docs-infra/src/withDocsInfra/withDocsInfra.ts
@@ -100,7 +100,10 @@ export function getDocsInfraMdxOptions(
): DocsInfraMdxOptions {
const defaultRemarkPlugins: Array = [
['remark-gfm'],
+ ['@mui/internal-docs-infra/pipeline/transformMarkdownRelativePaths'],
+ ['@mui/internal-docs-infra/pipeline/transformMarkdownBlockquoteCallouts'],
['@mui/internal-docs-infra/pipeline/transformMarkdownCode'],
+ ['@mui/internal-docs-infra/pipeline/transformMarkdownDemoLinks'],
];
const defaultRehypePlugins: Array = [
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cd0facd7a..9525bb5e3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -221,6 +221,73 @@ importers:
specifier: ^1.15.5
version: 1.15.5
+ docs:
+ dependencies:
+ '@base-ui-components/react':
+ specifier: 1.0.0-beta.1
+ version: 1.0.0-beta.1(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@mdx-js/loader':
+ specifier: ^3.1.0
+ version: 3.1.1(webpack@5.101.3(jiti@2.5.1))
+ '@mdx-js/react':
+ specifier: ^3.1.0
+ version: 3.1.1(@types/react@19.1.13)(react@19.1.1)
+ '@mui/internal-docs-infra':
+ specifier: workspace:^
+ version: link:../packages/docs-infra/build
+ '@next/mdx':
+ specifier: ^15.3.4
+ version: 15.5.2(@mdx-js/loader@3.1.1(webpack@5.101.3(jiti@2.5.1)))(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))
+ '@types/mdx':
+ specifier: ^2.0.13
+ version: 2.0.13
+ next:
+ specifier: v15.5.2
+ version: 15.5.2(@babel/core@7.28.4)(@opentelemetry/api@1.8.0)(babel-plugin-macros@3.1.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ react:
+ specifier: ^19.0.0
+ version: 19.1.1
+ react-dom:
+ specifier: ^19.0.0
+ version: 19.1.1(react@19.1.1)
+ react-runner:
+ specifier: ^1.0.5
+ version: 1.0.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ remark-gfm:
+ specifier: ^4.0.1
+ version: 4.0.1
+ server-only:
+ specifier: ^0.0.1
+ version: 0.0.1
+ use-editable:
+ specifier: ^2.3.3
+ version: 2.3.3(react@19.1.1)
+ vscode-oniguruma:
+ specifier: ^2.0.1
+ version: 2.0.1
+ devDependencies:
+ '@next/bundle-analyzer':
+ specifier: ^15.5.2
+ version: 15.5.2
+ '@types/node':
+ specifier: ^20
+ version: 20.19.12
+ '@types/react':
+ specifier: ^19
+ version: 19.1.13
+ '@types/react-dom':
+ specifier: ^19
+ version: 19.1.9(@types/react@19.1.13)
+ '@wooorm/starry-night':
+ specifier: ^3.8.0
+ version: 3.8.0
+ serve:
+ specifier: ^14.2.5
+ version: 14.2.5
+ typescript:
+ specifier: ^5
+ version: 5.9.2
+
packages/babel-plugin-display-name:
dependencies:
'@babel/helper-module-imports':
@@ -1462,6 +1529,17 @@ packages:
resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==}
engines: {node: '>=6.9.0'}
+ '@base-ui-components/react@1.0.0-beta.1':
+ resolution: {integrity: sha512-7zmGiz4/+HKnv99lWftItoSMqnj2PdSvt2krh0/GP+Rj0xK0NMnFI/gIVvP7CB2G+k0JPUrRWXjXa3y08oiakg==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ '@types/react': ^17 || ^18 || ^19
+ react: ^17 || ^18 || ^19
+ react-dom: ^17 || ^18 || ^19
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@base-ui-components/react@1.0.0-beta.3':
resolution: {integrity: sha512-4sAq6zmDA9ixV2HRjjeM1+tSEw5R6nvGjXUQmFoQnC3DZLEUdwO94gWDmUDdpoDuChn27jdbaJs9F0Ih4w2UAA==}
engines: {node: '>=14.0.0'}
@@ -1576,6 +1654,10 @@ packages:
resolution: {integrity: sha512-Y6+WUMsTFWE5jb20IFP4YGa5IrGY/+a/FbOSjDF/wz9gepU2hwCYSXRHP/vPwBvwcY3SVMASt4yXxbXNXigmZQ==}
engines: {node: '>=18'}
+ '@discoveryjs/json-ext@0.5.7':
+ resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
+ engines: {node: '>=10.0.0'}
+
'@dmsnell/diff-match-patch@1.1.0':
resolution: {integrity: sha512-yejLPmM5pjsGvxS9gXablUSbInW7H976c/FJ4iQxWIm7/38xBySRemTPDe34lhg1gVLbJntX0+sH0jYfU+PN9A==}
@@ -2614,6 +2696,23 @@ packages:
engines: {node: '>=18'}
hasBin: true
+ '@mdx-js/loader@3.1.1':
+ resolution: {integrity: sha512-0TTacJyZ9mDmY+VefuthVshaNIyCGZHJG2fMnGaDttCt8HmjUF7SizlHJpaCDoGnN635nK1wpzfpx/Xx5S4WnQ==}
+ peerDependencies:
+ webpack: '>=5'
+ peerDependenciesMeta:
+ webpack:
+ optional: true
+
+ '@mdx-js/mdx@3.1.1':
+ resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==}
+
+ '@mdx-js/react@3.1.1':
+ resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==}
+ peerDependencies:
+ '@types/react': '>=16'
+ react: '>=16'
+
'@mui/base@5.0.0-beta.69':
resolution: {integrity: sha512-r2YyGUXpZxj8rLAlbjp1x2BnMERTZ/dMqd9cClKj2OJ7ALAuiv/9X5E9eHfRc9o/dGRuLSMq/WTjREktJVjxVA==}
engines: {node: '>=14.0.0'}
@@ -3379,12 +3478,26 @@ packages:
engines: {node: '>=18.14.0'}
hasBin: true
+ '@next/bundle-analyzer@15.5.2':
+ resolution: {integrity: sha512-UWOFpy/NK5iSeIP0mgdq4VqGB4/z37uq5v5dEtvzmY/BlaPO6m4EtFUaH6RVI0w2wG5sh0TG86i/cA5wcaJtgg==}
+
'@next/env@15.5.2':
resolution: {integrity: sha512-Qe06ew4zt12LeO6N7j8/nULSOe3fMXE4dM6xgpBQNvdzyK1sv5y4oAP3bq4LamrvGCZtmRYnW8URFCeX5nFgGg==}
'@next/eslint-plugin-next@15.5.3':
resolution: {integrity: sha512-SdhaKdko6dpsSr0DldkESItVrnPYB1NS2NpShCSX5lc7SSQmLZt5Mug6t2xbiuVWEVDLZSuIAoQyYVBYp0dR5g==}
+ '@next/mdx@15.5.2':
+ resolution: {integrity: sha512-Lz9mdoKRfSNc7T1cSk3gzryhRcc7ErsiAWba1HBoInCX4ZpGUQXmiZLAAyrIgDl7oS/UHxsgKtk2qp/Df4gKBg==}
+ peerDependencies:
+ '@mdx-js/loader': '>=0.15.0'
+ '@mdx-js/react': '>=0.15.0'
+ peerDependenciesMeta:
+ '@mdx-js/loader':
+ optional: true
+ '@mdx-js/react':
+ optional: true
+
'@next/swc-darwin-arm64@15.5.2':
resolution: {integrity: sha512-8bGt577BXGSd4iqFygmzIfTYizHb0LGWqH+qgIF/2EDxS5JsSdERJKA8WgwDyNBZgTIIA4D8qUtoQHmxIIquoQ==}
engines: {node: '>= 10'}
@@ -3885,6 +3998,9 @@ packages:
resolution: {integrity: sha512-bWLDlHsBlgKY/05wDN/V3ETcn5G2SV/SiA2ZmNvKGGlmVX4G5li7GRDhHcgYvHJHyJ8TUStqg2xtHmCs0UbAbg==}
engines: {node: '>=18'}
+ '@polka/url@1.0.0-next.29':
+ resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
+
'@popperjs/core@2.11.8':
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
@@ -5305,6 +5421,9 @@ packages:
'@types/mdast@4.0.4':
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
+ '@types/mdx@2.0.13':
+ resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==}
+
'@types/micromatch@4.0.9':
resolution: {integrity: sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg==}
@@ -5781,6 +5900,9 @@ packages:
resolution: {integrity: sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA==}
engines: {node: '>=18.12.0'}
+ '@zeit/schemas@2.36.0':
+ resolution: {integrity: sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==}
+
'@zkochan/js-yaml@0.0.7':
resolution: {integrity: sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==}
hasBin: true
@@ -5878,6 +6000,9 @@ packages:
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+ ajv@8.12.0:
+ resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==}
+
ajv@8.17.1:
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
@@ -5925,6 +6050,9 @@ packages:
resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==}
engines: {node: '>=14'}
+ any-promise@1.3.0:
+ resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
+
anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
@@ -5932,6 +6060,9 @@ packages:
aproba@2.0.0:
resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
+ arch@2.2.0:
+ resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==}
+
archiver-utils@2.1.0:
resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==}
engines: {node: '>= 6'}
@@ -6060,6 +6191,10 @@ packages:
resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
engines: {node: '>=8'}
+ astring@1.9.0:
+ resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==}
+ hasBin: true
+
async-function@1.0.0:
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
engines: {node: '>= 0.4'}
@@ -6235,6 +6370,10 @@ packages:
bowser@2.12.1:
resolution: {integrity: sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==}
+ boxen@7.0.0:
+ resolution: {integrity: sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==}
+ engines: {node: '>=14.16'}
+
boxen@8.0.1:
resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==}
engines: {node: '>=18'}
@@ -6300,6 +6439,10 @@ packages:
resolution: {integrity: sha512-tUkzZWK0M/qdoLEqikxBWe4kumyuwjl3HO6zHTr4yEI23EojPtLYXdG1+AQY7MN0cGyNDvEaJ8wiYQm6P2bPxg==}
engines: {node: '>=12.17'}
+ bytes@3.0.0:
+ resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==}
+ engines: {node: '>= 0.8'}
+
bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'}
@@ -6354,6 +6497,10 @@ packages:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
+ camelcase@7.0.1:
+ resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==}
+ engines: {node: '>=14.16'}
+
camelcase@8.0.0:
resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==}
engines: {node: '>=16'}
@@ -6371,6 +6518,10 @@ packages:
chainsaw@0.1.0:
resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==}
+ chalk-template@0.4.0:
+ resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==}
+ engines: {node: '>=12'}
+
chalk@4.1.0:
resolution: {integrity: sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==}
engines: {node: '>=10'}
@@ -6379,6 +6530,10 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
+ chalk@5.0.1:
+ resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==}
+ engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+
chalk@5.4.1:
resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
@@ -6483,6 +6638,10 @@ packages:
clipboard-copy@4.0.1:
resolution: {integrity: sha512-wOlqdqziE/NNTUJsfSgXmBMIrYmfd5V0HCGsR8uAKHcg+h9NENWINcfRjtWGU77wDHC8B8ijV4hMTGYbrKovng==}
+ clipboardy@3.0.0:
+ resolution: {integrity: sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
clipboardy@4.0.0:
resolution: {integrity: sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==}
engines: {node: '>=18'}
@@ -6514,6 +6673,9 @@ packages:
resolution: {integrity: sha512-rtpaCbr164TPPh+zFdkWpCyZuKkjpAzODfaZCf/SVJZzJN+4bHQb/LP3Jzq5/+84um3XXY8r548XiWKSborwVw==}
engines: {node: ^18.17.0 || >=20.5.0}
+ collapse-white-space@2.1.0:
+ resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==}
+
color-convert@1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
@@ -6577,10 +6739,18 @@ packages:
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
+ commander@4.1.1:
+ resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
+ engines: {node: '>= 6'}
+
commander@6.2.1:
resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==}
engines: {node: '>= 6'}
+ commander@7.2.0:
+ resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
+ engines: {node: '>= 10'}
+
commander@9.5.0:
resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
engines: {node: ^12.20.0 || >=14}
@@ -6650,6 +6820,10 @@ packages:
console-control-strings@1.1.0:
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
+ content-disposition@0.5.2:
+ resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==}
+ engines: {node: '>= 0.6'}
+
content-disposition@0.5.4:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'}
@@ -6934,6 +7108,9 @@ packages:
resolution: {integrity: sha512-Sr4SdOZ4vw6eQDvPYNxHogvrxmCIld/VenC5JbNrFwMiwd7lY/Z18ZFfo+EWNG4DD9nFlAujWAo/wGuOPHmy5A==}
engines: {node: '>=12'}
+ debounce@1.2.1:
+ resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
+
debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
@@ -7364,6 +7541,12 @@ packages:
resolution: {integrity: sha512-aiQ/QyJBVJbabtsSediM1S4qI+P3p8F5J5YR5o/bH003BCnnclzxK9pi5Qd2Hg01ktAtZCaQBdejHrkOBGwf5Q==}
engines: {node: '>=0.4.0'}
+ esast-util-from-estree@2.0.0:
+ resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==}
+
+ esast-util-from-js@2.0.1:
+ resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==}
+
esbuild@0.21.5:
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
engines: {node: '>=12'}
@@ -7574,9 +7757,24 @@ packages:
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'}
+ estree-util-attach-comments@3.0.0:
+ resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==}
+
+ estree-util-build-jsx@3.0.1:
+ resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==}
+
estree-util-is-identifier-name@3.0.0:
resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==}
+ estree-util-scope@1.0.0:
+ resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==}
+
+ estree-util-to-js@2.0.0:
+ resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==}
+
+ estree-util-visit@2.0.0:
+ resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==}
+
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
@@ -8285,6 +8483,9 @@ packages:
hast-util-parse-selector@4.0.0:
resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
+ hast-util-to-estree@3.1.3:
+ resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==}
+
hast-util-to-html@9.0.5:
resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==}
@@ -8734,6 +8935,10 @@ packages:
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
engines: {node: '>=0.10.0'}
+ is-port-reachable@4.0.0:
+ resolution: {integrity: sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
is-potential-custom-element-name@1.0.1:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
@@ -9376,6 +9581,13 @@ packages:
resolution: {integrity: sha512-K6K2NgKnTXimT3779/4KxSvobxOtMmx1LBZ3NwRxT/MDIR3Br/fQ4Q+WCX5QxjyUR8zg5+RV9Tbf2c5pAWTD2A==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ markdown-extensions@2.0.0:
+ resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==}
+ engines: {node: '>=16'}
+
+ markdown-table@3.0.4:
+ resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
+
markdown-to-jsx@7.7.4:
resolution: {integrity: sha512-1bSfXyBKi+EYS3YY+e0Csuxf8oZ3decdfhOav/Z7Wrk89tjudyL5FOmwZQUoy0/qVXGUl+6Q3s2SWtpDEWITfQ==}
engines: {node: '>= 10'}
@@ -9395,15 +9607,39 @@ packages:
maxstache@1.0.7:
resolution: {integrity: sha512-53ZBxHrZM+W//5AcRVewiLpDunHnucfdzZUGz54Fnvo4tE+J3p8EL66kBrs2UhBXvYKTWckWYYWBqJqoTcenqg==}
+ mdast-util-find-and-replace@3.0.2:
+ resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==}
+
mdast-util-from-markdown@2.0.2:
resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==}
+ mdast-util-gfm-autolink-literal@2.0.1:
+ resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==}
+
+ mdast-util-gfm-footnote@2.1.0:
+ resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==}
+
+ mdast-util-gfm-strikethrough@2.0.0:
+ resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==}
+
+ mdast-util-gfm-table@2.0.0:
+ resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==}
+
+ mdast-util-gfm-task-list-item@2.0.0:
+ resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==}
+
+ mdast-util-gfm@3.1.0:
+ resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==}
+
mdast-util-mdx-expression@2.0.1:
resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==}
mdast-util-mdx-jsx@3.2.0:
resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==}
+ mdast-util-mdx@3.0.0:
+ resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==}
+
mdast-util-mdxjs-esm@2.0.1:
resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==}
@@ -9468,12 +9704,51 @@ packages:
micromark-core-commonmark@2.0.3:
resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==}
+ micromark-extension-gfm-autolink-literal@2.1.0:
+ resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==}
+
+ micromark-extension-gfm-footnote@2.1.0:
+ resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==}
+
+ micromark-extension-gfm-strikethrough@2.1.0:
+ resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==}
+
+ micromark-extension-gfm-table@2.1.1:
+ resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==}
+
+ micromark-extension-gfm-tagfilter@2.0.0:
+ resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==}
+
+ micromark-extension-gfm-task-list-item@2.1.0:
+ resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==}
+
+ micromark-extension-gfm@3.0.0:
+ resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==}
+
+ micromark-extension-mdx-expression@3.0.1:
+ resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==}
+
+ micromark-extension-mdx-jsx@3.0.2:
+ resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==}
+
+ micromark-extension-mdx-md@2.0.0:
+ resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==}
+
+ micromark-extension-mdxjs-esm@3.0.0:
+ resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==}
+
+ micromark-extension-mdxjs@3.0.0:
+ resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==}
+
micromark-factory-destination@2.0.1:
resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==}
micromark-factory-label@2.0.1:
resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==}
+ micromark-factory-mdx-expression@2.0.3:
+ resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==}
+
micromark-factory-space@2.0.1:
resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==}
@@ -9504,6 +9779,9 @@ packages:
micromark-util-encode@2.0.1:
resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==}
+ micromark-util-events-to-acorn@2.0.3:
+ resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==}
+
micromark-util-html-tag-name@2.0.1:
resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==}
@@ -9532,6 +9810,10 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
+ mime-db@1.33.0:
+ resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==}
+ engines: {node: '>= 0.6'}
+
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
@@ -9540,6 +9822,10 @@ packages:
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
engines: {node: '>= 0.6'}
+ mime-types@2.1.18:
+ resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==}
+ engines: {node: '>= 0.6'}
+
mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
@@ -9703,6 +9989,10 @@ packages:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
+ mrmime@2.0.1:
+ resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
+ engines: {node: '>=10'}
+
ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
@@ -9739,6 +10029,9 @@ packages:
resolution: {integrity: sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==}
engines: {node: '>= 0.6'}
+ mz@2.7.0:
+ resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
+
named-placeholders@1.1.3:
resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==}
engines: {node: '>=12.0.0'}
@@ -10073,6 +10366,10 @@ packages:
openapi-typescript-helpers@0.0.15:
resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==}
+ opener@1.5.2:
+ resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
+ hasBin: true
+
oppa@0.4.0:
resolution: {integrity: sha512-DFvM3+F+rB/igo3FRnkDWitjZgBH9qZAn68IacYHsqbZBKwuTA+LdD4zSJiQtgQpWq7M08we5FlGAVHz0yW7PQ==}
engines: {node: '>=10'}
@@ -10311,6 +10608,9 @@ packages:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
+ path-is-inside@1.0.2:
+ resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==}
+
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
@@ -10336,6 +10636,9 @@ packages:
path-to-regexp@0.1.12:
resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==}
+ path-to-regexp@3.3.0:
+ resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==}
+
path-to-regexp@6.3.0:
resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
@@ -10445,6 +10748,10 @@ packages:
resolution: {integrity: sha512-40SszWPOPwGhUIJ3zj0PsbMNV1bfg8nw5Qp/tP2FE2p3EuycmhDeYimKOMBAu6rtxcSw2QpjJsuK5A6v+en8Yw==}
hasBin: true
+ pirates@4.0.7:
+ resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
+ engines: {node: '>= 6'}
+
piscina@4.8.0:
resolution: {integrity: sha512-EZJb+ZxDrQf3dihsUL7p42pjNyrNIFJCrRHPMgxu/svsj+P3xS3fuEWp7k2+rfsavfl1N0G29b1HGs7J0m8rZA==}
@@ -10707,6 +11014,10 @@ packages:
randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
+ range-parser@1.2.0:
+ resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==}
+ engines: {node: '>= 0.6'}
+
range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
@@ -10826,6 +11137,12 @@ packages:
react-dom:
optional: true
+ react-runner@1.0.5:
+ resolution: {integrity: sha512-eCIybRpssp6ffjqXqId024esO9UP2lV838Lvm3fC7VgMQ/dQHhR0jJwOY2IPrYD3AaM/bcvMikmASIRZqNUHsw==}
+ peerDependencies:
+ react: ^16.0.0 || ^17 || ^18
+ react-dom: ^16.0.0 || ^17 || ^18
+
react-style-singleton@2.2.3:
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
engines: {node: '>=10'}
@@ -10922,6 +11239,20 @@ packages:
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
engines: {node: '>= 12.13.0'}
+ recma-build-jsx@1.0.0:
+ resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==}
+
+ recma-jsx@1.0.1:
+ resolution: {integrity: sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+ recma-parse@1.0.0:
+ resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==}
+
+ recma-stringify@1.0.0:
+ resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==}
+
recursive-readdir@2.2.3:
resolution: {integrity: sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==}
engines: {node: '>=6.0.0'}
@@ -10953,10 +11284,17 @@ packages:
resolution: {integrity: sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==}
engines: {node: '>=4'}
+ registry-auth-token@3.3.2:
+ resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==}
+
registry-auth-token@5.1.0:
resolution: {integrity: sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==}
engines: {node: '>=14'}
+ registry-url@3.1.0:
+ resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==}
+ engines: {node: '>=0.10.0'}
+
registry-url@6.0.1:
resolution: {integrity: sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==}
engines: {node: '>=12'}
@@ -10971,6 +11309,9 @@ packages:
rehype-parse@9.0.1:
resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==}
+ rehype-recma@1.0.0:
+ resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==}
+
rehype-stringify@10.0.1:
resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==}
@@ -10978,12 +11319,21 @@ packages:
resolution: {integrity: sha512-dLN0+SBPutC6bVFCH2+1o2VrHrvAj/PX6MzTemeaEKlCL10JKPMRlqszkitLQnHVgm90QQ94wxoBJRgfIEkstg==}
engines: {node: ^20.18.0 || ^22.12.0 || >=23.3.0}
+ remark-gfm@4.0.1:
+ resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
+
+ remark-mdx@3.1.1:
+ resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==}
+
remark-parse@11.0.0:
resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
remark-rehype@11.1.2:
resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==}
+ remark-stringify@11.0.0:
+ resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
+
remove-trailing-separator@1.1.0:
resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==}
@@ -11217,10 +11567,21 @@ packages:
serialize-javascript@6.0.2:
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
+ serve-handler@6.1.6:
+ resolution: {integrity: sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==}
+
serve-static@1.16.2:
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
engines: {node: '>= 0.8.0'}
+ serve@14.2.5:
+ resolution: {integrity: sha512-Qn/qMkzCcMFVPb60E/hQy+iRLpiU8PamOfOSYoAHmmF+fFFmpPpqa6Oci2iWYpTdOUM3VF+TINud7CfbQnsZbA==}
+ engines: {node: '>= 14'}
+ hasBin: true
+
+ server-only@0.0.1:
+ resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
+
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
@@ -11302,6 +11663,10 @@ packages:
simple-swizzle@0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
+ sirv@2.0.4:
+ resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==}
+ engines: {node: '>= 10'}
+
sisteransi@1.0.5:
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
@@ -11628,6 +11993,11 @@ packages:
stylis@4.2.0:
resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
+ sucrase@3.35.0:
+ resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ hasBin: true
+
superjson@2.0.0:
resolution: {integrity: sha512-W3n+NJ7TFjaLle8ihIIvsr/bbuKpnxeatsyjmhy7iSkom+/cshaHziCQAWXrHGWJVQSQFDOuES6C3nSEvcbrQg==}
engines: {node: '>=16'}
@@ -11743,6 +12113,13 @@ packages:
text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
+ thenify-all@1.6.0:
+ resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
+ engines: {node: '>=0.8'}
+
+ thenify@3.3.1:
+ resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+
thread-stream@3.1.0:
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
@@ -11821,6 +12198,10 @@ packages:
tomlify-j0.4@3.0.0:
resolution: {integrity: sha512-2Ulkc8T7mXJ2l0W476YC/A209PR38Nw8PuaCNtk9uI3t1zzFdGQeWYGQvmj2PZkVvRC/Yoi4xQKMRnWc/N29tQ==}
+ totalist@3.0.1:
+ resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
+ engines: {node: '>=6'}
+
tough-cookie@6.0.0:
resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==}
engines: {node: '>=16'}
@@ -11876,6 +12257,9 @@ packages:
peerDependencies:
typescript: '>=4.0.0'
+ ts-interface-checker@0.1.13:
+ resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+
ts-node@10.9.2:
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
hasBin: true
@@ -12091,6 +12475,9 @@ packages:
unist-util-is@6.0.0:
resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
+ unist-util-position-from-estree@2.0.0:
+ resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==}
+
unist-util-position@5.0.0:
resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
@@ -12211,6 +12598,9 @@ packages:
peerDependencies:
browserslist: '>= 4.21.0'
+ update-check@1.5.4:
+ resolution: {integrity: sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==}
+
update-notifier@7.3.1:
resolution: {integrity: sha512-+dwUY4L35XFYEzE+OAL3sarJdUioVovq+8f7lcIJ7wnmnYQV5UD1Y/lcwaMSyaQ6Bj3JMj1XSTjZbNLHn/19yA==}
engines: {node: '>=18'}
@@ -12244,6 +12634,11 @@ packages:
'@types/react':
optional: true
+ use-editable@2.3.3:
+ resolution: {integrity: sha512-7wVD2JbfAFJ3DK0vITvXBdpd9JAz5BcKAAolsnLBuBn6UDDwBGuCIAGvR3yA2BNKm578vAMVHFCWaOcA+BhhiA==}
+ peerDependencies:
+ react: '>= 16.8.0'
+
use-sidecar@1.1.3:
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
engines: {node: '>=10'}
@@ -12464,6 +12859,11 @@ packages:
resolution: {integrity: sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==}
engines: {node: '>=20'}
+ webpack-bundle-analyzer@4.10.1:
+ resolution: {integrity: sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==}
+ engines: {node: '>= 10.13.0'}
+ hasBin: true
+
webpack-sources@3.3.3:
resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==}
engines: {node: '>=10.13.0'}
@@ -12538,6 +12938,10 @@ packages:
wide-align@1.1.5:
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
+ widest-line@4.0.1:
+ resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==}
+ engines: {node: '>=12'}
+
widest-line@5.0.0:
resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==}
engines: {node: '>=18'}
@@ -12599,6 +13003,18 @@ packages:
resolution: {integrity: sha512-v2UQ+50TNf2rNHJ8NyWttfm/EJUBWMJcx6ZTYZr6Qp52uuegWw/lBkCtCbnYZEmPRNL61m+u67dAmGxo+HTULA==}
engines: {node: '>=8'}
+ ws@7.5.10:
+ resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==}
+ engines: {node: '>=8.3.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: ^5.0.2
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
ws@8.18.1:
resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==}
engines: {node: '>=10.0.0'}
@@ -14181,6 +14597,19 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
+ '@base-ui-components/react@1.0.0-beta.1(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ dependencies:
+ '@babel/runtime': 7.28.4
+ '@floating-ui/react-dom': 2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ '@floating-ui/utils': 0.2.10
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ reselect: 5.1.1
+ tabbable: 6.2.0
+ use-sync-external-store: 1.5.0(react@19.1.1)
+ optionalDependencies:
+ '@types/react': 19.1.13
+
'@base-ui-components/react@1.0.0-beta.3(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
@@ -14302,6 +14731,8 @@ snapshots:
gonzales-pe: 4.3.0
node-source-walk: 7.0.1
+ '@discoveryjs/json-ext@0.5.7': {}
+
'@dmsnell/diff-match-patch@1.1.0': {}
'@dual-bundle/import-meta-resolve@4.2.1': {}
@@ -15234,6 +15665,51 @@ snapshots:
- encoding
- supports-color
+ '@mdx-js/loader@3.1.1(webpack@5.101.3(jiti@2.5.1))':
+ dependencies:
+ '@mdx-js/mdx': 3.1.1
+ source-map: 0.7.6
+ optionalDependencies:
+ webpack: 5.101.3(jiti@2.5.1)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@mdx-js/mdx@3.1.1':
+ dependencies:
+ '@types/estree': 1.0.8
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdx': 2.0.13
+ acorn: 8.15.0
+ collapse-white-space: 2.1.0
+ devlop: 1.1.0
+ estree-util-is-identifier-name: 3.0.0
+ estree-util-scope: 1.0.0
+ estree-walker: 3.0.3
+ hast-util-to-jsx-runtime: 2.3.6
+ markdown-extensions: 2.0.0
+ recma-build-jsx: 1.0.0
+ recma-jsx: 1.0.1(acorn@8.15.0)
+ recma-stringify: 1.0.0
+ rehype-recma: 1.0.0
+ remark-mdx: 3.1.1
+ remark-parse: 11.0.0
+ remark-rehype: 11.1.2
+ source-map: 0.7.6
+ unified: 11.0.5
+ unist-util-position-from-estree: 2.0.0
+ unist-util-stringify-position: 4.0.0
+ unist-util-visit: 5.0.0
+ vfile: 6.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1)':
+ dependencies:
+ '@types/mdx': 2.0.13
+ '@types/react': 19.1.13
+ react: 19.1.1
+
'@mui/base@5.0.0-beta.69(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.28.4
@@ -16318,12 +16794,26 @@ snapshots:
- rollup
- supports-color
+ '@next/bundle-analyzer@15.5.2':
+ dependencies:
+ webpack-bundle-analyzer: 4.10.1
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
'@next/env@15.5.2': {}
'@next/eslint-plugin-next@15.5.3':
dependencies:
fast-glob: 3.3.1
+ '@next/mdx@15.5.2(@mdx-js/loader@3.1.1(webpack@5.101.3(jiti@2.5.1)))(@mdx-js/react@3.1.1(@types/react@19.1.13)(react@19.1.1))':
+ dependencies:
+ source-map: 0.7.6
+ optionalDependencies:
+ '@mdx-js/loader': 3.1.1(webpack@5.101.3(jiti@2.5.1))
+ '@mdx-js/react': 3.1.1(@types/react@19.1.13)(react@19.1.1)
+
'@next/swc-darwin-arm64@15.5.2':
optional: true
@@ -16873,6 +17363,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@polka/url@1.0.0-next.29': {}
+
'@popperjs/core@2.11.8': {}
'@radix-ui/number@1.1.1': {}
@@ -18660,6 +19152,8 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
+ '@types/mdx@2.0.13': {}
+
'@types/micromatch@4.0.9':
dependencies:
'@types/braces': 3.0.5
@@ -19344,6 +19838,8 @@ snapshots:
js-yaml: 3.14.1
tslib: 2.8.1
+ '@zeit/schemas@2.36.0': {}
+
'@zkochan/js-yaml@0.0.7':
dependencies:
argparse: 2.0.1
@@ -19423,6 +19919,13 @@ snapshots:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
+ ajv@8.12.0:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+ uri-js: 4.4.1
+
ajv@8.17.1:
dependencies:
fast-deep-equal: 3.1.3
@@ -19462,6 +19965,8 @@ snapshots:
ansis@4.1.0: {}
+ any-promise@1.3.0: {}
+
anymatch@3.1.3:
dependencies:
normalize-path: 3.0.0
@@ -19469,6 +19974,8 @@ snapshots:
aproba@2.0.0: {}
+ arch@2.2.0: {}
+
archiver-utils@2.1.0:
dependencies:
glob: 7.2.3
@@ -19657,6 +20164,8 @@ snapshots:
astral-regex@2.0.0: {}
+ astring@1.9.0: {}
+
async-function@1.0.0: {}
async-sema@3.1.1: {}
@@ -19856,6 +20365,17 @@ snapshots:
bowser@2.12.1: {}
+ boxen@7.0.0:
+ dependencies:
+ ansi-align: 3.0.1
+ camelcase: 7.0.1
+ chalk: 5.6.2
+ cli-boxes: 3.0.0
+ string-width: 5.1.2
+ type-fest: 2.19.0
+ widest-line: 4.0.1
+ wrap-ansi: 8.1.0
+
boxen@8.0.1:
dependencies:
ansi-align: 3.0.1
@@ -19922,6 +20442,8 @@ snapshots:
byte-size@8.1.1: {}
+ bytes@3.0.0: {}
+
bytes@3.1.2: {}
cac@6.7.14: {}
@@ -20004,6 +20526,8 @@ snapshots:
camelcase@5.3.1: {}
+ camelcase@7.0.1: {}
+
camelcase@8.0.0: {}
caniuse-lite@1.0.30001739: {}
@@ -20022,6 +20546,10 @@ snapshots:
dependencies:
traverse: 0.3.9
+ chalk-template@0.4.0:
+ dependencies:
+ chalk: 4.1.2
+
chalk@4.1.0:
dependencies:
ansi-styles: 4.3.0
@@ -20032,6 +20560,8 @@ snapshots:
ansi-styles: 4.3.0
supports-color: 7.2.0
+ chalk@5.0.1: {}
+
chalk@5.4.1: {}
chalk@5.6.2: {}
@@ -20112,10 +20642,16 @@ snapshots:
clipboard-copy@4.0.1: {}
- clipboardy@4.0.0:
+ clipboardy@3.0.0:
dependencies:
- execa: 8.0.1
- is-wsl: 3.1.0
+ arch: 2.2.0
+ execa: 5.1.1
+ is-wsl: 2.2.0
+
+ clipboardy@4.0.0:
+ dependencies:
+ execa: 8.0.1
+ is-wsl: 3.1.0
is64bit: 2.0.0
cliui@7.0.4:
@@ -20144,6 +20680,8 @@ snapshots:
cmd-shim@7.0.0: {}
+ collapse-white-space@2.1.0: {}
+
color-convert@1.9.3:
dependencies:
color-name: 1.1.3
@@ -20201,8 +20739,12 @@ snapshots:
commander@2.20.3: {}
+ commander@4.1.1: {}
+
commander@6.2.1: {}
+ commander@7.2.0: {}
+
commander@9.5.0: {}
comment-json@4.2.5:
@@ -20303,6 +20845,8 @@ snapshots:
console-control-strings@1.1.0: {}
+ content-disposition@0.5.2: {}
+
content-disposition@0.5.4:
dependencies:
safe-buffer: 5.2.1
@@ -20611,6 +21155,8 @@ snapshots:
dependencies:
mimic-fn: 4.0.0
+ debounce@1.2.1: {}
+
debug@2.6.9:
dependencies:
ms: 2.0.0
@@ -21086,6 +21632,20 @@ snapshots:
string.prototype.trimleft: 2.1.3
string.prototype.trimright: 2.1.3
+ esast-util-from-estree@2.0.0:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ devlop: 1.1.0
+ estree-util-visit: 2.0.0
+ unist-util-position-from-estree: 2.0.0
+
+ esast-util-from-js@2.0.1:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ acorn: 8.15.0
+ esast-util-from-estree: 2.0.0
+ vfile-message: 4.0.3
+
esbuild@0.21.5:
optionalDependencies:
'@esbuild/aix-ppc64': 0.21.5
@@ -21446,8 +22006,35 @@ snapshots:
estraverse@5.3.0: {}
+ estree-util-attach-comments@3.0.0:
+ dependencies:
+ '@types/estree': 1.0.8
+
+ estree-util-build-jsx@3.0.1:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ devlop: 1.1.0
+ estree-util-is-identifier-name: 3.0.0
+ estree-walker: 3.0.3
+
estree-util-is-identifier-name@3.0.0: {}
+ estree-util-scope@1.0.0:
+ dependencies:
+ '@types/estree': 1.0.8
+ devlop: 1.1.0
+
+ estree-util-to-js@2.0.0:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ astring: 1.9.0
+ source-map: 0.7.6
+
+ estree-util-visit@2.0.0:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/unist': 3.0.3
+
estree-walker@2.0.2: {}
estree-walker@3.0.3:
@@ -22342,6 +22929,27 @@ snapshots:
dependencies:
'@types/hast': 3.0.4
+ hast-util-to-estree@3.1.3:
+ dependencies:
+ '@types/estree': 1.0.8
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ comma-separated-tokens: 2.0.3
+ devlop: 1.1.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.1
+ mdast-util-mdx-jsx: 3.2.0
+ mdast-util-mdxjs-esm: 2.0.1
+ property-information: 7.1.0
+ space-separated-tokens: 2.0.2
+ style-to-js: 1.1.17
+ unist-util-position: 5.0.0
+ zwitch: 2.0.4
+ transitivePeerDependencies:
+ - supports-color
+
hast-util-to-html@9.0.5:
dependencies:
'@types/hast': 3.0.4
@@ -22867,6 +23475,8 @@ snapshots:
is-plain-object@5.0.0: {}
+ is-port-reachable@4.0.0: {}
+
is-potential-custom-element-name@1.0.1: {}
is-property@1.0.2: {}
@@ -23614,6 +24224,10 @@ snapshots:
map-obj@5.0.2: {}
+ markdown-extensions@2.0.0: {}
+
+ markdown-table@3.0.4: {}
+
markdown-to-jsx@7.7.4(react@19.1.1):
dependencies:
react: 19.1.1
@@ -23631,6 +24245,13 @@ snapshots:
maxstache@1.0.7: {}
+ mdast-util-find-and-replace@3.0.2:
+ dependencies:
+ '@types/mdast': 4.0.4
+ escape-string-regexp: 5.0.0
+ unist-util-is: 6.0.0
+ unist-util-visit-parents: 6.0.1
+
mdast-util-from-markdown@2.0.2:
dependencies:
'@types/mdast': 4.0.4
@@ -23648,6 +24269,63 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ mdast-util-gfm-autolink-literal@2.0.1:
+ dependencies:
+ '@types/mdast': 4.0.4
+ ccount: 2.0.1
+ devlop: 1.1.0
+ mdast-util-find-and-replace: 3.0.2
+ micromark-util-character: 2.1.1
+
+ mdast-util-gfm-footnote@2.1.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ micromark-util-normalize-identifier: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm-strikethrough@2.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm-table@2.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ markdown-table: 3.0.4
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm-task-list-item@2.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-gfm@3.1.0:
+ dependencies:
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-gfm-autolink-literal: 2.0.1
+ mdast-util-gfm-footnote: 2.1.0
+ mdast-util-gfm-strikethrough: 2.0.0
+ mdast-util-gfm-table: 2.0.0
+ mdast-util-gfm-task-list-item: 2.0.0
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
mdast-util-mdx-expression@2.0.1:
dependencies:
'@types/estree-jsx': 1.0.5
@@ -23676,6 +24354,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ mdast-util-mdx@3.0.0:
+ dependencies:
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-mdx-expression: 2.0.1
+ mdast-util-mdx-jsx: 3.2.0
+ mdast-util-mdxjs-esm: 2.0.1
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
mdast-util-mdxjs-esm@2.0.1:
dependencies:
'@types/estree-jsx': 1.0.5
@@ -23781,6 +24469,115 @@ snapshots:
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
+ micromark-extension-gfm-autolink-literal@2.1.0:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-sanitize-uri: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-footnote@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-core-commonmark: 2.0.3
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-sanitize-uri: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-strikethrough@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-util-chunked: 2.0.1
+ micromark-util-classify-character: 2.0.1
+ micromark-util-resolve-all: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-table@2.1.1:
+ dependencies:
+ devlop: 1.1.0
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-tagfilter@2.0.0:
+ dependencies:
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm-task-list-item@2.1.0:
+ dependencies:
+ devlop: 1.1.0
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-gfm@3.0.0:
+ dependencies:
+ micromark-extension-gfm-autolink-literal: 2.1.0
+ micromark-extension-gfm-footnote: 2.1.0
+ micromark-extension-gfm-strikethrough: 2.1.0
+ micromark-extension-gfm-table: 2.1.1
+ micromark-extension-gfm-tagfilter: 2.0.0
+ micromark-extension-gfm-task-list-item: 2.1.0
+ micromark-util-combine-extensions: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-mdx-expression@3.0.1:
+ dependencies:
+ '@types/estree': 1.0.8
+ devlop: 1.1.0
+ micromark-factory-mdx-expression: 2.0.3
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-events-to-acorn: 2.0.3
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+
+ micromark-extension-mdx-jsx@3.0.2:
+ dependencies:
+ '@types/estree': 1.0.8
+ devlop: 1.1.0
+ estree-util-is-identifier-name: 3.0.0
+ micromark-factory-mdx-expression: 2.0.3
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-events-to-acorn: 2.0.3
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+ vfile-message: 4.0.3
+
+ micromark-extension-mdx-md@2.0.0:
+ dependencies:
+ micromark-util-types: 2.0.2
+
+ micromark-extension-mdxjs-esm@3.0.0:
+ dependencies:
+ '@types/estree': 1.0.8
+ devlop: 1.1.0
+ micromark-core-commonmark: 2.0.3
+ micromark-util-character: 2.1.1
+ micromark-util-events-to-acorn: 2.0.3
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+ unist-util-position-from-estree: 2.0.0
+ vfile-message: 4.0.3
+
+ micromark-extension-mdxjs@3.0.0:
+ dependencies:
+ acorn: 8.15.0
+ acorn-jsx: 5.3.2(acorn@8.15.0)
+ micromark-extension-mdx-expression: 3.0.1
+ micromark-extension-mdx-jsx: 3.0.2
+ micromark-extension-mdx-md: 2.0.0
+ micromark-extension-mdxjs-esm: 3.0.0
+ micromark-util-combine-extensions: 2.0.1
+ micromark-util-types: 2.0.2
+
micromark-factory-destination@2.0.1:
dependencies:
micromark-util-character: 2.1.1
@@ -23794,6 +24591,18 @@ snapshots:
micromark-util-symbol: 2.0.1
micromark-util-types: 2.0.2
+ micromark-factory-mdx-expression@2.0.3:
+ dependencies:
+ '@types/estree': 1.0.8
+ devlop: 1.1.0
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-events-to-acorn: 2.0.3
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+ unist-util-position-from-estree: 2.0.0
+ vfile-message: 4.0.3
+
micromark-factory-space@2.0.1:
dependencies:
micromark-util-character: 2.1.1
@@ -23846,6 +24655,16 @@ snapshots:
micromark-util-encode@2.0.1: {}
+ micromark-util-events-to-acorn@2.0.3:
+ dependencies:
+ '@types/estree': 1.0.8
+ '@types/unist': 3.0.3
+ devlop: 1.1.0
+ estree-util-visit: 2.0.0
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.2
+ vfile-message: 4.0.3
+
micromark-util-html-tag-name@2.0.1: {}
micromark-util-normalize-identifier@2.0.1:
@@ -23900,10 +24719,16 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
+ mime-db@1.33.0: {}
+
mime-db@1.52.0: {}
mime-db@1.54.0: {}
+ mime-types@2.1.18:
+ dependencies:
+ mime-db: 1.33.0
+
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
@@ -24042,6 +24867,8 @@ snapshots:
mri@1.2.0: {}
+ mrmime@2.0.1: {}
+
ms@2.0.0: {}
ms@2.1.3: {}
@@ -24100,6 +24927,12 @@ snapshots:
safe-buffer: 5.1.2
sqlstring: 2.3.1
+ mz@2.7.0:
+ dependencies:
+ any-promise: 1.3.0
+ object-assign: 4.1.1
+ thenify-all: 1.6.0
+
named-placeholders@1.1.3:
dependencies:
lru-cache: 7.18.3
@@ -24632,6 +25465,8 @@ snapshots:
openapi-typescript-helpers@0.0.15: {}
+ opener@1.5.2: {}
+
oppa@0.4.0:
dependencies:
chalk: 4.1.2
@@ -24897,6 +25732,8 @@ snapshots:
path-is-absolute@1.0.1: {}
+ path-is-inside@1.0.2: {}
+
path-key@3.1.1: {}
path-key@4.0.0: {}
@@ -24917,6 +25754,8 @@ snapshots:
path-to-regexp@0.1.12: {}
+ path-to-regexp@3.3.0: {}
+
path-to-regexp@6.3.0: {}
path-type@3.0.0:
@@ -25012,6 +25851,8 @@ snapshots:
sonic-boom: 4.2.0
thread-stream: 3.1.0
+ pirates@4.0.7: {}
+
piscina@4.8.0:
optionalDependencies:
'@napi-rs/nice': 1.1.1
@@ -25332,6 +26173,8 @@ snapshots:
dependencies:
safe-buffer: 5.2.1
+ range-parser@1.2.0: {}
+
range-parser@1.2.1: {}
rasterizehtml@1.3.1:
@@ -25471,6 +26314,12 @@ snapshots:
optionalDependencies:
react-dom: 19.1.1(react@19.1.1)
+ react-runner@1.0.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
+ dependencies:
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ sucrase: 3.35.0
+
react-style-singleton@2.2.3(@types/react@19.1.13)(react@19.1.1):
dependencies:
get-nonce: 1.0.1
@@ -25598,6 +26447,35 @@ snapshots:
real-require@0.2.0: {}
+ recma-build-jsx@1.0.0:
+ dependencies:
+ '@types/estree': 1.0.8
+ estree-util-build-jsx: 3.0.1
+ vfile: 6.0.3
+
+ recma-jsx@1.0.1(acorn@8.15.0):
+ dependencies:
+ acorn: 8.15.0
+ acorn-jsx: 5.3.2(acorn@8.15.0)
+ estree-util-to-js: 2.0.0
+ recma-parse: 1.0.0
+ recma-stringify: 1.0.0
+ unified: 11.0.5
+
+ recma-parse@1.0.0:
+ dependencies:
+ '@types/estree': 1.0.8
+ esast-util-from-js: 2.0.1
+ unified: 11.0.5
+ vfile: 6.0.3
+
+ recma-stringify@1.0.0:
+ dependencies:
+ '@types/estree': 1.0.8
+ estree-util-to-js: 2.0.0
+ unified: 11.0.5
+ vfile: 6.0.3
+
recursive-readdir@2.2.3:
dependencies:
minimatch: 3.1.2
@@ -25651,10 +26529,19 @@ snapshots:
unicode-match-property-ecmascript: 2.0.0
unicode-match-property-value-ecmascript: 2.2.0
+ registry-auth-token@3.3.2:
+ dependencies:
+ rc: 1.2.8
+ safe-buffer: 5.2.1
+
registry-auth-token@5.1.0:
dependencies:
'@pnpm/npm-conf': 2.3.1
+ registry-url@3.1.0:
+ dependencies:
+ rc: 1.2.8
+
registry-url@6.0.1:
dependencies:
rc: 1.2.8
@@ -25671,6 +26558,14 @@ snapshots:
hast-util-from-html: 2.0.3
unified: 11.0.5
+ rehype-recma@1.0.0:
+ dependencies:
+ '@types/estree': 1.0.8
+ '@types/hast': 3.0.4
+ hast-util-to-estree: 3.1.3
+ transitivePeerDependencies:
+ - supports-color
+
rehype-stringify@10.0.1:
dependencies:
'@types/hast': 3.0.4
@@ -25684,6 +26579,24 @@ snapshots:
core-js: 3.45.1
exit-hook: 4.0.0
+ remark-gfm@4.0.1:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-gfm: 3.1.0
+ micromark-extension-gfm: 3.0.0
+ remark-parse: 11.0.0
+ remark-stringify: 11.0.0
+ unified: 11.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ remark-mdx@3.1.1:
+ dependencies:
+ mdast-util-mdx: 3.0.0
+ micromark-extension-mdxjs: 3.0.0
+ transitivePeerDependencies:
+ - supports-color
+
remark-parse@11.0.0:
dependencies:
'@types/mdast': 4.0.4
@@ -25701,6 +26614,12 @@ snapshots:
unified: 11.0.5
vfile: 6.0.3
+ remark-stringify@11.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-to-markdown: 2.1.2
+ unified: 11.0.5
+
remove-trailing-separator@1.1.0: {}
repeat-string@1.6.1: {}
@@ -25930,6 +26849,16 @@ snapshots:
dependencies:
randombytes: 2.1.0
+ serve-handler@6.1.6:
+ dependencies:
+ bytes: 3.0.0
+ content-disposition: 0.5.2
+ mime-types: 2.1.18
+ minimatch: 3.1.2
+ path-is-inside: 1.0.2
+ path-to-regexp: 3.3.0
+ range-parser: 1.2.0
+
serve-static@1.16.2:
dependencies:
encodeurl: 2.0.0
@@ -25939,6 +26868,24 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ serve@14.2.5:
+ dependencies:
+ '@zeit/schemas': 2.36.0
+ ajv: 8.12.0
+ arg: 5.0.2
+ boxen: 7.0.0
+ chalk: 5.0.1
+ chalk-template: 0.4.0
+ clipboardy: 3.0.0
+ compression: 1.8.1
+ is-port-reachable: 4.0.0
+ serve-handler: 6.1.6
+ update-check: 1.5.4
+ transitivePeerDependencies:
+ - supports-color
+
+ server-only@0.0.1: {}
+
set-blocking@2.0.0: {}
set-cookie-parser@2.7.1: {}
@@ -26070,6 +27017,12 @@ snapshots:
dependencies:
is-arrayish: 0.3.2
+ sirv@2.0.4:
+ dependencies:
+ '@polka/url': 1.0.0-next.29
+ mrmime: 2.0.1
+ totalist: 3.0.1
+
sisteransi@1.0.5: {}
slash@2.0.0: {}
@@ -26452,6 +27405,16 @@ snapshots:
stylis@4.2.0: {}
+ sucrase@3.35.0:
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ commander: 4.1.1
+ glob: 10.4.5
+ lines-and-columns: 1.2.4
+ mz: 2.7.0
+ pirates: 4.0.7
+ ts-interface-checker: 0.1.13
+
superjson@2.0.0:
dependencies:
copy-anything: 3.0.5
@@ -26574,6 +27537,14 @@ snapshots:
text-table@0.2.0: {}
+ thenify-all@1.6.0:
+ dependencies:
+ thenify: 3.3.1
+
+ thenify@3.3.1:
+ dependencies:
+ any-promise: 1.3.0
+
thread-stream@3.1.0:
dependencies:
real-require: 0.2.0
@@ -26645,6 +27616,8 @@ snapshots:
tomlify-j0.4@3.0.0: {}
+ totalist@3.0.1: {}
+
tough-cookie@6.0.0:
dependencies:
tldts: 7.0.16
@@ -26686,6 +27659,8 @@ snapshots:
picomatch: 4.0.3
typescript: 5.9.2
+ ts-interface-checker@0.1.13: {}
+
ts-node@10.9.2(@types/node@22.18.6)(typescript@5.9.2):
dependencies:
'@cspotcode/source-map-support': 0.8.1
@@ -26906,6 +27881,10 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
+ unist-util-position-from-estree@2.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
unist-util-position@5.0.0:
dependencies:
'@types/unist': 3.0.3
@@ -27009,6 +27988,11 @@ snapshots:
escalade: 3.2.0
picocolors: 1.1.1
+ update-check@1.5.4:
+ dependencies:
+ registry-auth-token: 3.3.2
+ registry-url: 3.1.0
+
update-notifier@7.3.1:
dependencies:
boxen: 8.0.1
@@ -27047,6 +28031,10 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.13
+ use-editable@2.3.3(react@19.1.1):
+ dependencies:
+ react: 19.1.1
+
use-sidecar@1.1.3(@types/react@19.1.13)(react@19.1.1):
dependencies:
detect-node-es: 1.1.0
@@ -27253,6 +28241,25 @@ snapshots:
webidl-conversions@8.0.0: {}
+ webpack-bundle-analyzer@4.10.1:
+ dependencies:
+ '@discoveryjs/json-ext': 0.5.7
+ acorn: 8.15.0
+ acorn-walk: 8.3.4
+ commander: 7.2.0
+ debounce: 1.2.1
+ escape-string-regexp: 4.0.0
+ gzip-size: 6.0.0
+ html-escaper: 2.0.2
+ is-plain-object: 5.0.0
+ opener: 1.5.2
+ picocolors: 1.1.1
+ sirv: 2.0.4
+ ws: 7.5.10
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
webpack-sources@3.3.3: {}
webpack@5.101.3(jiti@2.5.1):
@@ -27374,6 +28381,10 @@ snapshots:
dependencies:
string-width: 4.2.3
+ widest-line@4.0.1:
+ dependencies:
+ string-width: 5.1.2
+
widest-line@5.0.0:
dependencies:
string-width: 7.2.0
@@ -27463,6 +28474,8 @@ snapshots:
type-fest: 0.4.1
write-json-file: 3.2.0
+ ws@7.5.10: {}
+
ws@8.18.1: {}
ws@8.18.3: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index d7baa4c3b..773bfc338 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -2,6 +2,7 @@ packages:
- 'packages/*'
- 'apps/*'
- 'test/*'
+ - 'docs'
overrides:
# This is a workaround for the issue when the same type refers to @types/eslint for one instance
# and to eslint for another instance, causing a conflict.
diff --git a/tsconfig.json b/tsconfig.json
index 68dc21c74..0a1e6f5e3 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -17,7 +17,8 @@
{ "path": "packages/code-infra" },
{ "path": "packages/docs-infra" },
{ "path": "tsconfig.node.json" },
- { "path": "scripts" }
+ { "path": "scripts" },
+ { "path": "docs" }
],
"files": []
}