chore(root): refresh agent skills and docs (#1835)

This commit is contained in:
rahim
2026-07-27 16:21:40 -07:00
committed by GitHub
parent b4b30b0a01
commit fd4d2662ea
206 changed files with 2199 additions and 22577 deletions
+32
View File
@@ -0,0 +1,32 @@
# @videojs/spf source guide
Treat source and colocated tests as implementation truth. Use internal/design/spf only for durable rationale and conventions.
## Layers
- core: framework-neutral composition, signals, tasks, actors, and reactors. It must not depend on media, network, or playback.
- media: media models, algorithms, and format parsing; browser media helpers live under media/dom.
- network: fetch and bandwidth primitives.
- playback: playback actors, behaviors, and engines; browser-bound playback code lives in nested dom directories.
- index.ts and dom.ts are the primary entry points. Package exports also expose HLS and background-video compositions.
Place code in the lowest layer that satisfies its dependencies. Keep browser APIs out of core and review public entry-point changes deliberately.
## Working rules
- Read only the relevant file under internal/design/spf/conventions.
- Follow nearby ownership, lifecycle, cleanup, and test patterns.
- Add public exports intentionally and check their bundle impact when relevant.
- Prefer clear expressions over mutable intermediate state when both remain readable.
## Verification
Use the narrowest relevant test while iterating, then run as appropriate:
```bash
pnpm -F @videojs/spf test
pnpm -F @videojs/spf build
pnpm -F @videojs/spf size
```
Load one specialized SPF skill only when the requested workflow needs it.
+1 -104
View File
@@ -1,104 +1 @@
# `@videojs/spf` source layout
Guidance for AI agents and contributors working in `packages/spf/src/`.
## Top-level layout
```
src/
core/ SPF runtime primitives — signals, effects, tasks, actors, reactors, composition
media/ CML-like media building blocks — types, parsers, ABR, buffer logic, MSE/VTT primitives
network/ HTTP fetch utilities, chunked-stream iterables
playback/ Playback domain — composes core+media+network into engines
primitives/ Signal-aware playback building blocks composed by behaviors (no behavior lifecycle)
behaviors/ Compositional behaviors that drive playback (use signals/effects)
actors/ Actor factories used inside behaviors
engines/ Assembled playback engines (currently just hls/)
```
`media/`, `network/`, and `playback/behaviors/` each have a `dom/` subdirectory for DOM-bound code (browser APIs like `HTMLMediaElement`, `MediaSource`, `VTTCue`). DOM-free code lives outside those subdirs.
## Dependency rules
| From → To | Allowed? | Enforced by |
|---|---|---|
| `media/``core/` | ❌ | `media/tsconfig.json` does not reference `core` |
| `media/``network/` | ❌ | `media/tsconfig.json` does not reference `network` |
| `media/``playback/` | ❌ | `playback/` not referenced |
| `network/``core/` | ❌ | `network/tsconfig.json` has empty `references` |
| `network/``media/` | ❌ | same |
| `network/``playback/` | ❌ | same |
| `core/``media/`, `network/`, `playback/` | ❌ | `core/tsconfig.json` does not reference downstream subtrees |
| `core/` → DOM | ❌ | `core/tsconfig.json` `lib` excludes `DOM` |
| `media/` (non-dom) → DOM | ❌ | `media/tsconfig.json` `lib` excludes `DOM` |
| `network/` → DOM | ❌ | `network/tsconfig.json` `lib` excludes `DOM` |
| `playback/primitives/` → DOM | ❌ | tsconfig `lib` excludes `DOM` |
| `playback/behaviors/` (non-dom) → DOM | ❌ | tsconfig `lib` excludes `DOM` |
| `playback/actors/` (non-dom) → DOM | ❌ | tsconfig `lib` excludes `DOM` |
| `playback/behaviors/``playback/primitives/` | ✅ | references |
| `playback/``core/`, `network/`, `media/` | ✅ | references in playback tsconfigs |
| `playback/engines/hls/``playback/behaviors/`, `playback/actors/` | ✅ | references |
| `playback/engines/hls/``core/`, `media/` | ✅ — engines compose primitives directly | references |
The substance: `core/`, `media/`, `network/` are framework-agnostic foundations. `playback/` is the SPF-integrated layer that composes them. Engines live at the top of the dependency tree and may reach into any foundation.
## Where to put new code
- **Pure media/streaming logic** (parsers, types, selection algorithms, MSE/VTT helpers without signals): `media/` or `media/dom/`. Must not import from `core/`.
- **Signal-aware playback primitive** (a composable building block that reads/writes `core` signals but has no behavior lifecycle — no `effect`/`computed`/`subscribe`, not invoked by composition): `playback/primitives/`. These are composed *by* behaviors/actors/engines (e.g. `failoverFetch`, a fetch decorator that reads selected-track signals at fetch time; `track-types`, the per-type config bundles). They live at the `playback/` layer rather than `media/`/`network/` precisely because they touch `core/` — that's the line below.
- **Compositional behavior driving state** (uses `effect`/`computed`/`update` against owners or state signals): `playback/behaviors/` or `playback/behaviors/dom/`.
- **Actor factories** (long-lived stateful units that receive messages): `playback/actors/` or `playback/actors/dom/`.
- **Engine compositions** (wiring behaviors+actors+config into a `createComposition` call): `playback/engines/<name>/`.
- **Generic, framework-agnostic utilities** that aren't media-specific: prefer `@videojs/utils` over creating new homes inside spf.
If a module looks like a `media/`/`network/` primitive but reaches into `core/`, that's a smell — first consider whether the signal binding can move to the call site (see `onMediaSourceReadyStateChange` for a callback-shaped primitive that lets the consumer create the signal), keeping the primitive itself `core`-free. Only when a building block genuinely needs *live* signal access — e.g. `failoverFetch` is constructed once but its returned fetch reads `presentation`/selected-track signals lazily on every call — does it belong at the `playback/` layer, in `playback/primitives/`.
Conversely: if a function inside `playback/behaviors/` (or `playback/actors/`) has no `core/` dependency — no signals, effects, or reactors — it probably belongs in a layer below (`media/`, `network/`, or `@videojs/utils`). Same layering principle, opposite direction. When reviewing a behavior or actor file, scan its top-level helpers; any pure data-manipulation / lookup / format-handling code with no reactive concerns is a candidate to extract.
## DOM-vs-not enforcement
The DOM/no-DOM split is enforced by `lib` settings on each subtree's `tsconfig.json`:
- DOM-free subtrees use `lib: ["ES2022", "WebWorker"]` — referencing `HTMLMediaElement`, `document`, etc. fails typecheck.
- DOM subtrees use `lib: ["ES2022", "DOM", "DOM.Iterable"]`.
This is structural — you cannot accidentally import DOM types into a non-DOM module without surfacing a typecheck error.
## Public exports
External consumers go through three entry points (defined in `packages/spf/package.json`):
- `@videojs/spf` — main entry (`src/index.ts`): runtime-agnostic primitives + media/network helpers.
- `@videojs/spf/dom` — DOM-bound exports (`src/dom.ts`): re-exports from `playback/behaviors/dom/` and `media/dom/`.
- `@videojs/spf/hls` — HLS engine (`src/playback/engines/hls/index.ts`).
Internal paths are not part of the public API. Don't import from `@videojs/spf/playback/...` or similar deep paths in other workspace packages.
## Vitest projects
`packages/spf/vitest.config.ts` shards tests by area:
- `core`, `media`, `network`, `behaviors` — Node, no browser (the `behaviors` project also covers `playback/actors/` and `playback/primitives/`)
- `dom` — Chromium via Playwright, covers all `**/dom/**/*.test.ts` across subtrees
- `playback-engines` — Chromium, covers engines/
- `types` — type-only tests via tsgo
When adding a test in a new location, check the project globs match.
## Code style
SPF-specific style preferences. These extend the workspace-wide rules in the root `CLAUDE.md` — they apply across `packages/spf/src/`, not just behaviors. (Style guidance specific to a single domain — behavior naming, reactor state names, signal access patterns — lives in `internal/design/spf/conventions/` instead.)
### Prefer `const` + ternary over `let` + conditional reassignment
For single-branch derivations of one value, use a `const` with a ternary rather than a `let` that gets conditionally overwritten:
```ts
// Good
const duration = maxBufferedEnd > target ? maxBufferedEnd : target;
// Avoid
let duration = target;
if (maxBufferedEnd > duration) {
duration = maxBufferedEnd;
}
```
The `const`-ternary form makes the value's identity immutable from its first binding — readers don't have to scan ahead for reassignment, and the dependency on each branch's inputs is structurally clear. `let` is appropriate when the variable is genuinely mutable across multiple statements (accumulators, loop indices, state that flips on conditions other than a single-shot derivation); reserve it for those cases.
@AGENTS.md
@@ -25,7 +25,7 @@
* Inert where it shouldn't act: live (the MediaSource never reaches `ended` while the
* window grows; `duration` is `Infinity`) and streams that end cleanly (no `waiting`).
*
* See `internal/decisions/end-of-stream-av-skew-recovery.md`.
* See `internal/decisions/spf/end-of-stream-av-skew-recovery.md`.
*/
import { listen } from '@videojs/utils/dom';
import { defineBehavior } from '../../../core/composition/create-composition';