feat(05): Phase 5 — short-form video via Mux
Schema:
- Add VideoPost model (muxUploadId, muxAssetId, muxPlaybackId,
status, durationSecs, aspectRatio) linked 1-to-1 with Post
- Add VideoStatus enum (PROCESSING/READY/ERROR)
- Add PostType.VIDEO
Backend:
- src/lib/mux.ts — singleton Mux client + webhook secret
- videos router: createUpload (Mux direct upload URL + PROCESSING post),
getByPostId (status polling), updateCaption
- POST /api/webhooks/mux: verifies signature; handles
video.upload.asset_created (store muxAssetId),
video.asset.ready (set READY + playbackId + fan-out to feed),
video.asset.errored (set ERROR)
- feed.ts + posts.byPetId: include videoPost in all post queries
UI:
- VideoUploadForm: drop zone → XHR PUT to Mux upload URL with
progress bar; caption field; transitions to "Processing…" on success
- VideoCard: PROCESSING shows spinner; READY renders MuxPlayer
(HLS, orange accent); polls every 5s until status changes
- PostTypeSheet: "Video Post" option added (between Photo and Milestone)
- PostCard: VIDEO type renders VideoCard instead of image carousel
Env vars: MUX_TOKEN_ID, MUX_TOKEN_SECRET, MUX_WEBHOOK_SECRET,
NEXT_PUBLIC_APP_URL — placeholders added to .env.local
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>