8.8 KiB
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:
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
- Export it from the appropriate package index file
- Add JSDoc with a description
- If it doesn't match a naming convention (see below), add
@publicto the JSDoc - Run
pnpm api-docsto generate its JSON - Create an MDX page with
<UtilReference util="{Name}" /> - 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 override the root CLAUDE.md "Minimal JSDoc" guidelines for API reference exports.
Summary description (required)
Every util export needs a JSDoc summary. This becomes the description in the generated JSON:
/** 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:
/**
* 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:
/**
* 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:
/**
* @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:
/** @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:
// 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
{
"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:
- Ensure the builder discovers it (check with
pnpm api-docs) - Create the MDX page at
site/src/content/docs/reference/{slug}.mdx - 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, overloadssite/src/utils/tests/utilReferenceModel.test.ts— validates model structure and TOC headings