mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
chore(claude): add skills system (#310)
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
# AI/Agent Readiness Pattern
|
||||
|
||||
How to make documentation consumable by AI assistants and coding agents.
|
||||
|
||||
## llms.txt
|
||||
|
||||
Provide documentation in a single, AI-optimized file.
|
||||
|
||||
### Structure
|
||||
|
||||
```markdown
|
||||
# Video.js 10
|
||||
|
||||
> A framework-agnostic media player library.
|
||||
|
||||
## Quick Start
|
||||
|
||||
npm install @videojs/core
|
||||
|
||||
import { createPlayer } from '@videojs/core';
|
||||
const player = createPlayer({ src: 'video.mp4' });
|
||||
|
||||
## Core Concepts
|
||||
|
||||
- [State Management](/docs/concepts/state.md)
|
||||
- [Requests](/docs/concepts/requests.md)
|
||||
- [Events](/docs/concepts/events.md)
|
||||
|
||||
## API Reference
|
||||
|
||||
- [createPlayer](/docs/api/create-player.md)
|
||||
- [Player](/docs/api/player.md)
|
||||
|
||||
## Adapters
|
||||
|
||||
- [React](/docs/adapters/react.md)
|
||||
- [Vue](/docs/adapters/vue.md)
|
||||
- [Svelte](/docs/adapters/svelte.md)
|
||||
```
|
||||
|
||||
### Sizes
|
||||
|
||||
Provide multiple versions for different context windows:
|
||||
|
||||
| File | Size | Content |
|
||||
|------|------|---------|
|
||||
| `llms.txt` | ~10k tokens | Overview + links |
|
||||
| `llms-small.txt` | ~5k tokens | Quick ref only |
|
||||
| `llms-full.txt` | ~50k tokens | Complete docs |
|
||||
|
||||
### URL Pattern
|
||||
|
||||
```
|
||||
https://videojs.com/llms.txt
|
||||
https://videojs.com/llms-small.txt
|
||||
https://videojs.com/llms-full.txt
|
||||
https://videojs.com/docs/api/player.md # Direct markdown
|
||||
```
|
||||
|
||||
## AGENTS.md
|
||||
|
||||
Include in package root for AI coding agents.
|
||||
|
||||
```markdown
|
||||
# AGENTS.md
|
||||
|
||||
## Project
|
||||
|
||||
Video.js 10 - Framework-agnostic media player.
|
||||
|
||||
## Structure
|
||||
|
||||
packages/
|
||||
├── core/ # Framework-agnostic logic
|
||||
├── dom/ # Vanilla JS components
|
||||
├── react/ # React adapter
|
||||
├── vue/ # Vue adapter
|
||||
├── svelte/ # Svelte adapter
|
||||
└── solid/ # Solid adapter
|
||||
|
||||
## Commands
|
||||
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Build all packages
|
||||
pnpm build
|
||||
|
||||
# Run tests
|
||||
pnpm test
|
||||
|
||||
# Lint
|
||||
pnpm lint
|
||||
|
||||
# Type check
|
||||
pnpm typecheck
|
||||
|
||||
## Code Style
|
||||
|
||||
- TypeScript strict mode
|
||||
- Prefer `const` over `let`
|
||||
- Use named exports
|
||||
- Document public APIs with TSDoc
|
||||
- Test files: `*.test.ts`
|
||||
|
||||
## Key Files
|
||||
|
||||
- `packages/core/src/player.ts` — Main player class
|
||||
- `packages/core/src/state.ts` — State management
|
||||
- `packages/core/src/request.ts` — Request system
|
||||
|
||||
## Testing
|
||||
|
||||
# Run all tests
|
||||
pnpm test
|
||||
|
||||
# Run specific package
|
||||
pnpm --filter @videojs/core test
|
||||
|
||||
# Watch mode
|
||||
pnpm test --watch
|
||||
```
|
||||
|
||||
## TSDoc/JSDoc
|
||||
|
||||
Document all public exports:
|
||||
|
||||
```ts
|
||||
/**
|
||||
* Creates a new player instance.
|
||||
*
|
||||
* @param options - Configuration options
|
||||
* @returns A new Player instance
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { createPlayer } from '@videojs/core';
|
||||
*
|
||||
* const player = createPlayer({
|
||||
* src: 'video.mp4',
|
||||
* autoplay: true,
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @remarks
|
||||
* The player must be attached to a media element before playback.
|
||||
* Use {@link Player.attach} to connect to an element.
|
||||
*
|
||||
* @see {@link PlayerOptions} for all configuration options
|
||||
* @see {@link Player} for the returned instance type
|
||||
*/
|
||||
export function createPlayer(options: PlayerOptions): Player {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Required Tags
|
||||
|
||||
| Tag | Usage |
|
||||
|-----|-------|
|
||||
| `@param` | Every parameter |
|
||||
| `@returns` | Non-void return |
|
||||
| `@example` | At least one |
|
||||
| `@throws` | If can throw |
|
||||
| `@see` | Related items |
|
||||
|
||||
### Optional Tags
|
||||
|
||||
| Tag | Usage |
|
||||
|-----|-------|
|
||||
| `@remarks` | Implementation details |
|
||||
| `@defaultValue` | Default values |
|
||||
| `@deprecated` | Deprecation notice |
|
||||
| `@since` | Version introduced |
|
||||
| `@beta` / `@alpha` | Stability |
|
||||
|
||||
## Self-Contained Examples
|
||||
|
||||
AI agents need examples that work without external context:
|
||||
|
||||
```ts
|
||||
// ❌ Bad — requires context
|
||||
player.play();
|
||||
|
||||
// ✅ Good — self-contained
|
||||
import { createPlayer } from '@videojs/core';
|
||||
|
||||
const video = document.querySelector('video');
|
||||
const player = createPlayer({ src: 'video.mp4' });
|
||||
|
||||
await player.attach(video);
|
||||
await player.play();
|
||||
```
|
||||
|
||||
### Include All Imports
|
||||
|
||||
```ts
|
||||
// ❌ Assumes imports exist
|
||||
const player = createPlayer(options);
|
||||
|
||||
// ✅ Shows exactly what to import
|
||||
import { createPlayer } from '@videojs/core';
|
||||
import type { PlayerOptions } from '@videojs/core';
|
||||
|
||||
const options: PlayerOptions = { src: 'video.mp4' };
|
||||
const player = createPlayer(options);
|
||||
```
|
||||
|
||||
### Show Expected Output
|
||||
|
||||
```ts
|
||||
console.log(player.state);
|
||||
// Output:
|
||||
// {
|
||||
// currentTime: 0,
|
||||
// duration: 120,
|
||||
// paused: true,
|
||||
// volume: 1,
|
||||
// muted: false,
|
||||
// }
|
||||
```
|
||||
|
||||
### Include Error Cases
|
||||
|
||||
```ts
|
||||
try {
|
||||
await player.play();
|
||||
} catch (error) {
|
||||
// Error: NotAllowedError - Autoplay blocked by browser
|
||||
}
|
||||
```
|
||||
|
||||
## Markdown Export
|
||||
|
||||
Every documentation page should be available as raw markdown:
|
||||
|
||||
```markdown
|
||||
## Viewing as Markdown
|
||||
|
||||
This page is available in markdown format:
|
||||
[View as Markdown](/docs/api/player.md)
|
||||
```
|
||||
|
||||
Or automatic via URL suffix:
|
||||
|
||||
```
|
||||
/docs/api/player → HTML page
|
||||
/docs/api/player.md → Raw markdown
|
||||
```
|
||||
|
||||
## Context Window Optimization
|
||||
|
||||
Write docs that work within token limits:
|
||||
|
||||
### Chunk by Concept
|
||||
|
||||
```markdown
|
||||
<!-- Good: One concept per section -->
|
||||
## State
|
||||
|
||||
The player state is a readonly object...
|
||||
|
||||
## Requests
|
||||
|
||||
Requests are used to change state...
|
||||
```
|
||||
|
||||
### Avoid Redundancy
|
||||
|
||||
```markdown
|
||||
<!-- Bad: Repeats information -->
|
||||
The `play()` method plays the video. When you call `play()`,
|
||||
the video will start playing.
|
||||
|
||||
<!-- Good: Concise -->
|
||||
`play()` starts playback.
|
||||
```
|
||||
|
||||
### Front-Load Important Info
|
||||
|
||||
```markdown
|
||||
<!-- Good: Key info first -->
|
||||
## createPlayer
|
||||
|
||||
Creates a player instance. Returns `Player`.
|
||||
|
||||
const player = createPlayer({ src: 'video.mp4' });
|
||||
|
||||
### Options
|
||||
...
|
||||
```
|
||||
|
||||
## MCP Server
|
||||
|
||||
For advanced integration, provide an MCP server:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "videojs-docs",
|
||||
"version": "1.0.0",
|
||||
"tools": [
|
||||
{
|
||||
"name": "search_docs",
|
||||
"description": "Search Video.js documentation",
|
||||
"parameters": {
|
||||
"query": { "type": "string" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_api",
|
||||
"description": "Get API reference for a symbol",
|
||||
"parameters": {
|
||||
"symbol": { "type": "string" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Testing AI Readability
|
||||
|
||||
Checklist for AI-friendly docs:
|
||||
|
||||
- [ ] llms.txt at docs root
|
||||
- [ ] AGENTS.md in package root
|
||||
- [ ] All exports have TSDoc
|
||||
- [ ] Examples include imports
|
||||
- [ ] Examples are runnable
|
||||
- [ ] Pages available as markdown
|
||||
- [ ] No broken internal links
|
||||
- [ ] Code blocks have language tags
|
||||
- [ ] Types are documented or inferrable
|
||||
@@ -0,0 +1,343 @@
|
||||
# Code Examples Pattern
|
||||
|
||||
How to write effective code examples for documentation.
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **Self-contained** — include all imports
|
||||
2. **Copy-paste ready** — works immediately
|
||||
3. **TypeScript-first** — show types, leverage inference
|
||||
4. **Minimal** — only what's needed to demonstrate the concept
|
||||
5. **Real** — use realistic values, not `foo`/`bar`
|
||||
|
||||
## Self-Contained Examples
|
||||
|
||||
```tsx
|
||||
// ❌ Missing imports — won't work when copied
|
||||
function App() {
|
||||
const player = usePlayer();
|
||||
return <Player src="video.mp4" />;
|
||||
}
|
||||
|
||||
// ✅ Complete — copy, paste, run
|
||||
import { Player, usePlayer } from '@videojs/react';
|
||||
|
||||
function App() {
|
||||
const player = usePlayer();
|
||||
return <Player src="video.mp4" />;
|
||||
}
|
||||
```
|
||||
|
||||
## TypeScript Best Practices
|
||||
|
||||
### Show Type Inference
|
||||
|
||||
```ts
|
||||
// ✅ Let inference work — cleaner
|
||||
const player = createPlayer({
|
||||
src: 'video.mp4',
|
||||
autoplay: true,
|
||||
});
|
||||
// player is inferred as Player
|
||||
|
||||
// ❌ Redundant annotation
|
||||
const player: Player = createPlayer({
|
||||
src: 'video.mp4',
|
||||
autoplay: true,
|
||||
});
|
||||
```
|
||||
|
||||
### Annotate When Helpful
|
||||
|
||||
```ts
|
||||
// ✅ Annotation clarifies complex return
|
||||
function usePlayerState(): {
|
||||
state: PlayerState;
|
||||
request: RequestAPI;
|
||||
} {
|
||||
// ...
|
||||
}
|
||||
|
||||
// ✅ Annotation shows expected shape
|
||||
const options: PlayerOptions = {
|
||||
src: 'video.mp4',
|
||||
tracks: [
|
||||
{ kind: 'subtitles', src: 'en.vtt', label: 'English' },
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
### Show Type Imports
|
||||
|
||||
```ts
|
||||
// ✅ Show type imports for complex types
|
||||
import type { PlayerOptions, TextTrack } from '@videojs/core';
|
||||
|
||||
const tracks: TextTrack[] = [
|
||||
{ kind: 'subtitles', src: 'en.vtt', label: 'English' },
|
||||
];
|
||||
```
|
||||
|
||||
## Framework Tabs
|
||||
|
||||
Use tabs for multi-framework examples:
|
||||
|
||||
````markdown
|
||||
<Tabs>
|
||||
<Tab label="React">
|
||||
```tsx
|
||||
import { Player } from '@videojs/react';
|
||||
|
||||
function App() {
|
||||
return <Player src="video.mp4" />;
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="Vue">
|
||||
```vue
|
||||
<script setup>
|
||||
import { Player } from '@videojs/vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Player src="video.mp4" />
|
||||
</template>
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="Svelte">
|
||||
```svelte
|
||||
<script>
|
||||
import { Player } from '@videojs/svelte';
|
||||
</script>
|
||||
|
||||
<Player src="video.mp4" />
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="Vanilla">
|
||||
```ts
|
||||
import { createPlayer } from '@videojs/core';
|
||||
|
||||
const player = createPlayer({
|
||||
target: document.getElementById('player'),
|
||||
src: 'video.mp4',
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
````
|
||||
|
||||
## Progressive Examples
|
||||
|
||||
Start simple, add complexity:
|
||||
|
||||
```markdown
|
||||
## Basic Usage
|
||||
|
||||
const player = createPlayer({ src: 'video.mp4' });
|
||||
|
||||
## With Options
|
||||
|
||||
const player = createPlayer({
|
||||
src: 'video.mp4',
|
||||
autoplay: true,
|
||||
muted: true,
|
||||
});
|
||||
|
||||
## With Event Handling
|
||||
|
||||
const player = createPlayer({
|
||||
src: 'video.mp4',
|
||||
onPlay: () => console.log('Playing'),
|
||||
onError: (e) => console.error(e),
|
||||
});
|
||||
|
||||
## Full Configuration
|
||||
|
||||
const player = createPlayer({
|
||||
src: 'video.mp4',
|
||||
autoplay: true,
|
||||
muted: true,
|
||||
loop: false,
|
||||
preload: 'metadata',
|
||||
tracks: [
|
||||
{ kind: 'subtitles', src: 'en.vtt', label: 'English', default: true },
|
||||
],
|
||||
onPlay: () => analytics.track('video_play'),
|
||||
onError: (e) => errorReporter.capture(e),
|
||||
});
|
||||
```
|
||||
|
||||
## Highlight Key Lines
|
||||
|
||||
Use comments to draw attention:
|
||||
|
||||
```ts
|
||||
const player = createPlayer({
|
||||
src: 'video.mp4',
|
||||
// highlight-next-line
|
||||
autoplay: true, // ← Starts playing automatically
|
||||
});
|
||||
```
|
||||
|
||||
Or diff-style:
|
||||
|
||||
```ts
|
||||
const player = createPlayer({
|
||||
src: 'video.mp4',
|
||||
- autoplay: false,
|
||||
+ autoplay: true,
|
||||
});
|
||||
```
|
||||
|
||||
## Show Output
|
||||
|
||||
Include expected output as comments:
|
||||
|
||||
```ts
|
||||
console.log(player.state.currentTime);
|
||||
// => 0
|
||||
|
||||
await player.request.seek(30);
|
||||
console.log(player.state.currentTime);
|
||||
// => 30
|
||||
```
|
||||
|
||||
## Error Examples
|
||||
|
||||
Show what errors look like:
|
||||
|
||||
```ts
|
||||
// This will throw:
|
||||
player.play();
|
||||
// => Error: Player not attached to media element
|
||||
|
||||
// Do this instead:
|
||||
await player.attach(videoElement);
|
||||
player.play();
|
||||
```
|
||||
|
||||
## Interactive Examples
|
||||
|
||||
Link to StackBlitz/CodeSandbox:
|
||||
|
||||
```markdown
|
||||
```tsx
|
||||
import { Player } from '@videojs/react';
|
||||
|
||||
function App() {
|
||||
return <Player src="video.mp4" />;
|
||||
}
|
||||
```
|
||||
|
||||
[Open in StackBlitz →](https://stackblitz.com/edit/videojs-react-basic)
|
||||
```
|
||||
|
||||
## Copy Buttons
|
||||
|
||||
All code blocks should have copy functionality. In MDX:
|
||||
|
||||
```mdx
|
||||
<CodeBlock copy>
|
||||
const player = createPlayer({ src: 'video.mp4' });
|
||||
</CodeBlock>
|
||||
```
|
||||
|
||||
## Filename Headers
|
||||
|
||||
Show which file the code belongs to:
|
||||
|
||||
````markdown
|
||||
```tsx title="App.tsx"
|
||||
import { Player } from '@videojs/react';
|
||||
|
||||
export function App() {
|
||||
return <Player src="video.mp4" />;
|
||||
}
|
||||
```
|
||||
|
||||
```css title="player.css"
|
||||
.player {
|
||||
--player-accent-color: #3b82f6;
|
||||
}
|
||||
```
|
||||
````
|
||||
|
||||
## Do/Don't Examples
|
||||
|
||||
Show contrast:
|
||||
|
||||
```markdown
|
||||
### Event Handling
|
||||
|
||||
// ❌ Don't — inline handlers get recreated
|
||||
<Player
|
||||
onTimeUpdate={(t) => setTime(t)}
|
||||
/>
|
||||
|
||||
// ✅ Do — stable callback reference
|
||||
const handleTimeUpdate = useCallback((t) => setTime(t), []);
|
||||
<Player onTimeUpdate={handleTimeUpdate} />
|
||||
```
|
||||
|
||||
## Realistic Values
|
||||
|
||||
```ts
|
||||
// ❌ Meaningless
|
||||
const foo = createBar({ baz: 'qux' });
|
||||
|
||||
// ✅ Realistic
|
||||
const player = createPlayer({
|
||||
src: 'https://example.com/video.mp4',
|
||||
poster: 'https://example.com/poster.jpg',
|
||||
});
|
||||
```
|
||||
|
||||
## Console Examples
|
||||
|
||||
For CLI documentation:
|
||||
|
||||
```bash
|
||||
# Install the package
|
||||
npm install @videojs/core
|
||||
|
||||
# Or with other package managers
|
||||
pnpm add @videojs/core
|
||||
yarn add @videojs/core
|
||||
```
|
||||
|
||||
## API Response Examples
|
||||
|
||||
For async operations:
|
||||
|
||||
```ts
|
||||
const result = await player.request.play();
|
||||
// => { success: true, state: 'playing' }
|
||||
|
||||
const error = await player.request.play();
|
||||
// => { success: false, error: { code: 'NOT_ALLOWED', message: '...' } }
|
||||
```
|
||||
|
||||
## Configuration Comparison
|
||||
|
||||
Show equivalent configs:
|
||||
|
||||
```markdown
|
||||
### JavaScript
|
||||
|
||||
const player = createPlayer({
|
||||
src: 'video.mp4',
|
||||
autoplay: true,
|
||||
});
|
||||
|
||||
### HTML Data Attributes
|
||||
|
||||
<video
|
||||
data-player
|
||||
data-src="video.mp4"
|
||||
data-autoplay
|
||||
></video>
|
||||
|
||||
### React Props
|
||||
|
||||
<Player src="video.mp4" autoplay />
|
||||
```
|
||||
@@ -0,0 +1,274 @@
|
||||
# Error Documentation Pattern
|
||||
|
||||
Document store errors consistently across Video.js packages.
|
||||
|
||||
---
|
||||
|
||||
## Error Code Reference Table
|
||||
|
||||
Always document errors in this format:
|
||||
|
||||
| Code | Meaning | Recovery |
|
||||
| ------------ | ------------------------------------------- | ---------------------------------------------- |
|
||||
| `ABORTED` | Request aborted via signal | Expected during cleanup — no action needed |
|
||||
| `CANCELLED` | Cancelled by another request's `cancel: []` | Check request coordination |
|
||||
| `SUPERSEDED` | Same-key request replaced this one | Expected during rapid input — no action needed |
|
||||
| `REJECTED` | Guard returned falsy | Check preconditions, show user feedback |
|
||||
| `TIMEOUT` | Guard timed out | Increase timeout or check target readiness |
|
||||
| `NO_TARGET` | No target attached | Call `attach()` before making requests |
|
||||
| `DETACHED` | Target was detached | Re-attach or abort operation |
|
||||
| `DESTROYED` | Store was destroyed | Create new store instance |
|
||||
|
||||
---
|
||||
|
||||
## Expected vs Unexpected Errors
|
||||
|
||||
Document which errors are "normal" vs programming errors:
|
||||
|
||||
| Code | Expected? | Notes |
|
||||
| ------------ | --------- | --------------------------------------------- |
|
||||
| `SUPERSEDED` | Often | Rapid user input (scrubbing, repeated clicks) |
|
||||
| `ABORTED` | Often | Component unmount, navigation |
|
||||
| `CANCELLED` | Sometimes | Intentional coordination between requests |
|
||||
| `REJECTED` | Sometimes | Guard logic blocking execution |
|
||||
| `TIMEOUT` | Rarely | Slow media load, network issues |
|
||||
| `NO_TARGET` | Never | Programming error — attach before use |
|
||||
| `DETACHED` | Rarely | Lifecycle timing issue |
|
||||
| `DESTROYED` | Never | Programming error — don't use after destroy |
|
||||
|
||||
---
|
||||
|
||||
## Error Handling Patterns
|
||||
|
||||
### Global Handler (store config)
|
||||
|
||||
```ts
|
||||
const store = createStore({
|
||||
slices: [playbackSlice, volumeSlice],
|
||||
onError: ({ error, request }) => {
|
||||
if (request) {
|
||||
console.error(`${request.name} failed:`, error.code);
|
||||
}
|
||||
// Report to analytics, show toast, etc.
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Local Handler (try/catch)
|
||||
|
||||
```ts
|
||||
import { isStoreError } from '@videojs/store';
|
||||
|
||||
try {
|
||||
await store.request.play();
|
||||
} catch (error) {
|
||||
if (isStoreError(error)) {
|
||||
switch (error.code) {
|
||||
case 'SUPERSEDED':
|
||||
// Another request took over — expected, ignore
|
||||
break;
|
||||
case 'REJECTED':
|
||||
// Guard blocked execution — show feedback
|
||||
showMessage('Cannot play right now');
|
||||
break;
|
||||
case 'TIMEOUT':
|
||||
// Took too long — retry or show error
|
||||
showMessage('Media not ready');
|
||||
break;
|
||||
default:
|
||||
console.error(`[${error.code}]`, error.message);
|
||||
}
|
||||
} else {
|
||||
throw error; // Re-throw unknown errors
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Type Guard Pattern
|
||||
|
||||
Always show the type guard:
|
||||
|
||||
```ts
|
||||
import { isStoreError } from '@videojs/store';
|
||||
|
||||
function handleError(error: unknown) {
|
||||
if (isStoreError(error)) {
|
||||
// error is StoreError — has .code, .message
|
||||
return { code: error.code, message: error.message };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting Section Format
|
||||
|
||||
### Structure
|
||||
|
||||
1. Error code/message as heading
|
||||
2. **Cause:** One sentence
|
||||
3. **Solution:** Code example
|
||||
|
||||
### Examples
|
||||
|
||||
#### NO_TARGET
|
||||
|
||||
**Cause:** Request made before `attach()` was called.
|
||||
|
||||
**Solution:**
|
||||
|
||||
```ts
|
||||
// ❌ Wrong
|
||||
const store = createStore({ slices: [playbackSlice] });
|
||||
await store.request.play(); // Error: NO_TARGET
|
||||
|
||||
// ✅ Correct
|
||||
const store = createStore({ slices: [playbackSlice] });
|
||||
store.attach(videoElement);
|
||||
await store.request.play();
|
||||
```
|
||||
|
||||
#### SUPERSEDED
|
||||
|
||||
**Cause:** Another request with the same key started before this one finished.
|
||||
|
||||
**Solution:** This is usually expected behavior. If you need the result, check before making a new request:
|
||||
|
||||
```ts
|
||||
// If you need to know the final state
|
||||
const result = await store.request.play();
|
||||
// Result may be from a later request if superseded
|
||||
|
||||
// If you want to prevent supersession, use unique keys
|
||||
request: {
|
||||
trackEvent: {
|
||||
key: () => Symbol(), // Each call gets unique key
|
||||
handler: (data) => analytics.log(data),
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### REJECTED
|
||||
|
||||
**Cause:** A guard returned a falsy value.
|
||||
|
||||
**Solution:** Check what condition the guard expects:
|
||||
|
||||
```ts
|
||||
// Guard that checks readyState
|
||||
const canPlay: Guard<HTMLMediaElement> = ({ target }) => {
|
||||
return target.readyState >= HTMLMediaElement.HAVE_ENOUGH_DATA;
|
||||
};
|
||||
|
||||
// If rejected, media isn't ready — wait for canplay event
|
||||
player.on('canplay', () => {
|
||||
// Now safe to request play
|
||||
store.request.play();
|
||||
});
|
||||
```
|
||||
|
||||
#### TIMEOUT
|
||||
|
||||
**Cause:** A guard didn't resolve within the timeout period.
|
||||
|
||||
**Solution:** Increase the timeout or ensure the target is ready:
|
||||
|
||||
```ts
|
||||
import { timeout } from '@videojs/store';
|
||||
|
||||
request: {
|
||||
play: {
|
||||
// Increase timeout for slow connections
|
||||
guard: timeout(canMediaPlay, 10000), // 10 seconds
|
||||
handler: async (_, { target }) => {
|
||||
await target.play();
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### ABORTED
|
||||
|
||||
**Cause:** The abort signal was triggered (usually from component unmount).
|
||||
|
||||
**Solution:** This is expected behavior. Ensure cleanup runs:
|
||||
|
||||
```ts
|
||||
// React
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
|
||||
store.request.play(null, { signal: controller.signal });
|
||||
|
||||
return () => controller.abort(); // Cleans up on unmount
|
||||
}, []);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Reference Format
|
||||
|
||||
When documenting error-related APIs:
|
||||
|
||||
### isStoreError
|
||||
|
||||
Type guard for store errors.
|
||||
|
||||
```ts
|
||||
import { isStoreError } from '@videojs/store';
|
||||
|
||||
if (isStoreError(error)) {
|
||||
console.log(error.code); // 'ABORTED' | 'CANCELLED' | ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | --------- | ---------------- |
|
||||
| `error` | `unknown` | Any caught error |
|
||||
|
||||
#### Returns
|
||||
|
||||
`error is StoreError` — Type predicate
|
||||
|
||||
### StoreError
|
||||
|
||||
Error thrown by store operations.
|
||||
|
||||
#### Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --------- | ---------------- | -------------------------- |
|
||||
| `code` | `StoreErrorCode` | Error classification |
|
||||
| `message` | `string` | Human-readable description |
|
||||
|
||||
#### StoreErrorCode
|
||||
|
||||
```ts
|
||||
type StoreErrorCode =
|
||||
| 'ABORTED'
|
||||
| 'CANCELLED'
|
||||
| 'DESTROYED'
|
||||
| 'DETACHED'
|
||||
| 'NO_TARGET'
|
||||
| 'REJECTED'
|
||||
| 'SUPERSEDED'
|
||||
| 'TIMEOUT';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
When documenting errors:
|
||||
|
||||
- [ ] Error code table with all codes
|
||||
- [ ] Expected vs unexpected classification
|
||||
- [ ] Global handler example (onError)
|
||||
- [ ] Local handler example (try/catch)
|
||||
- [ ] Type guard usage shown
|
||||
- [ ] Troubleshooting section for common errors
|
||||
- [ ] Each troubleshooting entry has: Cause + Solution
|
||||
- [ ] Code examples are self-contained
|
||||
@@ -0,0 +1,303 @@
|
||||
# Progressive Disclosure Pattern
|
||||
|
||||
How to layer information for different audiences.
|
||||
|
||||
## Four-Tier Information Hierarchy
|
||||
|
||||
| Tier | Purpose | Audience | Length |
|
||||
|------|---------|----------|--------|
|
||||
| **Quick Start** | First success in <5 min | Everyone | 1 page |
|
||||
| **Concepts** | Mental models | Learning | 2-5 pages |
|
||||
| **Guides** | Task completion | Building | Per-task |
|
||||
| **API Reference** | Complete spec | Referencing | Comprehensive |
|
||||
|
||||
## Quick Start Pattern
|
||||
|
||||
Goal: Working code in under 5 minutes.
|
||||
|
||||
```markdown
|
||||
## Quick Start
|
||||
|
||||
### Install
|
||||
|
||||
npm install @videojs/react
|
||||
|
||||
### Use
|
||||
|
||||
import { Player } from '@videojs/react';
|
||||
|
||||
function App() {
|
||||
return <Player src="video.mp4" />;
|
||||
}
|
||||
|
||||
That's it. [See the full guide →](/guides/getting-started)
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- Max 3 code blocks
|
||||
- No configuration options
|
||||
- No edge cases
|
||||
- Link to "full guide" for more
|
||||
|
||||
## Expandable Sections
|
||||
|
||||
Use `<details>` for optional depth:
|
||||
|
||||
```markdown
|
||||
## Configuration
|
||||
|
||||
const player = createPlayer({ src: 'video.mp4' });
|
||||
|
||||
<details>
|
||||
<summary>All configuration options</summary>
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `src` | `string` | — | Source URL |
|
||||
| `autoplay` | `boolean` | `false` | Auto-start |
|
||||
| `muted` | `boolean` | `false` | Start muted |
|
||||
| ... | ... | ... | ... |
|
||||
|
||||
</details>
|
||||
```
|
||||
|
||||
## Tabbed Complexity
|
||||
|
||||
Simple → Advanced in tabs:
|
||||
|
||||
```markdown
|
||||
## Creating a Player
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Basic">
|
||||
const player = createPlayer({ src: 'video.mp4' });
|
||||
</Tab>
|
||||
<Tab label="With Options">
|
||||
const player = createPlayer({
|
||||
src: 'video.mp4',
|
||||
autoplay: true,
|
||||
muted: true,
|
||||
tracks: [{ kind: 'subtitles', src: 'en.vtt' }],
|
||||
});
|
||||
</Tab>
|
||||
<Tab label="Full Control">
|
||||
const player = createPlayer({
|
||||
src: 'video.mp4',
|
||||
autoplay: true,
|
||||
muted: true,
|
||||
loop: false,
|
||||
preload: 'metadata',
|
||||
crossOrigin: 'anonymous',
|
||||
tracks: [
|
||||
{ kind: 'subtitles', src: 'en.vtt', label: 'English', default: true },
|
||||
{ kind: 'subtitles', src: 'es.vtt', label: 'Español' },
|
||||
],
|
||||
plugins: [analyticsPlugin(), adsPlugin()],
|
||||
onPlay: () => console.log('play'),
|
||||
onError: (e) => console.error(e),
|
||||
});
|
||||
</Tab>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
## "See Also" Sections
|
||||
|
||||
End every page with related content:
|
||||
|
||||
```markdown
|
||||
## See Also
|
||||
|
||||
- [Events Guide](/guides/events) — Listen to player events
|
||||
- [Styling Guide](/guides/styling) — Customize appearance
|
||||
- [API Reference](/api/player) — Full Player API
|
||||
```
|
||||
|
||||
## Callout Boxes
|
||||
|
||||
For important asides without breaking flow:
|
||||
|
||||
```markdown
|
||||
:::note
|
||||
The player must be attached before calling `play()`.
|
||||
:::
|
||||
|
||||
:::warning
|
||||
`autoplay` requires `muted` in most browsers.
|
||||
:::
|
||||
|
||||
:::tip
|
||||
Use `preload="metadata"` for faster initial load.
|
||||
:::
|
||||
```
|
||||
|
||||
## Inline Links
|
||||
|
||||
Link concepts on first mention:
|
||||
|
||||
```markdown
|
||||
Create a [player](/api/player) and attach it to a
|
||||
[media element](/concepts/media-elements). The player uses
|
||||
[requests](/concepts/requests) to coordinate state changes.
|
||||
```
|
||||
|
||||
## Layered Examples
|
||||
|
||||
Same feature, increasing detail:
|
||||
|
||||
```markdown
|
||||
## Playing Media
|
||||
|
||||
### Basic
|
||||
|
||||
player.play();
|
||||
|
||||
### With Error Handling
|
||||
|
||||
try {
|
||||
await player.play();
|
||||
} catch (error) {
|
||||
if (error.name === 'NotAllowedError') {
|
||||
// Autoplay blocked, show play button
|
||||
}
|
||||
}
|
||||
|
||||
### With Request API
|
||||
|
||||
const result = await player.request.play();
|
||||
|
||||
if (!result.success) {
|
||||
switch (result.error.code) {
|
||||
case 'NOT_ALLOWED':
|
||||
// Show play button
|
||||
break;
|
||||
case 'NOT_SUPPORTED':
|
||||
// Show format error
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Feature Flags
|
||||
|
||||
Document experimental features separately:
|
||||
|
||||
```markdown
|
||||
## Experimental Features
|
||||
|
||||
:::warning
|
||||
These features may change or be removed.
|
||||
:::
|
||||
|
||||
### Picture-in-Picture
|
||||
|
||||
Enable with the `experimentalPiP` flag:
|
||||
|
||||
const player = createPlayer({
|
||||
src: 'video.mp4',
|
||||
experimentalPiP: true,
|
||||
});
|
||||
```
|
||||
|
||||
## Version-Specific Content
|
||||
|
||||
Show version differences:
|
||||
|
||||
```markdown
|
||||
## Migration from v9
|
||||
|
||||
<Tabs>
|
||||
<Tab label="v9 (Old)">
|
||||
videojs('player', { sources: [{ src: 'video.mp4' }] });
|
||||
</Tab>
|
||||
<Tab label="v10 (New)">
|
||||
createPlayer({ src: 'video.mp4' });
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### What Changed
|
||||
|
||||
| v9 | v10 |
|
||||
|----|-----|
|
||||
| `videojs()` function | `createPlayer()` |
|
||||
| `sources` array | `src` string |
|
||||
| jQuery-style API | Modern async API |
|
||||
```
|
||||
|
||||
## Audience Markers
|
||||
|
||||
Signal who content is for:
|
||||
|
||||
```markdown
|
||||
## Advanced: Custom Tech
|
||||
|
||||
> This section is for library authors building custom playback engines.
|
||||
|
||||
A Tech is the abstraction layer between the player and the media element...
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
State requirements upfront:
|
||||
|
||||
```markdown
|
||||
## Building Plugins
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Familiarity with the [Player API](/api/player)
|
||||
- Understanding of [Events](/concepts/events)
|
||||
- Node.js 18+
|
||||
|
||||
### Before You Start
|
||||
|
||||
Complete the [Getting Started](/guides/getting-started) guide first.
|
||||
```
|
||||
|
||||
## Summary Boxes
|
||||
|
||||
TL;DR for skimmers:
|
||||
|
||||
```markdown
|
||||
## State Management
|
||||
|
||||
:::summary
|
||||
- State is readonly — use requests to change it
|
||||
- Requests are async and can fail
|
||||
- Subscribe to state changes with `subscribe()`
|
||||
:::
|
||||
|
||||
The player uses a unidirectional data flow...
|
||||
```
|
||||
|
||||
## Code Annotations
|
||||
|
||||
Explain complex code inline:
|
||||
|
||||
```ts
|
||||
const player = createPlayer({
|
||||
src: 'video.mp4',
|
||||
// 1. Autoplay requires muted in most browsers
|
||||
autoplay: true,
|
||||
muted: true,
|
||||
// 2. Preload metadata for faster start
|
||||
preload: 'metadata',
|
||||
// 3. Enable CORS for cross-origin sources
|
||||
crossOrigin: 'anonymous',
|
||||
});
|
||||
```
|
||||
|
||||
## Skip Links
|
||||
|
||||
Let users jump to what they need:
|
||||
|
||||
```markdown
|
||||
## Player Configuration
|
||||
|
||||
**Jump to:** [Basic](#basic) | [Sources](#sources) | [Tracks](#tracks) | [Events](#events) | [Plugins](#plugins)
|
||||
|
||||
### Basic
|
||||
...
|
||||
|
||||
### Sources
|
||||
...
|
||||
```
|
||||
@@ -0,0 +1,255 @@
|
||||
# Props Tables Pattern
|
||||
|
||||
Consistent formats for documenting props, data attributes, CSS variables, and events.
|
||||
|
||||
## Props Table Format
|
||||
|
||||
```markdown
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `src` | `string` | — | Media source URL |
|
||||
| `autoplay` | `boolean` | `false` | Start playing automatically |
|
||||
| `muted` | `boolean` | `false` | Start muted |
|
||||
| `loop` | `boolean` | `false` | Loop playback |
|
||||
| `controls` | `boolean` | `true` | Show default controls |
|
||||
| `onPlay` | `() => void` | — | Called when playback starts |
|
||||
```
|
||||
|
||||
### Conventions
|
||||
|
||||
**Required props:** No default, use `—` or mark with `*`
|
||||
|
||||
```markdown
|
||||
| `src`* | `string` | — | Media source URL (required) |
|
||||
```
|
||||
|
||||
**Optional props:** Always show default
|
||||
|
||||
```markdown
|
||||
| `volume` | `number` | `1` | Initial volume (0-1) |
|
||||
```
|
||||
|
||||
**Callback props:** `on` prefix, show signature
|
||||
|
||||
```markdown
|
||||
| `onTimeUpdate` | `(time: number) => void` | — | Called on time change |
|
||||
```
|
||||
|
||||
**Enum props:** Show all options
|
||||
|
||||
```markdown
|
||||
| `preload` | `'auto' \| 'metadata' \| 'none'` | `'metadata'` | Preload behavior |
|
||||
```
|
||||
|
||||
**Complex types:** Link to type definition
|
||||
|
||||
```markdown
|
||||
| `tracks` | [`TextTrack[]`](#texttrack) | `[]` | Text tracks (captions, subtitles) |
|
||||
```
|
||||
|
||||
## Data Attributes Table Format
|
||||
|
||||
```markdown
|
||||
| Attribute | Values | Description |
|
||||
|-----------|--------|-------------|
|
||||
| `data-state` | `'idle' \| 'loading' \| 'ready' \| 'error'` | Current player state |
|
||||
| `data-playing` | `''` | Present during playback |
|
||||
| `data-paused` | `''` | Present when paused |
|
||||
| `data-muted` | `''` | Present when muted |
|
||||
| `data-fullscreen` | `''` | Present in fullscreen |
|
||||
| `data-orientation` | `'horizontal' \| 'vertical'` | Slider orientation |
|
||||
```
|
||||
|
||||
### Boolean Attributes
|
||||
|
||||
For boolean state, document presence/absence:
|
||||
|
||||
```markdown
|
||||
| `data-playing` | Present when playing, absent when not |
|
||||
```
|
||||
|
||||
### State Attributes
|
||||
|
||||
For state machines, show all values:
|
||||
|
||||
```markdown
|
||||
| `data-state` | `'idle'` | Initial state |
|
||||
| | `'loading'` | Loading media |
|
||||
| | `'ready'` | Ready to play |
|
||||
| | `'playing'` | Currently playing |
|
||||
| | `'paused'` | Paused |
|
||||
| | `'ended'` | Playback ended |
|
||||
| | `'error'` | Error occurred |
|
||||
```
|
||||
|
||||
### Usage Examples
|
||||
|
||||
Always follow with CSS example:
|
||||
|
||||
```css
|
||||
/* Style based on state */
|
||||
.player[data-loading] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.player[data-playing] .play-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.player[data-paused] .pause-icon {
|
||||
display: none;
|
||||
}
|
||||
```
|
||||
|
||||
## CSS Variables Table Format
|
||||
|
||||
```markdown
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `--player-accent-color` | `#3b82f6` | Primary accent color |
|
||||
| `--player-bg` | `#000` | Background color |
|
||||
| `--player-controls-bg` | `rgba(0,0,0,0.7)` | Controls background |
|
||||
| `--player-slider-height` | `4px` | Slider track height |
|
||||
| `--player-thumb-size` | `12px` | Slider thumb size |
|
||||
```
|
||||
|
||||
### With Scoping
|
||||
|
||||
Document which component owns the variable:
|
||||
|
||||
```markdown
|
||||
### Player Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `--player-bg` | `#000` | Player background |
|
||||
|
||||
### Slider Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `--slider-height` | `4px` | Track height |
|
||||
```
|
||||
|
||||
### Usage Examples
|
||||
|
||||
```css
|
||||
/* Customize theme */
|
||||
.player {
|
||||
--player-accent-color: #ef4444;
|
||||
--player-bg: #1a1a1a;
|
||||
}
|
||||
|
||||
/* Dynamic sizing */
|
||||
.slider {
|
||||
--slider-height: 8px;
|
||||
}
|
||||
```
|
||||
|
||||
## Events Table Format
|
||||
|
||||
```markdown
|
||||
| Event | Payload | Description |
|
||||
|-------|---------|-------------|
|
||||
| `play` | `void` | Playback started |
|
||||
| `pause` | `void` | Playback paused |
|
||||
| `ended` | `void` | Playback ended |
|
||||
| `timeupdate` | `{ currentTime: number }` | Current time changed |
|
||||
| `volumechange` | `{ volume: number, muted: boolean }` | Volume or muted changed |
|
||||
| `error` | `{ code: number, message: string }` | Error occurred |
|
||||
| `statechange` | `{ state: PlayerState }` | Any state changed |
|
||||
```
|
||||
|
||||
### Event Payload Types
|
||||
|
||||
Link to type definitions:
|
||||
|
||||
```markdown
|
||||
| `error` | [`PlayerError`](#playererror) | Error occurred |
|
||||
```
|
||||
|
||||
### Event Examples
|
||||
|
||||
```ts
|
||||
player.on('timeupdate', ({ currentTime }) => {
|
||||
console.log(`Time: ${currentTime}s`);
|
||||
});
|
||||
|
||||
player.on('error', ({ code, message }) => {
|
||||
console.error(`Error ${code}: ${message}`);
|
||||
});
|
||||
```
|
||||
|
||||
## Methods Table Format
|
||||
|
||||
```markdown
|
||||
| Method | Signature | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `play()` | `() => Promise<void>` | Start playback |
|
||||
| `pause()` | `() => void` | Pause playback |
|
||||
| `seek()` | `(time: number) => void` | Seek to time |
|
||||
| `setVolume()` | `(volume: number) => void` | Set volume (0-1) |
|
||||
| `destroy()` | `() => void` | Cleanup player |
|
||||
```
|
||||
|
||||
## Returns Table Format
|
||||
|
||||
For functions/hooks:
|
||||
|
||||
```markdown
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `state` | `PlayerState` | Current state (readonly) |
|
||||
| `request` | `RequestAPI` | Methods to request changes |
|
||||
| `subscribe` | `(cb: Callback) => Unsubscribe` | Subscribe to updates |
|
||||
| `destroy` | `() => void` | Cleanup |
|
||||
```
|
||||
|
||||
## Expandable Types
|
||||
|
||||
For complex types, use collapsible details:
|
||||
|
||||
```markdown
|
||||
| `options` | [`PlayerOptions`](#playeroptions) | Configuration |
|
||||
|
||||
<details>
|
||||
<summary>PlayerOptions</summary>
|
||||
|
||||
| Property | Type | Default |
|
||||
|----------|------|---------|
|
||||
| `src` | `string` | — |
|
||||
| `autoplay` | `boolean` | `false` |
|
||||
| `muted` | `boolean` | `false` |
|
||||
|
||||
</details>
|
||||
```
|
||||
|
||||
## Framework Variations
|
||||
|
||||
### React Props
|
||||
|
||||
```markdown
|
||||
| Prop | Type | Default |
|
||||
|------|------|---------|
|
||||
| `ref` | `React.Ref<PlayerRef>` | — |
|
||||
| `children` | `React.ReactNode` | — |
|
||||
| `className` | `string \| (state) => string` | — |
|
||||
```
|
||||
|
||||
### Vue Props
|
||||
|
||||
```markdown
|
||||
| Prop | Type | Default |
|
||||
|------|------|---------|
|
||||
| `modelValue` | `number` | — |
|
||||
| `@update:modelValue` | `(value: number) => void` | — |
|
||||
```
|
||||
|
||||
### Svelte Props
|
||||
|
||||
```markdown
|
||||
| Prop | Type | Default |
|
||||
|------|------|---------|
|
||||
| `bind:value` | `number` | — |
|
||||
| `$bindable` | ✓ | Can be bound |
|
||||
```
|
||||
Reference in New Issue
Block a user