3.7 KiB
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:
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.tswithshapeVariants(rows)that returns{ kind, size, key }only. - Create
apps/web/app/api/assets/[id]/variants/route.ts:- Validate
idwithz.string().uuid() - Query
asset_variantsbyasset_id - Return JSON array of
shapeVariants(rows)
- Validate
Step 4: Run test to verify it passes
Run: bun test apps/web/src/__tests__/variants-route.test.ts
Expected: PASS
Step 5: Commit
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:
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
pickVideoPlaybackVariantto returnnullwhen novideo_mp4variants exist. - Update
MediaPanelvideo URL loader to:- Fetch
/api/assets/:id/variants - Call
pickVideoPlaybackVariant - If variant found → request
kind=video_mp4&size=720 - If not found or fetch fails → request
variant=original
- Fetch
Step 4: Run test to verify it passes
Run: bun test apps/web/src/__tests__/prefer-derived.test.ts
Expected: PASS
Step 5: Commit
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"