chore(root): streamline agent guidance and skills

This commit is contained in:
Rahim
2026-07-14 15:49:05 -07:00
parent a9a09a7e52
commit b66b1ebf23
190 changed files with 1560 additions and 18335 deletions
@@ -0,0 +1,104 @@
# Builder Conventions
Naming and file placement conventions required by the api-docs-builder at `site/scripts/api-docs-builder/`.
## File Locations
| File | Path | Purpose |
|------|------|---------|
| Core | `packages/core/src/core/ui/{name}/{name}-core.ts` | Props, State, defaultProps |
| Data attrs | `packages/core/src/core/ui/{name}/{name}-data-attrs.ts` | Data attribute definitions |
| CSS vars | `packages/core/src/core/ui/{name}/{name}-css-vars.ts` | CSS custom property definitions (optional) |
| HTML element | `packages/html/src/ui/{name}/{name}-element.ts` | Custom element with `static tagName` |
| React parts | `packages/react/src/ui/{name}/index.parts.ts` | Multi-part detection (optional) |
## Naming Requirements
The builder derives PascalCase from kebab-case using `kebabCase` from es-toolkit. All interfaces and exports must follow this pattern:
| Convention | Example (play-button) |
|-----------|----------------------|
| Props interface | `PlayButtonProps` |
| State interface | `PlayButtonState` |
| Core class | `PlayButtonCore` |
| Data attrs export | `PlayButtonDataAttrs` |
| CSS vars export | `PlayButtonCSSVars` |
| HTML element class | `PlayButtonElement` |
| HTML tag name | `static tagName = 'media-play-button'` |
## NAME_OVERRIDES
When kebab-to-pascal conversion doesn't produce the correct name, add an override in `site/src/utils/api-reference-overrides.ts` (the shared map the builder imports and the reference pages invert for slug lookup):
```ts
export const NAME_OVERRIDES: Record<string, string> = {
'pip-button': 'PiPButton',
'airplay-button': 'AirPlayButton',
};
```
Use overrides only when the standard conversion fails (e.g., acronyms like PiP). Prefer aligning component naming with the standard conversion when possible.
The same map covers media elements whose PascalCase name doesn't kebab-case to their element tag name (e.g. `'hlsjs-video': 'HlsJsVideo'`). It is keyed by the generated-reference file slug regardless of component vs. media.
## Multi-Part Components
**Detection**: Presence of `packages/react/src/ui/{name}/index.parts.ts`.
**Non-local re-export filtering**: Only exports with source paths starting with `./` are treated as parts. Re-exports from other directories (e.g., `../slider/index.parts`) are filtered out. This prevents domain variant components (TimeSlider, VolumeSlider) from inheriting base component parts.
**Single-part fallback**: When filtering leaves only one part (typically Root), the component uses single-part mode — the remaining part's props/state/data-attrs are promoted to the top level, not nested under `parts`.
**Primary part identification**: The part whose React source file instantiates the component's Core class (matches `new \w+Core\(`). The primary part receives the shared core props/state/data-attrs/css-vars.
**Non-primary parts**: Each gets its own element file at `{name}-{part}-element.ts`. Element class must be `{Name}{Part}Element` (e.g., `TimeGroupElement`).
**Framework-divergent parts**: All parts get `platforms.react`. Parts with a matching HTML element file also get `platforms.html`. The renderer filters parts by framework — React-only parts are hidden in HTML docs.
**Part descriptions**: Extracted from JSDoc on the React component export:
```tsx
/** Displays a formatted time value. */
export const Value = ...;
```
## JSDoc Extraction
- **Data attribute descriptions**: From JSDoc comments on each property in the data-attrs export object
- **Part descriptions**: From JSDoc on React component exports in their `.tsx` files
- **Prop/state descriptions**: From JSDoc on interface properties in the core file
### Util JSDoc
Util exports (hooks, controllers, factories, selectors) have their own JSDoc conventions for `@param`, `@label`, and `@public` tags. See `references/util-conventions.md` → "JSDoc Conventions".
## Common Failures
The builder fails silently for many issues — data just won't appear in the JSON:
| Symptom | Cause |
|---------|-------|
| No JSON generated | Core file missing or Props interface not found |
| Empty props | Interface not named `{PascalCase}Props` |
| Empty state | Interface not named `{PascalCase}State` |
| No data attributes | File missing or export not named `{PascalCase}DataAttrs` |
| No CSS vars | File missing or export not named `{PascalCase}CSSVars` |
| No HTML tag | Element file missing or no `static tagName` |
| No part descriptions | Missing JSDoc on React component exports |
| Wrong PascalCase | Need a `NAME_OVERRIDES` entry |
## Validation
```bash
# Generate JSON
pnpm -F site api-docs
# Check component output
cat site/src/content/generated-component-reference/{name}.json
# Check util output
cat site/src/content/generated-util-reference/{slug}.json
# Verify schema
# The builder validates against ComponentReferenceSchema / UtilReferenceSchema before writing.
# Schema errors are logged as errors and cause exit code 1.
```
@@ -0,0 +1,277 @@
# Demo Patterns
File structure and conventions for interactive component demos.
## Directory Structure
```
site/src/components/docs/demos/{component}/
├── html/css/
│ ├── BasicUsage.astro # Astro wrapper (renders HTML, imports CSS, bundles script)
│ ├── BasicUsage.html # Markup only (no <style> or <script>)
│ ├── BasicUsage.css # Styles
│ └── BasicUsage.ts # Side-effect imports for custom element registration
└── react/css/
├── BasicUsage.tsx # React component
└── BasicUsage.css # Styles
```
## BEM Naming
Block = `{framework}-{component}-{variant}`, element = `__{part}`:
```
html-play-button-basic /* HTML framework, block */
html-play-button-basic__button /* HTML framework, element */
react-play-button-basic /* React framework, block */
react-play-button-basic__button /* React framework, element */
```
The framework prefix (`html-` / `react-`) prevents CSS leaking between HTML and React demos on the same page (both render but one is hidden).
## HTML Demo Files
### .astro wrapper
```astro
---
import HtmlDemo from '@/components/docs/demos/HtmlDemo.astro';
import html from './BasicUsage.html?raw';
import './BasicUsage.css';
---
<HtmlDemo html={html} />
<script>
import './BasicUsage.ts';
</script>
```
The `.astro` wrapper is required because only Astro `<script>` tags go through Vite's bundling pipeline.
### .html (markup only)
```html
<video-player class="html-mute-button-basic">
<video
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
playsinline
loop
></video>
<media-mute-button class="html-mute-button-basic__button">
<span class="show-when-muted">Unmute</span>
<span class="show-when-unmuted">Mute</span>
</media-mute-button>
</video-player>
```
- No `<style>` or `<script>` tags
- Video attributes: `autoplay muted playsinline loop`
- State labels use CSS class names toggled by data attributes
### .css (styles)
```css
.html-mute-button-basic {
position: relative;
}
.html-mute-button-basic video {
width: 100%;
}
.html-mute-button-basic__button {
padding-block: 8px;
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
color: black;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
padding-inline: 20px;
cursor: pointer;
}
/* State-based visibility via data attributes */
.html-mute-button-basic__button .show-when-muted { display: none; }
.html-mute-button-basic__button .show-when-unmuted { display: none; }
.html-mute-button-basic__button[data-muted] .show-when-muted { display: inline; }
.html-mute-button-basic__button:not([data-muted]) .show-when-unmuted { display: inline; }
```
### .ts (registration imports)
```ts
import '@videojs/html/video/player';
import '@videojs/html/ui/mute-button';
```
Import registration for:
- `@videojs/html/video/player` — always needed (registers `<video-player>`)
- `@videojs/html/ui/{component}` — registers the component's custom element
## React Demo Files
### .tsx (component)
```tsx
import { createPlayer, MuteButton } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
import './BasicUsage.css';
const Player = createPlayer({ features: videoFeatures });
export default function BasicUsage() {
return (
<Player.Provider>
<Player.Container className="react-mute-button-basic">
<Video
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoPlay
muted
playsInline
loop
/>
<MuteButton
className="react-mute-button-basic__button"
render={(props, state) => (
<button {...props}>{state.muted ? 'Unmute' : 'Mute'}</button>
)}
/>
</Player.Container>
</Player.Provider>
);
}
```
Key patterns:
- `createPlayer({ features: videoFeatures })` creates the player
- Video attributes: `autoPlay muted playsInline loop` (React camelCase)
- `render` prop for state-based rendering: `render={(props, state) => ...}`
- Spread `{...props}` on the rendered element for accessibility attributes
### .css (styles)
Same base styling as HTML but with `react-` BEM prefix:
```css
.react-mute-button-basic {
position: relative;
}
.react-mute-button-basic video {
width: 100%;
}
.react-mute-button-basic__button {
padding-block: 8px;
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
color: black;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
padding-inline: 20px;
cursor: pointer;
}
```
React CSS files typically don't need data-attribute selectors since the `render` prop handles state-based rendering. Include them only when CSS state reflection is used.
## State Reflection Patterns
### HTML: Data attribute selectors
Components expose state via `data-*` attributes. CSS toggles visibility:
```css
/* Hide all by default */
.html-play-button-basic__button .show-when-paused { display: none; }
.html-play-button-basic__button .show-when-playing { display: none; }
/* Show based on state */
.html-play-button-basic__button[data-paused] .show-when-paused { display: inline; }
.html-play-button-basic__button:not([data-paused]) .show-when-playing { display: inline; }
```
For multi-value attributes (e.g., `data-volume-level`):
```css
.html-mute-button-volume-levels__button .level-off,
.html-mute-button-volume-levels__button .level-low,
.html-mute-button-volume-levels__button .level-medium,
.html-mute-button-volume-levels__button .level-high {
display: none;
}
.html-mute-button-volume-levels__button[data-volume-level="off"] .level-off { display: inline; }
.html-mute-button-volume-levels__button[data-volume-level="low"] .level-low { display: inline; }
```
### React: Render prop
```tsx
<MuteButton
render={(props, state) => (
<button {...props}>
{state.volumeLevel === 'off'
? 'Off'
: state.volumeLevel === 'low'
? 'Low'
: state.volumeLevel === 'medium'
? 'Medium'
: 'High'}
</button>
)}
/>
```
### Three-state pattern (Play/Pause/Replay)
HTML uses `:not()` combinators to handle mutually exclusive states:
```css
.html-play-button-basic__button[data-paused]:not([data-ended]) .show-when-paused { display: inline; }
.html-play-button-basic__button:not([data-paused]) .show-when-playing { display: inline; }
.html-play-button-basic__button[data-ended] .show-when-ended { display: inline; }
```
React uses nested ternary in the render prop:
```tsx
render={(props, state) => (
<button {...props}>{state.ended ? 'Replay' : state.paused ? 'Play' : 'Pause'}</button>
)}
```
## Video Sources
- **Video**: `https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4`
- **Poster**: `https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.jpg`
## Base Button Styles
All button demos share this base overlay style:
```css
.__button {
padding-block: 8px;
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
color: black;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
padding-inline: 20px;
cursor: pointer;
}
```
@@ -0,0 +1,350 @@
# MDX Structure
Structure and conventions for API reference MDX pages at `site/src/content/docs/reference/`.
## Component Pages
### Frontmatter
```yaml
---
title: MuteButton # PascalCase component name
frameworkTitle:
html: media-mute-button # HTML custom element tag name
description: A button component for muting and unmuting audio playback
---
```
- `title`: PascalCase React component name
- `frameworkTitle.html`: The `static tagName` from the HTML element file
- `description`: One-line description of the component
### Page Structure
```
frontmatter
imports (React demos, HTML demos)
## Anatomy
## Behavior (if applicable)
## Styling (if applicable)
## Accessibility (if applicable)
## Examples
### BasicUsage
### [Additional demos]
<ComponentReference component="{PascalCase}" />
```
## Imports Section
### React demo imports
```mdx
{/* React demos */}
import BasicUsageDemoReact from "@/components/docs/demos/{component}/react/css/BasicUsage";
import basicUsageReactTsx from "@/components/docs/demos/{component}/react/css/BasicUsage.tsx?raw";
import basicUsageReactCss from "@/components/docs/demos/{component}/react/css/BasicUsage.css?raw";
```
- Component import: default export from `.tsx` (no extension needed)
- Source imports: `?raw` suffix for displaying source code in tabs
### HTML demo imports
```mdx
{/* HTML demos */}
import BasicUsageDemoHtml from "@/components/docs/demos/{component}/html/css/BasicUsage.astro";
import basicUsageHtml from "@/components/docs/demos/{component}/html/css/BasicUsage.html?raw";
import basicUsageHtmlCss from "@/components/docs/demos/{component}/html/css/BasicUsage.css?raw";
import basicUsageHtmlTs from "@/components/docs/demos/{component}/html/css/BasicUsage.ts?raw";
```
- `.astro` wrapper: renders live demo
- `.html`, `.css`, `.ts`: `?raw` imports for source tabs
### Import naming convention
| Type | Pattern | Example |
|------|---------|---------|
| React component | `{DemoName}DemoReact` | `BasicUsageDemoReact` |
| React source | `{demoName}React{Ext}` | `basicUsageReactTsx` |
| HTML component | `{DemoName}DemoHtml` | `BasicUsageDemoHtml` |
| HTML source | `{demoName}Html` / `{demoName}Html{Ext}` | `basicUsageHtml`, `basicUsageHtmlCss` |
## Anatomy Section
Anatomy shows part nesting with self-closing placeholders, following the Base UI anatomy convention. No hooks, state, handlers, or option mapping — working code belongs in Examples. This holds even when a component has no React component form (e.g. the radio groups, whose React API is a hook feeding `Menu.RadioGroup`): show the part skeleton and let the Behavior prose link to the hook for wiring.
```mdx
## Anatomy
<FrameworkCase frameworks={["react"]}>
```tsx
<MuteButton />
```
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
```html
<media-mute-button></media-mute-button>
```
</FrameworkCase>
```
For multi-part components, show composed usage:
```mdx
<FrameworkCase frameworks={["react"]}>
```tsx
<Time.Group>
<Time.Value type="current" />
<Time.Separator />
<Time.Value type="duration" />
</Time.Group>
```
</FrameworkCase>
```
## Prose Sections
### Behavior
Explain state transitions, timing, and interaction logic. Use tables for enumerated states:
```mdx
## Behavior
Toggles mute on and off. Exposes a derived `volumeLevel` based on the current volume and mute state:
| Level | Condition |
|-------|-----------|
| `off` | Muted or volume is 0 |
| `low` | Volume < 0.5 |
```
### Styling
**IMPORTANT:** All CSS code blocks in Styling sections MUST be wrapped in `<FrameworkCase>` blocks. HTML examples use custom element selectors (`media-mute-button`), React examples use className-based selectors (`.mute-button`). Never show bare CSS without a framework wrapper — React users should not see HTML element selectors and vice versa.
Show data attributes as a table, then framework-specific CSS selector patterns:
```mdx
## Styling
| Attribute | Values | Description |
|-----------|--------|-------------|
| `data-muted` | Present / absent | Present when audio is muted |
| `data-volume-level` | `"off"` \| `"low"` \| `"medium"` \| `"high"` | Current volume level |
Use `data-volume-level` for multi-level icon switching:
<FrameworkCase frameworks={["html"]}>
```css
media-mute-button[data-volume-level="off"] .icon-off { display: inline; }
```
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
React renders standard DOM elements with the same data attributes. Add a `className` and use it as the selector:
```css
.mute-button[data-volume-level="off"] .icon-off { display: inline; }
```
</FrameworkCase>
```
### Accessibility
Describe ARIA attributes, keyboard interactions, and label overrides:
```mdx
## Accessibility
Renders a `<button>` with an automatic `aria-label`: "Unmute" when muted, "Mute" when unmuted. Override with the `label` prop. Keyboard activation: <kbd>Enter</kbd> / <kbd>Space</kbd>.
```
## Examples Section
### Nesting pattern
```mdx
## Examples
### Basic Usage
<FrameworkCase frameworks={["react"]}>
<StyleCase styles={["css"]}>
<Demo files={[
{ title: "App.tsx", code: basicUsageReactTsx, lang: "tsx" },
{ title: "App.css", code: basicUsageReactCss, lang: "css" },
]}>
<BasicUsageDemoReact client:idle />
</Demo>
</StyleCase>
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
<StyleCase styles={["css"]}>
<Demo files={[
{ title: "index.html", code: basicUsageHtml, lang: "html" },
{ title: "index.css", code: basicUsageHtmlCss, lang: "css" },
{ title: "index.ts", code: basicUsageHtmlTs, lang: "ts" },
]}>
<BasicUsageDemoHtml />
</Demo>
</StyleCase>
</FrameworkCase>
```
Key details:
- React demos use `client:idle` for hydration
- HTML demos render server-side (no `client:*` directive)
- React source tabs: `App.tsx`, `App.css`
- HTML source tabs: `index.html`, `index.css`, `index.ts`
### ComponentReference Component
Always the last element in the file:
```mdx
<ComponentReference component="MuteButton" />
```
The component auto-renders Props, State, Data Attributes, and CSS Custom Properties for single-part and all Parts for multi-part. For multi-part components, React-only parts are hidden in HTML docs via framework filtering.
### Required Astro Component Imports
Every component reference MDX needs these at the top of the imports:
```mdx
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import Demo from "@/components/docs/demos/Demo.astro";
```
---
## Cross-linking
Link generously between related reference pages.
Same-framework or cross-framework link:
```mdx
Within a player provider, <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> is usually simpler.
```
Selector page linking to framework-specific utils:
```mdx
<FrameworkCase frameworks={["react"]}>
Pass `selectPlayback` to <DocsLink slug="reference/use-player">`usePlayer`</DocsLink> to subscribe.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
Pass `selectPlayback` to <DocsLink slug="reference/player-controller">`PlayerController`</DocsLink> to subscribe.
</FrameworkCase>
```
---
## Util Pages
Util pages document React hooks/utilities and HTML controllers/mixins. They are simpler than component pages — no demos, no anatomy.
### Frontmatter
```yaml
---
title: usePlayer
description: Hook to access the player store from within a Player Provider
---
```
- `title`: The exported function/class name (e.g., `usePlayer`, `PlayerController`)
- No `frameworkTitle` — util pages are framework-specific
- `description`: One-line description
### Page Structure
```
frontmatter
import UtilReference
## Import
## Usage
<UtilReference util="{Name}" />
```
### Import Section
Show the import statement for the util:
```mdx
## Import
\`\`\`tsx
import { usePlayer } from '@videojs/react';
\`\`\`
```
### Usage Section
Explain usage patterns with code examples. For multi-overload utils, document each overload:
```mdx
## Usage
`usePlayer` has two overloads:
**Store access (no subscription)** -- returns the store instance.
\`\`\`tsx
const store = usePlayer();
\`\`\`
**Selector-based subscription** -- returns selected state.
\`\`\`tsx
const paused = usePlayer((s) => s.paused);
\`\`\`
```
### UtilReference Component
Always the last element in the file:
```mdx
<UtilReference util="usePlayer" />
```
The component auto-renders Parameters and Return Value tables. For multi-overload utils, it renders each overload with its own sections.
### Required Import
```mdx
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
```
---
## Sidebar Entry
Add to `site/src/docs.config.ts` in the appropriate section, alphabetically:
- **Components** — UI component reference pages
- **Hooks & Utilities** (`frameworks: ['react']`) — React hooks and utilities
- **Controllers & Mixins** (`frameworks: ['html']`) — HTML controllers and mixins
```ts
{
sidebarLabel: 'Components',
contents: [
// sorted alphabetically
{ slug: 'reference/buffering-indicator' },
{ slug: 'reference/controls' },
// ...
{ slug: 'reference/{name}' }, // <-- insert alphabetically
// ...
],
},
```
@@ -0,0 +1,228 @@
# Util Reference Conventions
Conventions for the util reference system that documents React hooks/utilities and HTML controllers/mixins.
## Architecture
Util references use **convention-based auto-discovery** from package index files. The builder scans entry points, resolves local module paths, and includes exports matching naming conventions or annotated with `@public`.
## Auto-Discovery Pipeline
The builder (`site/scripts/api-docs-builder/src/util-handler.ts`) scans entry points:
```ts
packages/react/src/index.ts → framework: 'react'
packages/store/src/react/hooks/index.ts → framework: 'react'
packages/html/src/index.ts → framework: 'html'
packages/store/src/html/controllers/index.ts → framework: 'html'
packages/core/src/dom/store/selectors.ts → framework: null (agnostic)
packages/store/src/core/selector.ts → framework: null (agnostic)
```
Framework-agnostic entries (`framework: null`) produce JSON without a `frameworks` field, meaning they apply to all frameworks. Framework-specific entries get `frameworks: ['react']` or `frameworks: ['html']` in the JSON.
**Phase 1 — Resolve local modules.** Raw TS AST reads export declarations from each index file, keeping only local paths (`./...`), skipping external packages (`@videojs/...`).
**Phase 2 — Filter by convention.** Each local module is parsed with TAE (typescript-api-extractor) or raw TS AST. Exports are included if they match naming conventions or have `@public`.
### Adding a New Util
1. Export it from the appropriate package index file
2. Add JSDoc with a description
3. If it doesn't match a naming convention (see below), add `@public` to the JSDoc
4. Run `pnpm api-docs` to generate its JSON
4. Create an MDX page with `<UtilReference util="{Name}" />`
5. Add to the sidebar in `docs.config.ts`
No code changes needed in the builder itself — convention over configuration.
## JSDoc Conventions
The builder extracts JSDoc from source exports to populate reference pages. These rules are the detailed form of the API-reference exception in the root `AGENTS.md`.
### Summary description (required)
Every util export needs a JSDoc summary. This becomes the description in the generated JSON:
```ts
/** Subscribe to the player's volume state. */
export function useVolume(...): VolumeResult;
```
### `@param` descriptions (required for non-obvious params)
Unlike internal code, API reference exports need `@param` tags so the builder can populate parameter tables. Describe intent and defaults, not types:
```ts
/**
* Subscribe to derived state with customizable equality check.
*
* @param subscribe - Subscribe function that returns an unsubscribe callback.
* @param selector - Derives a value from the snapshot.
* @param isEqual - Custom equality function. Defaults to `shallowEqual`.
*/
export function useSelector<S, R>(...): R;
```
Format: `@param name - description` (dash after name).
### No `@returns`
Return types are inferred from the TypeScript signature. Don't add `@returns`.
### `@label` for multi-overload functions
When a function has multiple overload signatures with different return types, each overload gets its own JSDoc block with an `@label` tag. The label becomes a heading in the docs:
```ts
/**
* Create a player instance with typed store, Provider, and hooks.
*
* @label Video
* @param config - Player configuration with features.
*/
export function createPlayer(config: CreatePlayerConfig<VideoFeatures>): CreatePlayerResult<VideoPlayerStore>;
/**
* Create a player for audio media.
*
* @label Audio
* @param config - Player configuration with features.
*/
export function createPlayer(config: CreatePlayerConfig<AudioFeatures>): CreatePlayerResult<AudioPlayerStore>;
```
Without `@label`, overloads render as "Overload 1", "Overload 2", etc.
### `@label` for constructor overloads
Same pattern applies to controller constructors:
```ts
/**
* @label Without Selector
* @param host - The host element that owns this controller.
* @param state - The State container to subscribe to.
*/
constructor(host: ReactiveControllerHost, state: State<T>);
/**
* @label With Selector
* @param host - The host element that owns this controller.
* @param state - The State container to subscribe to.
* @param selector - Derives a value from the state.
*/
constructor(host: ReactiveControllerHost, state: State<T>, selector: Selector<T, R>);
```
### `@public` for non-convention exports
Exports that don't match a naming convention (`use*`, `*Controller`, `create*`, `select*`) need `@public` to be discovered:
```ts
/** @public The default player context for consuming the player store. */
export const playerContext = createContext<PlayerContextValue>(...);
```
## Inclusion Conventions
Exports are auto-included when they match these patterns:
| Pattern | Match Rule | Examples |
|---------|-----------|----------|
| Hooks | Name starts with `use`, is a function | `usePlayer`, `useStore` |
| Controllers | Name ends with `Controller` | `PlayerController`, `StoreController` |
| Factories | Name starts with `create`, is a function | `createPlayer` |
| Mixin factories | Name starts with `create` + contains `Mixin` | `createProviderMixin` |
| `@public` | Has `@public` JSDoc tag | `playerContext`, `mergeProps`, `renderElement` |
Exports that don't match any convention are excluded (UI components, types, internal helpers).
## Slug Conventions
- Slugs are kebab-case: `use-player`, `player-controller`, `merge-props`
- All slugs must be unique across both frameworks
- When the same name exists in both React and HTML (e.g., `createPlayer`), prefix the HTML slug: `html-create-player`
## Frameworks
| Framework | Sidebar Section | Source Packages |
|-----------|----------------|-----------------|
| `react` | Hooks & Utilities | `@videojs/react`, `@videojs/store/react` |
| `html` | Controllers & Mixins | `@videojs/html`, `@videojs/store/html` |
| `null` (agnostic) | Selectors | `@videojs/core/dom`, `@videojs/store` |
Agnostic utils omit the `frameworks` field in JSON, meaning they're available to all frameworks.
## Overloads
Use multiple overloads when the return type genuinely differs between signatures:
```ts
// Two overloads — different return types
usePlayer() → PlayerStore
usePlayer(selector) → T (selected value)
```
For simple param-count differences with the same return type, use a single overload with optional params instead.
## Generated JSON
Output: `site/src/content/generated-util-reference/{slug}.json`
Schema: `UtilReferenceSchema` from `site/src/types/util-reference.ts`
```json
{
"name": "usePlayer",
"description": "...",
"overloads": [
{
"description": "...",
"parameters": { "selector": { "type": "...", "required": true } },
"returnValue": { "type": "...", "description": "..." }
}
]
}
```
## Content Collection
The `utilReference` collection in `site/src/content.config.ts` loads from `generated-util-reference/` and validates against `UtilReferenceSchema`.
## Astro Components
| Component | Purpose |
|-----------|---------|
| `UtilReference.astro` | Main component — loads JSON, renders sections |
| `UtilParamsTable.astro` | Parameter table (reuses `PropRow.astro`) |
| `UtilReturnTable.astro` | Return value table (reuses `StateRow.astro` for object returns) |
## Common Failures
| Symptom | Cause |
|---------|-------|
| No JSON generated | Export not in a scanned index file, or doesn't match convention / lack `@public` |
| Zod validation error | Schema mismatch — check field names and types |
| Missing from sidebar | Not added to `docs.config.ts` in correct section |
| Page 404 | MDX file missing or slug mismatch |
| Wrong framework section | Export in wrong entry point — check which index file re-exports it |
| TAE crash on index file | Known issue with `UniqueESSymbol` types — raw TS AST fallback handles this |
## Discovery vs MDX Pages
Auto-discovery generates JSON files in `site/src/content/generated-util-reference/`. A JSON file without a corresponding MDX page is harmless — it sits unused.
Only utils with **both** generated JSON **and** a manually-created MDX page appear in the docs. This is intentional: discovery casts a wide net using naming conventions, while MDX pages are curated to document the public API surface.
For example, `SubscriptionController` is discovered (matches `*Controller`) but has no MDX page because it's an internal building block not intended for direct consumer use.
When adding a new util to the docs:
1. Ensure the builder discovers it (check with `pnpm api-docs`)
2. Create the MDX page at `site/src/content/docs/reference/{slug}.mdx`
3. Add to the sidebar in `docs.config.ts`
## Tests
- `site/scripts/api-docs-builder/src/tests/util-handler.test.ts` — fixture-based tests for discovery, slug uniqueness, frameworks, overloads
- `site/src/utils/tests/utilReferenceModel.test.ts` — validates model structure and TOC headings