# API Docs Builder Spec Ground-truth specification for the API docs builder pipeline: from TypeScript source code to rendered documentation tables. When implementation diverges from this spec, this spec wins. Inspired by [Base UI](https://github.com/mui/base-ui)'s API reference system. Base UI generates one JSON file per component part, and each part gets its own props and data-attributes tables. Our system aspires to the same philosophy but has a known architectural limitation: a single Props/State interface per component at the core level means only one part (the "primary") can own core-level props and state. Non-primary parts get shared data attributes, custom React-specific props, and a description. **Principles:** - **Convention over configuration.** The builder infers structure from file naming and placement. No config files, no explicit annotations for standard cases. Follow the conventions and things just work. - **Spec wins.** When implementation diverges from this spec, the spec is the source of truth. **Scope:** This spec covers the builder (extraction + JSON generation), the reference model layer (JSON → heading/section structure), TOC integration, and rendered output (tables + disclosure panels). It does not cover CSS styling, demo scaffolding, or MDX page authoring. --- ## 1. Sample Inputs These fictional examples are the source-of-truth for expected behavior throughout this spec. ### 1a. Single-part component: `ToggleButton` **Core file** — `packages/core/src/core/ui/toggle-button/toggle-button-core.ts`: ```ts interface ToggleButtonProps { /** Whether the button is disabled. */ disabled: boolean; /** Custom label for the button. */ label: string | ((state: ToggleButtonState) => string); } interface ToggleButtonState { /** Whether the toggle is pressed. */ pressed: boolean; /** Whether the button is disabled. */ disabled: boolean; } class ToggleButtonCore { static readonly defaultProps = { disabled: false, label: '', } as const; } ``` **Data attributes file** — `packages/core/src/core/ui/toggle-button/toggle-button-data-attrs.ts`: ```ts import type { StateAttrMap } from '../types'; export const ToggleButtonDataAttrs = { /** Present when the toggle is pressed. */ pressed: 'data-pressed', /** Present when the button is disabled. */ disabled: 'data-disabled', } as const satisfies StateAttrMap; ``` **HTML element file** — `packages/html/src/ui/toggle-button/toggle-button-element.ts`: ```ts export class ToggleButtonElement extends ... { static readonly tagName = 'media-toggle-button'; } ``` ### 1b. Multi-part component: `Meter` **Core file** — `packages/core/src/core/ui/meter/meter-core.ts`: ```ts interface MeterProps { /** Minimum value. */ min: number; /** Maximum value. */ max: number; /** Custom label for accessibility. */ label: string | ((state: MeterState) => string); } interface MeterState { /** Current value as a percentage (0–1). */ percentage: number; /** The fill level. */ fillState: 'empty' | 'partial' | 'full'; } class MeterCore { static readonly defaultProps = { min: 0, max: 100, label: '', } as const; } ``` **Data attributes file** — `packages/core/src/core/ui/meter/meter-data-attrs.ts`: ```ts import type { StateAttrMap } from '../types'; export const MeterDataAttrs = { /** Current percentage as a string. */ percentage: 'data-percentage', /** The fill level. */ fillState: 'data-fill-state', } as const satisfies StateAttrMap; ``` **HTML element files:** - `packages/html/src/ui/meter/meter-element.ts` → `static tagName = 'media-meter'` - `packages/html/src/ui/meter/meter-track-element.ts` → `static tagName = 'media-meter-track'` - `packages/html/src/ui/meter/meter-fill-element.ts` → `static tagName = 'media-meter-fill'` **React parts index** — `packages/react/src/ui/meter/index.parts.ts`: ```ts export { default as Track } from './Track'; export { default as Fill } from './Fill'; export { default as Indicator } from './Indicator'; ``` **React component JSDoc:** ```tsx // Track.tsx /** The track area of the meter. Renders a `
` element. */ export default function Track(...) { ... } // Fill.tsx /** The filled portion of the meter. Renders a `
` element. */ export default function Fill(...) { ... } // Indicator.tsx — no matching HTML element file /** A visual indicator for the current value. Renders a `` element. */ export default function Indicator(...) { ... } ``` ### 1c. Single-overload util: `useVolume` ```ts /** * Subscribe to the player's volume state. */ export function useVolume(options?: { muted?: boolean }): { /** Current volume level (0–1). */ volume: number; /** Whether audio is muted. */ muted: boolean; /** Set the volume level. */ setVolume: (level: number) => void; }; ``` Exported from `packages/react/src/index.ts` → framework: `react`. ### 1d. Multi-overload util: `createPlayer` ```ts /** * Create a player instance with typed store, Provider component, Container, and hooks. */ /** @label Video */ export function createPlayer(config: CreatePlayerConfig): CreatePlayerResult; /** @label Audio */ export function createPlayer(config: CreatePlayerConfig): CreatePlayerResult; ``` Exported from `packages/react/src/index.ts` → framework: `react`. Return types differ (`VideoPlayerStore` vs `AudioPlayerStore`). Each overload uses the optional `@label` JSDoc tag to give it a descriptive heading in the docs (see §2d). ### 1e. Context: `playerContext` **Source** — `packages/html/src/player/context.ts`: ```ts /** @public The default player context instance for consuming the player store in controllers. */ export const playerContext = createContext(PLAYER_CONTEXT_KEY); ``` Exported from `packages/html/src/index.ts` → framework: `html`. Discovered via `@public` JSDoc. This is a non-function, non-controller export — a context value. --- ## 2. Builder Pipeline ### 2a. Component discovery The builder scans `packages/core/src/core/ui/` for directories. For each directory with kebab-name `{name}`: | File | Location | Required | |------|----------|----------| | Core | `packages/core/src/core/ui/{name}/{name}-core.ts` | Yes | | Data attrs | `packages/core/src/core/ui/{name}/{name}-data-attrs.ts` | No | | CSS vars | `packages/core/src/core/ui/{name}/{name}-css-vars.ts` | No | | HTML element | `packages/html/src/ui/{name}/{name}-element.ts` | No | | React parts index | `packages/react/src/ui/{name}/index.parts.ts` | No | **Kebab-to-PascalCase conversion:** `toggle-button` → `ToggleButton`. For cases where standard conversion fails (e.g., `pip-button` → `PiPButton`), a `NAME_OVERRIDES` map provides the correct PascalCase name. **Multi-part detection:** A component is multi-part if and only if `index.parts.ts` exists. **Domain variant components:** Components like TimeSlider and VolumeSlider that share base logic (e.g., from `slider/`) must still have their own directories under `core/ui/`. The builder discovers components by directory — files nested inside a shared directory (like `slider/time-slider-core.ts`) won't be found. ### 2b. Component extraction #### Single-part components Extract from the three source files and merge into one reference object. | Source | Extracts | |--------|----------| | Core file | Props interface members (if present), State interface members (if present), `defaultProps` values (if present) | | Data attrs file | Data attribute names, JSDoc descriptions, and inferred types (if file exists) | | CSS vars file | CSS custom property names and JSDoc descriptions (if file exists) | | HTML element file | `static tagName` value (if file exists) | **Naming conventions the builder depends on:** | Symbol | Expected name | |--------|---------------| | Props interface | `{PascalCase}Props` | | State interface | `{PascalCase}State` | | Core class | `{PascalCase}Core` | | Data attrs export | `{PascalCase}DataAttrs` | | CSS vars export | `{PascalCase}CSSVars` | | HTML element class | `{PascalCase}Element` | **All symbols are optional.** Only the Core class is required. If a component has no Props interface, `props` is `{}`. If it has no State interface, `state` is `{}`. If the Core class has no `defaultProps`, no defaults are populated. If both Props and State are missing, the component is skipped with a warning. **Extraction conventions** (apply to both Props and State members): - Members named `ref` are auto-skipped (React internal). - Members with `@ignore` JSDoc tag are skipped. **Missing-symbol behavior:** | Missing symbol | Behavior | Output | |---|---|---| | Props interface only | Silent | `props: {}` | | State interface only | Silent | `state: {}` | | Both Props and State | Warn, skip component | Component omitted | | `defaultProps` static | Silent | Props have no `default` field | | Data-attrs file/export | Silent | `dataAttributes: {}` | | CSS-vars file/export | Silent | `cssCustomProperties: {}` | | JSDoc on a data attribute | Silent | `description: ""` (empty string) | | HTML element file | Silent | No `platforms.html` section | This applies uniformly to single-part and multi-part component extraction. **Data attribute type inference:** The builder infers data attribute types from the `StateAttrMap` constraint on the data-attrs export. Every data-attrs file follows the pattern: ```ts export const FooDataAttrs = { ... } as const satisfies StateAttrMap; ``` The builder uses the TypeScript type checker to: 1. Extract the `State` type argument from the `satisfies StateAttrMap` expression 2. Resolve each property key's type on the state interface 3. Format the resolved type as a display string | State property type | Inferred display type | Example | |---|---|---| | `boolean` | _(omitted)_ | `data-paused` — presence/absence, no type shown | | String literal union | The union | `'off' \| 'low' \| 'medium' \| 'high'` | | Named type alias (resolves to union) | Expanded literals | `VolumeLevel` → `'off' \| 'low' \| 'medium' \| 'high'` | | `string` | `string` | Rare — freeform string value | | `number` | `number` | Rare — numeric attribute | Boolean attributes are presence/absence by convention (the runtime uses `setAttribute`/ `removeAttribute`), so their type is omitted from the output to avoid noise. Non-boolean types are included to show the enumerated values the attribute can take. **Fallback:** If the `satisfies` expression is absent or the type checker cannot resolve the state type, the builder falls back to the existing `@type` JSDoc tag extraction. Manual `@type` tags are no longer needed when the `satisfies StateAttrMap` pattern is used. #### Multi-part components The builder discovers parts from `index.parts.ts` and matches them to HTML element files. **Re-exported parts:** When `index.parts.ts` re-exports parts from another component (source path doesn't start with `./`), the builder resolves the re-export back to its origin. It parses the origin's `index.parts.ts`, matches each re-exported name to the original local export, then derives the kebab segment and HTML element file from the **origin component** — not the current one. Re-exported parts are never primary. For example, TimeSlider re-exports Buffer, Fill, Thumb, Track, and Value from `../slider/index.parts`; each resolves to the Slider component's HTML element files (`slider-buffer-element.ts`, etc.). **Single-part fallback:** When all exports are local and filtering leaves only one part, the component uses single-part mode. The remaining part (typically Root) becomes the top-level component — its props/state/data-attrs/CSS-vars are promoted to the component level, not nested under `parts`. Components with re-exported parts (like TimeSlider and VolumeSlider) always produce multi-part output since the re-exports are resolved rather than filtered. **Primary vs. sub-part convention:** Every multi-part component has one **primary part** and one or more **sub-parts**. **Primary part:** The part whose React source file instantiates the component's own Core class (matches `new {ComponentName}Core\b`). This captures the architectural relationship — the primary part owns the Core — and is immune to import ordering and framework-divergent element structures. Sub-part element files use the naming convention `{component}-{part}-element.ts` (e.g., `time-group-element.ts`) for HTML tag resolution. **Part-to-element matching:** For each local named export in `index.parts.ts`: 1. Derive kebab segment from the export's source path (e.g., `./time-value` → `value`) 2. Look for `{component}-{part}-element.ts` in the HTML directory 3. If found → sub-part (gets its own tag name) 4. If not found AND `{component}-element.ts` exists → check via Core-instantiation for primary For cases where the element file doesn't follow standard naming (e.g., Tooltip's Provider maps to `tooltip-group-element.ts`, not `tooltip-provider-element.ts`), a `PART_ELEMENT_OVERRIDES` map provides the correct filename. For re-exported parts: use the origin component's kebab and HTML directory for element file lookup. The element class name is derived from the filename convention (`kebabToPascal` of the basename, e.g., `slider-buffer-element.ts` → `SliderBufferElement`), not from the current component's PascalCase name. This same convention-based derivation is also used for local non-primary parts. **What the primary part gets:** The shared core Props, State, data attributes, CSS custom properties, and the root element's tag name. **What sub-parts get:** Their own tag name, a description (from React JSDoc), shared data attributes from the component's `*-data-attrs.ts` file (when the sub-part's React source references `stateAttrMap`), and custom props from the `{LocalName}Props` interface — own members plus members inherited from project-local interfaces, excluding `children` and React DOM attributes. State and CSS custom properties remain empty. For re-exported sub-parts, data attributes come from the **origin** component's data-attrs file (e.g., TimeSlider.Fill uses Slider's data-attrs, not TimeSlider's), because the builder can't resolve spread entries and the origin file has the complete set that sub-parts inherit. **What the top-level component gets:** Empty props, state, dataAttributes, cssCustomProperties, and empty platforms. All meaningful data lives in the `parts` record. **Framework-divergent parts:** Parts discovered from `index.parts.ts` always get `platforms.react`. Parts with a matching HTML element file also get `platforms.html`. The renderer filters parts by framework — only parts with the current framework's platform entry are shown. This handles cases like Popover where Arrow, Popup, and Trigger are React-only compound parts with no HTML element counterparts. > **Known limitation:** Our architecture has a single Props/State interface per component at the > core level, so only the primary part can own core-level props and state. Sub-parts can declare > custom React-specific props (e.g., `SliderValueProps.type`), which the builder extracts from > the React source. In Base UI, each part has its own props independently. ### 2c. Util discovery The builder scans a fixed set of entry points: | Entry point | Framework | |-------------|-----------| | `packages/react/src/index.ts` | `react` | | `packages/store/src/react/hooks/index.ts` | `react` | | `packages/html/src/index.ts` | `html` | | `packages/store/src/html/controllers/index.ts` | `html` | | `packages/core/src/dom/store/selectors.ts` | _(none — framework-agnostic)_ | | `packages/store/src/core/selector.ts` | _(none — framework-agnostic)_ | **Re-export scoping:** For each entry point, the builder resolves local re-exports (relative import paths starting with `./`) and scans the resolved modules. External package re-exports (e.g., `export * from '@videojs/core/dom'`) are not followed — cross-package exports are discovered via their own dedicated entry points instead. This prevents duplication and incorrect framework tagging. **Inclusion rules** — An export is included if it matches any naming convention OR has `@public` JSDoc: | Pattern | Rule | Category | |---------|------|----------| | `use*` (capital 4th char) + function | Hook | `react` | | `select*` (capital 7th char) + function | Selector | varies | | `*Controller` (non-function) | Controller | `html` | | `create*` + function | Factory | varies | | `@public` JSDoc tag | Explicit inclusion | varies | **Note:** The raw TS AST fallback (used when TAE fails on a module) relaxes the function check for `select*` — it includes by name alone, since type information isn't reliably available in that path. **Display name:** The export name is used as-is, except `create*Mixin` factories strip the `create` prefix (e.g., `createProviderMixin` → `ProviderMixin`). **Leaf module scanning:** When an entry point has no relative re-exports (i.e., `resolveLocalModules` returns an empty list), the file itself is scanned directly. **Slug generation:** kebab-case of the display name. If two frameworks produce the same slug, React keeps the bare slug and HTML gets prefixed with `html-` (e.g., `create-player` vs `html-create-player`). React entries must come before HTML entries in `UTIL_ENTRY_POINTS` to ensure this ordering. ### 2d. Util extraction **Functions (hooks, factories):** Extract call signatures via TypeScript API. Each signature becomes an overload with parameters and return value. **Controllers:** Extract constructor signatures. Each constructor becomes an overload. The return value type uses `ClassName` when the class has type parameters, or just `ClassName` when it has none. The return value describes the controller's public interface (e.g., `value` property). **Contexts (non-function, non-controller):** These are non-function, non-controller exports included via `@public` JSDoc. They produce a single overload with empty parameters and a `returnValue` containing the type string. **All overloads are preserved.** When a function or constructor has multiple overload signatures, each becomes a separate entry in the `overloads` array. This applies uniformly to function call signatures and class constructor signatures. **Overload labels (`@label`):** If an overload signature has a `@label` JSDoc tag, the builder extracts its value as the overload's `label`. This is optional — overloads without `@label` get no label and fall back to "Overload {N}" headings in the rendered output. **Degraded type repair:** After TAE extraction, the builder cross-references param and return types against the raw TS AST. When a formatted type contains `any` (word boundary) or `__type` — indicators that TAE couldn't resolve the type — the builder replaces it with the source annotation text from the raw AST declaration. This handles types TAE can't resolve (e.g., cross-package interfaces like `Media`, ESSymbol-based types like `PlayerContext`). ### 2e. Type formatting Every type string goes through two stages: **Stage 1: Format** — Convert the TypeScript type node to a human-readable string. | TS construct | Formatted as | Example | |--------------|-------------|---------| | Primitives | Lowercase name | `boolean`, `string`, `number` | | String literals | Single-quoted | `'current'` | | Unions | Pipe-separated | `'current' \| 'duration' \| 'remaining'` | | Objects | Inline notation | `{ volume: number; muted: boolean }` | | Arrays | Bracket suffix | `string[]` | | Functions | Arrow notation | `((level: number) => void)` | | Tuples | Bracket notation | `[string, number]` | | Empty object type | `object` | `object` (not `{}`) | | Type parameter (small constraint) | Constraint expansion | `string` for `T extends string` | | Type parameter (large constraint, >5 union members) | Parameter name | `TagName` for `TagName extends keyof JSX.IntrinsicElements` | **Stage 2: Abbreviate** — Shorten complex types for display. The abbreviated form goes in `type`; the full form goes in `detailedType` (shown in the disclosure panel). Abbreviation checks rules in the following order. The first match wins: | # | Rule | Condition | Abbreviated to | `detailedType` | |---|------|-----------|---------------|----------------| | 1 | Pure function | Type contains `=>`, and either no `\|` or is a single function type (paren-depth matching detects `(params) => return \| union` vs `(fn) \| undefined`) | `function` | Full signature | | 2 | `on*` / `get*` callback | Name matches, type contains `=>` | `function` | Full signature | | 3 | `className` | Name matches, type contains `=>` | `string \| function` | Full union | | 4 | `style` | Name matches, type contains `=>` | `CSSProperties \| function` | Full union | | 5 | `render` | Name matches, type contains `=>` | `ReactElement \| function` | Full union | | 6 | Simple primitive | `boolean`, `string`, `number` | _(no abbreviation)_ | _(omitted)_ | | 7 | Object literal (> 40 chars) | Starts with `{ `, length > 40 | `object` | Full inline notation | | 8 | Short union (< 3 members AND < 40 chars, no fn) | — | _(no abbreviation)_ | _(omitted)_ | | 9 | Union with a function member | Type contains `=>` and `\|` | Non-fn members `\| function` | Full union | | 10 | Long type (> 40 chars) | — | _(as-is, truncated)_ | Full type | | 11 | Fallback | Everything else | _(no abbreviation)_ | _(omitted)_ | `detailedType` is only present when abbreviation occurred. If the type is short enough to display as-is, `detailedType` is omitted. **Union member ordering:** `null`, `undefined`, and `any` sort to the end. **Default values:** Stored as string representations of the literal value. Examples: `'false'`, `"''"` (empty string), `'0'`, `'null'`, `'[]'`. --- ## 3. JSON Schemas These are the intermediate format between builder and front-end. Defined as Zod schemas in `site/src/types/`. ### 3a. Component reference Output: `site/src/content/generated-component-reference/{kebab-name}.json` ``` ComponentReference ├── name: string — PascalCase (e.g., "ToggleButton") ├── description?: string — JSDoc description of the component ├── props: Record — Empty {} for multi-part top-level ├── state: Record — Empty {} for multi-part top-level ├── dataAttributes: Record ├── cssCustomProperties: Record ├── platforms │ ├── html? │ │ └── tagName: string — e.g., "media-toggle-button" │ └── react? — Present for React-discovered parts (object, no fields) └── parts?: Record — Only for multi-part components └── [partId] ├── name: string — PascalCase part name (e.g., "Track") ├── description?: string — From React component JSDoc ├── props: Record ├── state: Record ├── dataAttributes: Record ├── cssCustomProperties: Record └── platforms ├── html? │ └── tagName: string └── react? — Always present (parts come from index.parts.ts) ``` **CSSVarDef:** ``` └── description: string — JSDoc description of the CSS custom property ``` **PropDef:** ``` ├── type: string — Abbreviated type for display ├── detailedType?: string — Full type (only if abbreviated) ├── description?: string — JSDoc description ├── default?: string — String representation of default value └── required?: boolean — Only present when true ``` **StateDef:** ``` ├── type: string ├── detailedType?: string └── description?: string ``` **DataAttrDef:** ``` ├── description: string ├── type?: string — Inferred from state type. Omitted for boolean (presence/absence). │ Shows enumerated values for non-boolean types (e.g., "'empty' | 'partial' | 'full'"). │ Abbreviated via the same rules as PropDef/StateDef (§2e Stage 2). └── detailedType?: string — Full type when abbreviation occurred. Same semantics as PropDef.detailedType. ``` ### 3b. Util reference Output: `site/src/content/generated-util-reference/{slug}.json` ``` UtilReference ├── name: string — Display name (e.g., "useVolume") ├── description?: string — JSDoc description ├── frameworks?: string[] — e.g., ["react"] or ["html"]; omitted if agnostic └── overloads: UtilOverload[] — At least one └── [n] ├── label?: string — From @label JSDoc tag (e.g., "Video") ├── description?: string — Overload-specific description ├── parameters: Record └── returnValue: ReturnValue ``` **ParamDef** — Same shape as PropDef: ``` ├── type: string ├── detailedType?: string ├── description?: string ├── default?: string — Default parameter value (e.g., "0", "'muted'") └── required?: boolean ``` **ReturnValue:** ``` ├── type: string — e.g., "object", "void", "boolean" ├── detailedType?: string ├── description?: string — Used when return is a simple type (no fields) └── fields?: Record — Used when return is an object └── [fieldName] ├── type: string ├── detailedType?: string └── description?: string ``` **Cleanup rules:** Optional fields are omitted from JSON when undefined. `required: false` is omitted (absence means not required). This keeps JSON files small. --- ## 4. Expected JSON Output for Sample Inputs ### 4a. ToggleButton (single-part component) ```json { "name": "ToggleButton", "props": { "disabled": { "type": "boolean", "description": "Whether the button is disabled.", "default": "false" }, "label": { "type": "string | function", "detailedType": "string | ((state: ToggleButtonState) => string)", "description": "Custom label for the button.", "default": "''" } }, "state": { "pressed": { "type": "boolean", "description": "Whether the toggle is pressed." }, "disabled": { "type": "boolean", "description": "Whether the button is disabled." } }, "dataAttributes": { "data-pressed": { "description": "Present when the toggle is pressed." }, "data-disabled": { "description": "Present when the button is disabled." } }, "cssCustomProperties": {}, "platforms": { "html": { "tagName": "media-toggle-button" } } } ``` ### 4b. Meter (multi-part component) ```json { "name": "Meter", "props": {}, "state": {}, "dataAttributes": {}, "cssCustomProperties": {}, "platforms": {}, "parts": { "indicator": { "name": "Indicator", "description": "A visual indicator for the current value. Renders a `` element.", "props": { "min": { "type": "number", "description": "Minimum value.", "default": "0" }, "max": { "type": "number", "description": "Maximum value.", "default": "100" }, "label": { "type": "string | function", "detailedType": "string | ((state: MeterState) => string)", "description": "Custom label for accessibility.", "default": "''" } }, "state": { "percentage": { "type": "number", "description": "Current value as a percentage (0–1)." } }, "dataAttributes": { "data-percentage": { "description": "Current percentage as a string.", "type": "number" }, "data-fill-state": { "description": "The fill level.", "type": "'empty' | 'partial' | 'full'" } }, "cssCustomProperties": {}, "platforms": { "html": { "tagName": "media-meter" }, "react": {} } }, "track": { "name": "Track", "description": "The track area of the meter. Renders a `
` element.", "props": {}, "state": {}, "dataAttributes": {}, "cssCustomProperties": {}, "platforms": { "html": { "tagName": "media-meter-track" }, "react": {} } }, "fill": { "name": "Fill", "description": "The filled portion of the meter. Renders a `
` element.", "props": {}, "state": {}, "dataAttributes": {}, "cssCustomProperties": {}, "platforms": { "html": { "tagName": "media-meter-fill" }, "react": {} } } } } ``` ### 4c. useVolume (single-overload hook) ```json { "name": "useVolume", "description": "Subscribe to the player's volume state.", "overloads": [ { "parameters": { "options": { "type": "object", "detailedType": "{ muted?: boolean }", "default": "{}" } }, "returnValue": { "type": "object", "detailedType": "{ volume: number; muted: boolean; setVolume: (level: number) => void }", "fields": { "volume": { "type": "number", "description": "Current volume level (0–1)." }, "muted": { "type": "boolean", "description": "Whether audio is muted." }, "setVolume": { "type": "function", "detailedType": "((level: number) => void)", "description": "Set the volume level." } } } } ], "frameworks": ["react"] } ``` ### 4d. createPlayer (multi-overload) Both overloads are preserved. Return types differ (`VideoPlayerStore` vs `AudioPlayerStore`). ```json { "name": "createPlayer", "description": "Create a player instance with typed store, Provider component, Container, and hooks.", "overloads": [ { "label": "Video", "description": "Create a player instance with typed store, Provider component, Container, and hooks.", "parameters": { "config": { "type": "CreatePlayerConfig", "required": true } }, "returnValue": { "type": "CreatePlayerResult", "fields": { "Provider": { "type": "React.FC" }, "Container": { "type": "function", "detailedType": "React.ForwardRefExoticComponent>" }, "usePlayer": { "type": "UsePlayerHook" } } } }, { "label": "Audio", "parameters": { "config": { "type": "CreatePlayerConfig", "required": true } }, "returnValue": { "type": "CreatePlayerResult", "fields": { "Provider": { "type": "React.FC" }, "Container": { "type": "function", "detailedType": "React.ForwardRefExoticComponent>" }, "usePlayer": { "type": "UsePlayerHook" } } } } ], "frameworks": ["react"] } ``` ### 4e. playerContext (context) ```json { "name": "playerContext", "description": "The default player context instance for consuming the player store in controllers.", "overloads": [ { "parameters": {}, "returnValue": { "type": "Context" } } ], "frameworks": ["html"] } ``` --- ## 5. Reference Model Layer The reference model transforms flat JSON into a structured heading/section model. This model is consumed by two places: 1. **Astro components** — to render headings and tables 2. **remarkConditionalHeadings** — to inject heading entries into the table of contents Both consume the same model, which prevents anchor drift (TOC links matching rendered heading IDs). ### 5a. Component reference model **Single-part** heading structure: ``` H2 "API Reference" id="api-reference" ├─ H3 "Props" id="props" (if props non-empty) ├─ H3 "State" id="state" (if state non-empty) ├─ H3 "Data attributes" id="data-attributes" (if dataAttributes non-empty) └─ H3 "CSS custom properties" id="css-custom-properties" (if cssCustomProperties non-empty) ``` **Multi-part** heading structure: ``` H2 "API Reference" id="api-reference" ├─ H3 "{Part.name}" (React) id="{partId}" │ or "{part.tagName}" (HTML) │ ├─ H4 "Props" id="{partId}-props" (if props non-empty) │ ├─ H4 "State" id="{partId}-state" (if state non-empty) │ ├─ H4 "Data attributes" id="{partId}-data-attributes" (if dataAttributes non-empty) │ └─ H4 "CSS custom properties" id="{partId}-css-custom-properties" (if cssCustomProperties non-empty) ├─ H3 next part... └─ ... ``` Multi-part H3 headings are framework-aware: React sees the PascalCase part name (e.g., "Track"), HTML sees the tag name (e.g., "media-meter-track"). The TOC emits both variants with `frameworks` metadata so the correct one displays per framework. **Framework filtering:** The TOC and rendered output only emit headings for parts the current framework supports (derived from `platforms` keys). React-only parts (those with `platforms.react` but no `platforms.html`) are hidden when viewing HTML docs. **Part ordering:** By default, parts render in JSON key order (primary first, then discovery order). The `` component accepts an optional `partOrder` prop — an array of part IDs (e.g., `["provider", "root", "trigger", "popup", "arrow"]`) that overrides the default order. This lets MDX authors match the anatomy. The reordering is applied inside `createComponentReferenceModel`, so both the rendered output and the TOC consume the same order. Parts not listed in `partOrder` appear after the listed ones in their original order. ### 5b. Util reference model **Single-overload** heading structure: ``` H2 "API Reference" id="api-reference" ├─ H3 "Parameters" id="parameters" (if params non-empty) └─ H3 "Return Value" id="return-value" ``` **Multi-overload** heading structure: ``` H2 "API Reference" id="api-reference" ├─ H3 "{label}" or "Overload 1" id="{slug}" or "overload-1" │ ├─ H4 "Parameters" id="{slug}-parameters" (if params non-empty) │ └─ H4 "Return Value" id="{slug}-return-value" ├─ H3 "{label}" or "Overload 2" id="{slug}" or "overload-2" │ ├─ H4 "Parameters" id="{slug}-parameters" │ └─ H4 "Return Value" id="{slug}-return-value" └─ ... ``` ### 5c. TOC integration The `remarkConditionalHeadings` remark plugin detects `` and `` components in MDX, loads the generated JSON, builds the reference model, and injects synthetic heading entries into `frontmatter.conditionalHeadings`. These entries carry the same `id`/`slug` values as the rendered headings, so TOC links always match. For ``, the plugin also reads the optional `partOrder` attribute and forwards it to `createComponentReferenceModel`, ensuring the TOC reflects the same part ordering as the rendered page. --- ## 6. Rendered Output ### 6a. Props table (components) Rendered by `ApiPropsTable` → `PropRow` → `DetailRow`. **Columns:** | Prop | Type | Default | | |------|------|---------|-| - **Prop** — Property name in monospace. Required props have an orange `*` suffix. - **Type** — Abbreviated type in monospace. - **Default** — Default value in monospace, or `—` if none. - **(toggle)** — Disclosure triangle. Only present if the row has a description or detailedType. **Disclosure panel** (when expanded): Contains a description list (`
`): - **Description** — Markdown-rendered description. Only shown if `description` is present. - **Type** — Full `detailedType` in monospace. Only shown if `detailedType` is present (i.e., the type was abbreviated). **Sort order:** Required props first, then alphabetical. (Defined at the builder level via `sortProps`.) **ToggleButton example:** | Prop | Type | Default | | |------|------|---------|-| | `disabled` | `boolean` | `false` | | | `label` | `string \| function` | `''` | ▸ | Expanding `label`: > **Description:** Custom label for the button. > **Type:** `string | ((state: ToggleButtonState) => string)` ### 6b. State table (components) Rendered by `ApiStateTable` → `StateRow` → `DetailRow`. **Columns:** | Property | Type | | |----------|------|-| - **Property** — State property name in monospace. - **Type** — Type in monospace (abbreviated if needed). - **(toggle)** — Disclosure triangle, same rules as props. **Disclosure panel:** Same as props (description + detailedType). **State preamble** (framework-specific, shown above the table): - **React:** "State is accessible via the `render`, `className`, and `style` props." - **HTML:** "State is reflected as data attributes for CSS styling." **ToggleButton example:** | Property | Type | | |----------|------|-| | `pressed` | `boolean` | ▸ | | `disabled` | `boolean` | ▸ | Expanding `pressed`: > **Description:** Whether the toggle is pressed. ### 6c. Data attributes table (components) Rendered by `ApiDataAttrsTable` → `DataAttrRow` → `DetailRow`. Uses the same disclosure pattern as props and state tables. **Columns:** | Attribute | Type | | |-----------|------|-| - **Attribute** — Data attribute name in monospace (e.g., `data-pressed`). - **Type** — Inferred from `StateAttrMap`. Shows enumerated values in monospace for non-boolean types. Empty cell for boolean (present/absent) attributes. Abbreviated via §2e Stage 2 when the type is long. - **(toggle)** — Disclosure triangle. Only present if the row has a description or detailedType. **Disclosure panel** (when expanded): Contains a description list (`
`): - **Description** — Markdown-rendered description. Only shown if `description` is present. - **Type** — Full `detailedType` in monospace. Only shown if `detailedType` is present (i.e., the type was abbreviated). **ToggleButton example** (boolean attributes — type column empty): | Attribute | Type | | |-----------|------|-| | `data-pressed` | | ▸ | | `data-disabled` | | ▸ | Expanding `data-pressed`: > **Description:** Present when the toggle is pressed. **Meter example** (mix of boolean and non-boolean — types inferred from state): | Attribute | Type | | |-----------|------|-| | `data-percentage` | `number` | ▸ | | `data-fill-state` | `'empty' \| 'partial' \| 'full'` | ▸ | Expanding `data-percentage`: > **Description:** Current percentage as a string. ### 6d. Parameters table (utils) Rendered by `UtilParamsTable` → `PropRow` → `DetailRow`. Reuses the same row component as component props. **Columns:** | Parameter | Type | Default | | |-----------|------|---------|-| - **Parameter** — Parameter name in monospace. Required params have an orange `*` suffix. - **Type** — Abbreviated type in monospace. - **Default** — Default value in monospace, or `—` if none. - **(toggle)** — Disclosure triangle. **Disclosure panel:** Same as props (description + detailedType). **useVolume example:** | Parameter | Type | Default | | |-----------|------|---------|-| | `options` | `object` | `{}` | ▸ | Expanding `options`: > **Type:** `{ muted?: boolean }` ### 6e. Return value (utils) Rendered by `UtilReturnTable`. Two rendering modes: **Mode 1: Object return with fields** — Renders a table using `StateRow`: | Property | Type | | |----------|------|-| Same columns and disclosure behavior as the state table. **useVolume example:** | Property | Type | | |----------|------|-| | `volume` | `number` | ▸ | | `muted` | `boolean` | ▸ | | `setVolume` | `function` | ▸ | Expanding `setVolume`: > **Description:** Set the volume level. > **Type:** `((level: number) => void)` **Mode 2: Simple return with detail** — When no fields but `detailedType` or `description` is present, renders a single-row disclosure table using `DetailRow`: | Type | | |------|-| | `function` | ▸ | Expanding the row reveals the detailed type and/or description, same as prop/state disclosure. **Mode 3: Simple return (no detail)** — When no fields, no `detailedType`, and no `description`, renders inline: > `ReturnType` — Description text here. ### 6f. CSS custom properties table (components) Rendered by `ApiCSSVarsTable` → `CSSVarRow` → `DetailRow`. Uses the same disclosure pattern as data attributes tables but with no type column. **Columns:** | Variable | | |----------|-| - **Variable** — CSS custom property name in monospace (e.g., `--media-slider-fill`). - **(toggle)** — Disclosure triangle. Present if the row has a description. **Disclosure panel** (when expanded): Contains a description list (`
`): - **Description** — Markdown-rendered description. Only shown if `description` is present. ### 6g. Multi-part component rendering For multi-part components, the top-level has no tables (all empty). Each part renders as: ``` H3: Part name (framework-specific label) Part description (if present) H4: Props (if non-empty) → Props table H4: State (if non-empty) → State table H4: Data attributes (if non-empty) → Data attributes table H4: CSS custom properties (if non-empty) → CSS custom properties table ``` **Part ordering:** Parts render in JSON key order by default (primary part first). To match the component anatomy, pass `partOrder` on the `` component: ```mdx ``` **State section preamble** (framework-specific): - **React:** "State is accessible via the `render`, `className`, and `style` props." - **HTML:** "State is reflected as data attributes for CSS styling." ### 6h. Multi-overload util rendering Each overload renders as: ``` H3: "{label}" or "Overload {N}" Overload description (if present) H4: Parameters (if non-empty) → Parameters table H4: Return Value → Return value table or inline ``` **Heading text:** If the overload has a `label` (from `@label` JSDoc), use it as the H3 heading text. Otherwise fall back to "Overload {N}". **Heading ID:** Labeled overloads use the kebab-case slug of the label (e.g., `"Video"` → `id="video"`). Unlabeled overloads use `id="overload-{n}"`. ### 6i. Disclosure panel interaction The `DetailRow` component implements an expandable disclosure pattern: - **Toggle button** renders a disclosure triangle (`▸`) that rotates 90° when expanded. - Clicking anywhere on the summary row toggles the detail panel (unless the click target is a link or button). - Uses `aria-expanded` and `aria-controls` for accessibility. - The detail panel is initially `hidden` and toggled via JavaScript. --- ## 7. Full Rendered Example: ToggleButton Given the ToggleButton source code from Section 1a, the user sees: ``` ## API Reference ### Props | Prop | Type | Default | | |------------|---------------------|---------|-| | disabled | boolean | false | | | label | string | function | '' | ▸ | └─ [expanded] Description: Custom label for the button. Type: string | ((state: ToggleButtonState) => string) ### State State is accessible via the render, className, and style props. ← React State is reflected as data attributes for CSS styling. ← HTML | Property | Type | | |------------|---------|--| | pressed | boolean | ▸ | | disabled | boolean | ▸ | └─ [expanded] Description: Whether the toggle is pressed. └─ [expanded] Description: Whether the button is disabled. ### Data attributes | Attribute | Type | | |-----------------|------|-| | data-pressed | | ▸ | | data-disabled | | ▸ | └─ [expanded] Description: Present when the toggle is pressed. └─ [expanded] Description: Present when the button is disabled. ``` ## 8. Full Rendered Example: Meter (multi-part) Given the Meter source code from Section 1b, the user sees: ``` ## API Reference ### Indicator ← React framework ### media-meter ← HTML framework A visual indicator for the current value. Renders a `` element. #### Props | Prop | Type | Default | | |-------|---------------------|---------|-| | label | string | function | '' | ▸ | | max | number | 100 | | | min | number | 0 | | #### State State is accessible via the render, className, and style props. ← React State is reflected as data attributes for CSS styling. ← HTML | Property | Type | | |------------|--------|-| | percentage | number | ▸ | #### Data attributes | Attribute | Type | | |------------------|-------------------------------|-| | data-percentage | number | ▸ | | data-fill-state | 'empty' | 'partial' | 'full' | ▸ | └─ [expanded] Description: Current percentage as a string. └─ [expanded] Description: The fill level. ### Track ← React framework ### media-meter-track ← HTML framework The track area of the meter. Renders a `
` element. (no Props, State, or Data attributes sections — all empty) ### Fill ← React framework ### media-meter-fill ← HTML framework The filled portion of the meter. Renders a `
` element. (no Props, State, or Data attributes sections — all empty) ``` ## 9. Full Rendered Example: useVolume (single-overload) ``` ## API Reference ### Parameters | Parameter | Type | Default | | |-----------|--------|---------|-| | options | object | {} | ▸ | └─ [expanded] Type: { muted?: boolean } ### Return Value | Property | Type | | |-----------|----------|-| | volume | number | ▸ | | muted | boolean | ▸ | | setVolume | function | ▸ | └─ [expanded] Description: Set the volume level. Type: ((level: number) => void) ``` ## 10. Full Rendered Example: createPlayer (multi-overload, labeled) ``` ## API Reference ### Video ← from @label "Video" Create a player instance with typed store, Provider component, Container, and hooks. #### Parameters | Parameter | Type | Default | | |-----------|-----------------------------------|---------|-| | config* | CreatePlayerConfig | — | | #### Return Value | Property | Type | | |-----------|---------------------------------|-| | Provider | React.FC | | | Container | function | ▸ | | usePlayer | UsePlayerHook | | └─ [expanded] Type: React.ForwardRefExoticComponent> ### Audio ← from @label "Audio" #### Parameters | Parameter | Type | Default | | |-----------|-----------------------------------|---------|-| | config* | CreatePlayerConfig | — | | #### Return Value | Property | Type | | |-----------|---------------------------------|-| | Provider | React.FC | | | Container | function | ▸ | | usePlayer | UsePlayerHook | | └─ [expanded] Type: React.ForwardRefExoticComponent> ```