docs: add playback selector plan

This commit is contained in:
William Valentin
2026-02-01 16:52:38 -08:00
parent 691f5908d3
commit b6d588843d

View File

@@ -0,0 +1,109 @@
# Use Playback Selector Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Add an asset variants endpoint and wire MediaPanel to use `pickVideoPlaybackVariant` for derived MP4 selection with a safe fallback.
**Architecture:** Introduce a minimal `/api/assets/:id/variants` route that returns `{ kind, size, key }` from `asset_variants`. MediaPanel fetches variants on-demand for videos, uses `pickVideoPlaybackVariant` to decide whether to request `video_mp4` (size 720), and falls back to original if the derived URL fails.
**Tech Stack:** Next.js App Router API routes, Postgres via `@tline/db`, Bun test runner.
### Task 1: Add variants API route
**Files:**
- Create: `apps/web/app/api/assets/[id]/variants/route.ts`
- Test: `apps/web/src/__tests__/variants-route.test.ts`
**Step 1: Write the failing test**
Create `apps/web/src/__tests__/variants-route.test.ts`:
```ts
import { test, expect } from "bun:test";
test("variants route returns only kind/size/key fields", async () => {
const { shapeVariants } = await import("../../app/api/assets/[id]/variants/shape");
const rows = [
{ kind: "video_mp4", size: 720, key: "derived/video/a.mp4", mime_type: "video/mp4" },
];
expect(shapeVariants(rows)).toEqual([{ kind: "video_mp4", size: 720, key: "derived/video/a.mp4" }]);
});
```
**Step 2: Run test to verify it fails**
Run: `bun test apps/web/src/__tests__/variants-route.test.ts`
Expected: FAIL (missing module or function)
**Step 3: Write minimal implementation**
- Create `apps/web/app/api/assets/[id]/variants/shape.ts` with `shapeVariants(rows)` that returns `{ kind, size, key }` only.
- Create `apps/web/app/api/assets/[id]/variants/route.ts`:
- Validate `id` with `z.string().uuid()`
- Query `asset_variants` by `asset_id`
- Return JSON array of `shapeVariants(rows)`
**Step 4: Run test to verify it passes**
Run: `bun test apps/web/src/__tests__/variants-route.test.ts`
Expected: PASS
**Step 5: Commit**
```bash
git add apps/web/app/api/assets/[id]/variants/route.ts apps/web/app/api/assets/[id]/variants/shape.ts \
apps/web/src/__tests__/variants-route.test.ts
git commit -m "feat: add asset variants endpoint"
```
### Task 2: Use playback selector in MediaPanel
**Files:**
- Modify: `apps/web/app/components/MediaPanel.tsx`
- Modify: `apps/web/app/lib/playback.ts`
- Test: `apps/web/src/__tests__/prefer-derived.test.ts`
**Step 1: Write the failing test**
Add to `apps/web/src/__tests__/prefer-derived.test.ts`:
```ts
import { test, expect } from "bun:test";
import { pickVideoPlaybackVariant } from "../../app/lib/playback";
test("pickVideoPlaybackVariant returns null when no variants", () => {
expect(
pickVideoPlaybackVariant({
originalMimeType: "video/x-matroska",
variants: [],
}),
).toBeNull();
});
```
**Step 2: Run test to verify it fails**
Run: `bun test apps/web/src/__tests__/prefer-derived.test.ts`
Expected: FAIL (function does not handle empty variants)
**Step 3: Write minimal implementation**
- Update `pickVideoPlaybackVariant` to return `null` when no `video_mp4` variants exist.
- Update `MediaPanel` video URL loader to:
1) Fetch `/api/assets/:id/variants`
2) Call `pickVideoPlaybackVariant`
3) If variant found → request `kind=video_mp4&size=720`
4) If not found or fetch fails → request `variant=original`
**Step 4: Run test to verify it passes**
Run: `bun test apps/web/src/__tests__/prefer-derived.test.ts`
Expected: PASS
**Step 5: Commit**
```bash
git add apps/web/app/components/MediaPanel.tsx apps/web/app/lib/playback.ts \
apps/web/src/__tests__/prefer-derived.test.ts
git commit -m "fix: use playback selector in MediaPanel"
```