628c398e

By: Michael Lynch <git@mtlynch.io>

Add owner media uploads (milestone 6)

Owners can now upload photos and videos through the web UI, the first
write path in the app. Everything was previously read-only, populated
only by the offline import-from-tinybeans command.

Behavior:
- An always-visible "Upload" button appears in the navbar for owners
  (outside the collapsible region so it stays visible on mobile).
- Photos (JPEG) and animated GIFs are processed synchronously: the
  full-size image is re-encoded to strip Exif/metadata, and display and
  thumbnail variants are generated, before the item is published.
- Videos are transcoded to WebM (VP9 + Opus) in a background worker. The
  uploader sees a "Video processing in progress" notice and the item is
  hidden from other users until transcoding completes.
- Uploads are owner-only; subscribers get 403 and never see the button.

Media model and storage:
- Video is now always WebM. The TinyBeans import pipeline transcodes
  imported .mp4 videos to .webm too, so the system has a single video
  format. ImportMedia takes an injected video converter alongside the
  GIF converter.
- media_items gains a status column (ready/processing). ReadMediaItems
  filters by viewer so processing items are visible only to their
  uploader.

Other changes:
- newPageData resolves the current user to expose IsOwner to templates.
- The app binary is wrapped with ffmpeg in flake.nix, and the prod
  Dockerfile installs ffmpeg, since the app now execs ffmpeg/ffprobe.

Known limitation: because raw originals are not retained, a server
restart mid-transcode leaves a video stuck in the processing state;
durable job scheduling is future work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Suite timing

Time to Start Worker time Duration Time to finish Idle
Config 1s 9s 9s 10s 1s
Eval 8s 55s 55s 1m04s 0s
Build 16s 13m28s 3m21s 3m38s 0s
Suite 1s 14m33s 3m36s 3m38s 1s

Timeline

0s20s40s1m1m20s1m40s2m2m20s2m40s3m3m20s