>) {
- return extendBaseConfig(baseConfig, extension);
-}
-
-export const { Provider, create } = createStore(extendConfig());
-```
-
-### 5.2 Skin component
-
-**File:** `packages/react/src/skins/frosted/skin.tsx`
-
-```typescript
-import type { PropsWithChildren } from 'react';
-
-export type SkinProps = PropsWithChildren<{
- className?: string;
-}>;
-
-export function Skin({ children, className }: SkinProps): JSX.Element {
- return (
-
- {children}
- {/* Controls rendered here */}
-
- );
-}
-
-/**
- * Namespace pattern for component types.
- * Allows: `Skin.Props` instead of importing `SkinProps` separately.
- */
-export namespace Skin {
- export type Props = SkinProps;
-}
-```
-
-### 5.3 Exports
-
-**File:** `packages/react/src/skins/frosted/index.ts`
-
-```typescript
-export { Skin } from './skin';
-export type { SkinProps } from './skin';
-export { extendConfig, Provider } from './store';
-```
-
----
-
-## Phase 6: Frosted Skin [DONE]
-
-> Refer to [PR #298](https://github.com/videojs/v10/pull/298) for implementation details.
-
-React and HTML frosted skin store setup:
-
-- React: `Provider`, `Skin`, `extendConfig`, `createStore` in `@videojs/react/skins/frosted`
-- HTML: `FrostedSkinElement`, `StoreMixin`, `extendConfig` in `@videojs/html/skins/frosted`
-- Auto-define entry: `@videojs/html/define/vjs-frosted-skin` for CDN usage
-- `StoreAttachMixin` uses default slot (no `slot="media"` attribute needed)
-
-**Note:** Don't export `context` from skins (causes unique symbol type issues). Users can import from `@videojs/store/lit` if needed.
-
----
-
-## Usage Examples
-
-### React: Custom UI
-
-```tsx
-import { createStore, media, Video } from '@videojs/react';
-// With mutation status tracking (base hook - import directly)
-
-// With optimistic updates (base hook - import directly)
-import { useMutation, useOptimistic } from '@videojs/store/react';
-
-// Note: media is re-exported from @videojs/react (not @videojs/core/dom)
-
-const { Provider, useStore, useSelector, useRequest } = createStore({
- slices: [media.playback],
-});
-
-function App() {
- return (
-
-
-
-
- );
-}
-
-function MyCustomControls() {
- const currentTime = useSelector((s) => s.currentTime);
- const seek = useRequest('seek');
- return ;
-}
-
-function PlayButton() {
- const store = useStore();
- const paused = useSelector((s) => s.paused);
- const playResult = useMutation(store, 'play');
- const pauseResult = useMutation(store, 'pause');
-
- return (
-
- );
-}
-
-function VolumeSlider() {
- const store = useStore();
- const result = useOptimistic(store, 'setVolume', (s) => s.volume);
-
- return (
- <>
- result.setValue(Number(e.target.value))}
- style={{ opacity: result.status === 'pending' ? 0.5 : 1 }}
- />
- {result.status === 'error' && Failed to change volume}
- >
- );
-}
-```
-
-### React: Pre-created store instance (imperative access)
-
-```tsx
-import { useState } from 'react';
-
-import { createStore, media, Video } from '@videojs/react';
-
-const { Provider, create, useSelector } = createStore({
- slices: [media.playback],
-});
-
-function App() {
- // Create store instance in useState for stable reference
- const [store] = useState(() => create());
-
- return (
-
-
-
-
- );
-}
-```
-
-### React: Frosted skin
-
-```tsx
-import { Video } from '@videojs/react';
-import { Provider, Skin } from '@videojs/react/skins/frosted';
-
-function App() {
- return (
-
-
-
-
-
- );
-}
-```
-
-### React: Extending frosted with custom slices
-
-```tsx
-import { createStore, Video } from '@videojs/react';
-import { extendConfig, Skin } from '@videojs/react/skins/frosted';
-
-import { chaptersSlice } from './slices/chapters';
-
-// Extend frosted config with custom slice (merges with base slices)
-const { Provider, useSlice } = createStore(
- extendConfig({ slices: [chaptersSlice] })
-);
-
-function App() {
- return (
-
-
-
-
-
-
- );
-}
-
-function ChaptersPanel() {
- const chapters = useSlice(chaptersSlice);
- if (!chapters.isAvailable) return null;
- return {chapters.state.markers.map(...)}
;
-}
-```
-
-### HTML: Frosted skin (CDN)
-
-```html
-
-
-
-
-
-```
-
-### HTML: Custom provider element
-
-```html
-
-
-
-
-
-
-```
-
-Where `my-player.js` contains:
-
-```typescript
-import { ReactiveElement } from '@lit/reactive-element';
-import { media } from '@videojs/core/dom';
-import { createStore } from '@videojs/store/lit';
-
-const { StoreMixin } = createStore({
- slices: [...media.all],
-});
-
-// Create custom element with store provider and auto-attach
-class MyPlayer extends StoreMixin(ReactiveElement) {
- constructor() {
- super();
- const shadow = this.attachShadow({ mode: 'open' });
- shadow.innerHTML = '';
- }
-}
-
-customElements.define('my-player', MyPlayer);
-```
-
-### HTML: Extending frosted with custom slices
-
-```html
-
-
-
-
-
-```
-
-Where `my-extended-skin.js` contains:
-
-```typescript
-import { extendConfig, FrostedSkinElement } from '@videojs/html/skins/frosted';
-import { createStore } from '@videojs/store/lit';
-
-import { chaptersSlice } from './slices/chapters.js';
-
-// Extend frosted config with custom slice (merges with base slices)
-const { StoreMixin } = createStore(extendConfig({ slices: [chaptersSlice] }));
-
-FrostedSkinElement.define('my-extended-skin', StoreMixin);
-```
-
----
-
-## File Structure
-
-```
-packages/utils/src/
-├── array/
-│ ├── uniq-by.ts # DONE
-│ ├── tests/
-│ │ └── uniq-by.test.ts # DONE
-│ └── index.ts # DONE
-└── function/
- ├── compose-callbacks.ts # DONE
- ├── tests/
- │ └── compose-callbacks.test.ts # DONE
- └── index.ts # DONE
-
-packages/store/src/
-├── core/
-│ ├── store.ts # existing
-│ ├── slice.ts # existing
-│ ├── queue.ts # existing
-│ ├── extend-config.ts # DONE
-│ ├── tests/
-│ │ └── extend-config.test.ts # DONE
-│ └── index.ts # DONE
-├── shared/
-│ └── types.ts # DONE (AsyncStatus, MutationResult, OptimisticResult)
-├── react/
-│ ├── context.ts # DONE (internal shared context)
-│ ├── create-store.tsx # DONE (Provider, useStore, useSelector, useRequest, useTasks)
-│ ├── hooks/ # DONE (base hooks split into separate files)
-│ │ ├── index.ts
-│ │ ├── use-selector.ts
-│ │ ├── use-request.ts
-│ │ ├── use-tasks.ts
-│ │ ├── use-mutation.ts
-│ │ ├── use-optimistic.ts # DONE (PR #291)
-│ │ └── tests/
-│ │ ├── test-utils.ts
-│ │ ├── use-selector.test.tsx
-│ │ ├── use-request.test.tsx
-│ │ ├── use-tasks.test.tsx
-│ │ ├── use-mutation.test.tsx
-│ │ └── use-optimistic.test.tsx # DONE (PR #291)
-│ └── index.ts
-└── lit/
- ├── create-store.ts # DONE (StoreMixin, StoreProviderMixin, StoreAttachMixin, context)
- ├── controllers/ # DONE (split into separate files)
- │ ├── index.ts
- │ ├── selector-controller.ts
- │ ├── request-controller.ts
- │ ├── tasks-controller.ts
- │ ├── mutation-controller.ts
- │ └── optimistic-controller.ts
- └── index.ts
-
-packages/core/src/dom/
-├── store/
-│ └── slices/
-│ ├── playback.ts # DONE
-│ ├── time.ts # DONE
-│ ├── buffer.ts # DONE
-│ ├── volume.ts # DONE
-│ ├── source.ts # DONE
-│ ├── index.parts.ts # DONE (media namespace parts)
-│ ├── index.ts # DONE (media namespace + exports)
-│ └── tests/ # DONE
-├── predicate.ts # DONE (type guards)
-└── index.ts # DONE
-
-packages/react/src/
-├── media/
-│ └── video.tsx # NEW
-├── skins/
-│ ├── frosted/
-│ │ ├── store.ts # NEW
-│ │ ├── skin.tsx # NEW
-│ │ └── index.ts # NEW
-│ └── minimal/
-│ └── ...
-└── index.ts
-
-packages/html/src/
-├── define/
-│ └── vjs-frosted-skin.ts # NEW
-├── skins/
-│ ├── frosted/
-│ │ ├── store.ts # NEW
-│ │ ├── skin.ts # NEW
-│ │ ├── styles.css # NEW
-│ │ └── index.ts # NEW
-│ └── minimal/
-│ └── ...
-└── index.ts
-```
-
----
-
-## Implementation Order
-
-1. **Phase 0**: Core utilities **[DONE - PR #283]**
- - `uniqBy`, `composeCallbacks` utilities ✓
- - `extendConfig` ✓
-
-2. **Phase 0.5**: Queue Task Refactor **[DONE - PR #287]**
- - Unified `tasks` map with status discriminator ✓
- - `PendingTask`, `SuccessTask`, `ErrorTask` types ✓
- - `reset(key)` method ✓
- - Update existing tests ✓
- - Added `tryCatch` utility to `@videojs/utils/function` ✓
-
-3. **Phase 1**: React Bindings (basic) **[DONE - PR #288]**
- - Shared context, `useStoreContext` ✓
- - `createStore()` with `inherit` prop ✓
- - `useStore`, `useSelector`, `useRequest`, `useTasks` ✓
- - Base hooks for testing/advanced use ✓
-
-4. **Phase 2**: Lit Bindings (basic) **[DONE - PR #289]**
- - `createStore()` with mixins ✓
- - `SelectorController`, `RequestController`, `TasksController` ✓
- - `@lit/context` integration ✓
-
-5. **Phase 3**: DOM Media Slices **[DONE - PR #292]**
- - Modular slices: `playbackSlice`, `timeSlice`, `bufferSlice`, `volumeSlice`, `sourceSlice` ✓
- - `media` namespace export ✓
- - Type guards and utilities ✓
-
-6. **Phase 4**: Mutation Hooks/Controllers **[DONE - PR #290]**
- - React: `useMutation(store, name)` - base hook with direct name param ✓
- - React: `useRequest(store, name)` - updated to use direct name param ✓
- - Lit: `MutationController(host, store, name)` ✓
- - Shared types: `MutationResult` discriminated union in `src/shared/types.ts` ✓
- - Hooks split into `src/react/hooks/` directory ✓
-
-7. **Phase 5**: Optimistic Hooks/Controllers **[DONE - PR #291]**
- - React: `useOptimistic(store, name, stateSelector)` - base hook ✓
- - Lit: `OptimisticController(host, store, name, stateSelector)` - already existed ✓
- - Shared types: `OptimisticResult` discriminated union in `src/shared/types.ts` ✓
- - Tests: 13 new tests for React hook ✓
-
-8. **Phase 6**: Skins **[DONE - PR #298]**
- - React skin (Provider, Skin, extendConfig) ✓
- - HTML skin (FrostedSkinElement, extendConfig) ✓
-
-Each phase includes tests.
-
----
-
-## Package Configuration
-
-### tsdown.config.ts Updates
-
-Each package with new subpaths needs tsdown entry points:
-
-**`packages/html/tsdown.config.ts`:**
-
-```typescript
-import { readdirSync } from 'node:fs';
-
-// Dynamically gather define/ entries
-const defineEntries = readdirSync('src/define')
- .filter((f) => f.endsWith('.ts'))
- .reduce(
- (acc, f) => {
- const name = f.replace('.ts', '');
- acc[`define/${name}`] = `src/define/${f}`;
- return acc;
- },
- {} as Record
- );
-
-export default {
- entry: {
- index: 'src/index.ts',
- 'skins/frosted': 'src/skins/frosted/index.ts',
- ...defineEntries,
- },
-};
-```
-
-**`packages/react/tsdown.config.ts`:**
-
-```typescript
-export default {
- entry: {
- index: 'src/index.ts',
- 'skins/frosted': 'src/skins/frosted/index.ts',
- },
-};
-```
-
-**`packages/store/tsdown.config.ts`:**
-
-```typescript
-export default {
- entry: {
- index: 'src/index.ts',
- react: 'src/react/index.ts',
- lit: 'src/lit/index.ts',
- },
-};
-```
-
-### package.json Exports Updates
-
-Use `types` + `default` format to match existing packages.
-
-**`packages/html/package.json`:**
-
-```json
-{
- "exports": {
- ".": {
- "types": "./dist/index.d.ts",
- "default": "./dist/index.js"
- },
- "./define/*": {
- "default": "./dist/define/*.js"
- },
- "./skins/frosted": {
- "types": "./dist/skins/frosted.d.ts",
- "default": "./dist/skins/frosted.js"
- }
- }
-}
-```
-
-**`packages/react/package.json`:**
-
-```json
-{
- "exports": {
- ".": {
- "types": "./dist/index.d.ts",
- "default": "./dist/index.js"
- },
- "./skins/frosted": {
- "types": "./dist/skins/frosted.d.ts",
- "default": "./dist/skins/frosted.js"
- }
- }
-}
-```
-
-**`packages/store/package.json`:**
-
-```json
-{
- "exports": {
- ".": {
- "types": "./dist/index.d.ts",
- "default": "./dist/index.js"
- },
- "./react": {
- "types": "./dist/react.d.ts",
- "default": "./dist/react.js"
- },
- "./lit": {
- "types": "./dist/lit.d.ts",
- "default": "./dist/lit.js"
- }
- }
-}
-```
-
----
-
-## PR Coordination
-
-### Related Issues
-
-| Issue | Title | Description | Status |
-| ----- | ------------------- | --------------------------------------- | ---------------- |
-| #218 | Store | Parent tracking issue | Open |
-| #285 | Queue Task Refactor | Unified tasks map, status discriminator | Closed |
-| #228 | Optimistic Updates | useMutation, useOptimistic | Open |
-| #229 | React Bindings | createStore, hooks, context | Closed |
-| #230 | Lit Bindings | Controllers, mixins, context | Closed |
-| #239 | DOM Media Slices | media slices | Closed |
-| #231 | Skin Stores | Skin store configuration | Closed (PR #298) |
-
-### PR Strategy
-
-```
-PR #283: Core Utilities [DONE]
-├── uniqBy, composeCallbacks (utils) ✓
-├── extendConfig (store/core) ✓
-└── Tests ✓
-
-PR #287: Queue Task Refactor [DONE]
-├── Unified Task type with status discriminator ✓
-├── PendingTask, SuccessTask, ErrorTask ✓
-├── Single `tasks` map, `reset(key)` method ✓
-├── Update tests ✓
-├── Added tryCatch utility ✓
-└── Closes #285
-
-PR #288: React Bindings (basic) [DONE]
-├── createStore, Provider, useStore ✓
-├── useSelector, useRequest, useTasks ✓
-├── Base hooks for testing ✓
-├── References #218
-└── Closes #229
-
-PR #289: Lit Bindings (basic) [DONE]
-├── createStore with mixins ✓
-├── SelectorController, RequestController, TasksController ✓
-├── References #218
-└── Closes #230
-
-PR #292: DOM Media Slices [DONE]
-├── Modular slices: playback, time, buffer, volume, source ✓
-├── media namespace export ✓
-├── Type guards and utilities ✓
-├── References #218
-└── Closes #239
-
-PR #290: Mutation Hooks/Controllers [DONE]
-├── React: useMutation(store, name) - base hook with direct name param ✓
-├── React: useRequest(store, name) - updated to use direct name param ✓
-├── Lit: MutationController(host, store, name) ✓
-├── Shared types: MutationResult discriminated union ✓
-├── Hooks split into src/react/hooks/ directory ✓
-└── References #228
-
-PR #291: Optimistic Hooks/Controllers [DONE]
-├── React: useOptimistic(store, name, stateSelector) - base hook ✓
-├── Lit: OptimisticController(host, store, name, stateSelector) - already existed ✓
-├── Shared types: OptimisticResult discriminated union in src/shared/types.ts ✓
-├── Tests: 13 new tests for React hook ✓
-└── Closes #228
-
-PR #298: Skins [DONE]
-├── React skin (Provider, Skin, extendConfig) ✓
-├── HTML skin (FrostedSkinElement, extendConfig) ✓
-├── References #218
-└── Closes #231
-```
-
-### Dependency Graph
-
-```
-PR #283 ───> PR #287 ───> PR #288 ───> PR #290 ───> PR #291 ───> PR #298 (Skins)
- └──> PR #289 (done) ────────────────────────┘
- └──> PR #292 (done) ────────────────────────┘
-```
-
-All PRs merged. Store bindings implementation complete.
-
----
-
-## Deferred
-
-- Testing utilities (`@videojs/store/testing`) - separate plan
-- Minimal skin implementation
diff --git a/.claude/plans/store-queue-simplification.md b/.claude/plans/store-queue-simplification.md
deleted file mode 100644
index 40b9064b..00000000
--- a/.claude/plans/store-queue-simplification.md
+++ /dev/null
@@ -1,627 +0,0 @@
-# Store Queue Simplification Plan
-
-> **STATUS: COMPLETED**
->
-> Final result: ~405 LOC total (328 queue.ts + 77 task.ts) down from ~524 LOC.
-> Task types and guards extracted to separate `task.ts` module for better organization.
-
-## Overview
-
-Simplify the queue from ~524 LOC to ~200 LOC while preserving core value.
-
-**Goal:** Remove convenience features only used in tests/docs, keep what's essential for real-world async media operations.
-
----
-
-## Why the Queue Exists
-
-The store design: **all writes are async requests**. This isn't optional — it's the architecture.
-
-```ts
-// Read path: sync state from target
-store.state.paused; // Synced from video.paused
-
-// Write path: async requests to target
-await store.request.play(); // Returns Promise, always
-```
-
-Media operations are inherently async and can fail:
-
-| Scenario | What Can Happen |
-| ---------------------- | --------------------------------------------------- |
-| **Chromecast/AirPlay** | 100-500ms+ network round-trip, device disconnect |
-| **Source loading** | 1-5s+ load time, 404, DRM errors, codec unsupported |
-| **Quality switching** | Segment fetching, ABR delays |
-| **Network conditions** | Timeouts, flaky connections |
-| **User behavior** | Impatient clicks, changing mind mid-operation |
-
-The queue handles these realities uniformly.
-
----
-
-## Real-World Scenarios
-
-### Chromecast Play
-
-```ts
-// Command goes over network to Cast device
-await store.request.play();
-```
-
-**Without queue:**
-
-- How does UI show "Connecting..."?
-- User taps 5 times impatiently → 5 network messages?
-- Device disconnects mid-request → how to surface error?
-
-**With queue:**
-
-- `tasks['play'].status === 'pending'` → show loading
-- Supersession → only 1 message sent
-- `tasks['play'].status === 'error'` → show failure
-
-### Source Switching
-
-```ts
-// User browses playlist quickly
-store.request.setSource('a.mp4'); // Starts loading
-store.request.setSource('b.mp4'); // User changed mind
-store.request.setSource('c.mp4'); // Final choice
-```
-
-**Without queue:**
-
-- All three sources load simultaneously
-- Wasted bandwidth, race conditions
-- Which promise resolves? Which errors?
-
-**With queue:**
-
-- Supersession aborts a.mp4 and b.mp4
-- Only c.mp4 loads
-- Clean promise semantics (superseded reject with SUPERSEDED)
-
-### Network Failure
-
-```ts
-// Quality change needs segment fetch
-await store.request.setQuality('1080p');
-// Network times out
-```
-
-**Without queue:**
-
-- Component needs try/catch + useState for error
-- Every async operation repeats this boilerplate
-
-**With queue:**
-
-- `tasks['setQuality'].status === 'error'`
-- `tasks['setQuality'].error` contains details
-- `reset('setQuality')` + retry
-
-### Component Implementation Comparison
-
-**Without queue (manual):**
-
-```tsx
-function SourceSelector() {
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState(null);
-
- const handleChange = async (src) => {
- setLoading(true);
- setError(null);
- try {
- await loadSource(src);
- } catch (e) {
- setError(e);
- } finally {
- setLoading(false);
- }
- };
-
- return (
- <>
-