Files
porthole/docs/plans/2026-02-01-use-playback-selector.md
2026-02-01 16:52:38 -08:00

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.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

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 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

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"