# 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" ```