# 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
