Editors can upload PNG, JPEG, WebP, MP3 and WAV files inside question and lesson block editors. Audio and passage blocks use the same validated content model and renderer in lessons, practice, mocks and results. Listening transcripts that disclose an answer belong in the question explanation or solution, which is withheld until submission.
POST /api/media requires a current editor/admin session, same-origin request and per-editor rate limit. The raw request stream is capped at 4,000,000 bytes regardless of Content-Length.<uploader UUID>/<new object UUID>.<extension>. Original filenames are discarded; uploads never overwrite an existing object. Each upload adds an audit record without file contents./api/media/<key>. These durable references are included in existing question/attempt snapshots. No expiring signed URL is stored in content.APP_MODE=local stores files under DATA_DIR/media, outside public. Existing local-production safeguards still apply. Back up both data.json and media; tests use isolated directories. Local media is excluded from version control and deployment output tracing with the existing local data exclusions.
Set NEXT_PUBLIC_SUPABASE_URL, server-only SUPABASE_SERVICE_ROLE_KEY, and SUPABASE_MEDIA_BUCKET. Missing credentials never activate local fallback. Provision a dedicated private Supabase Storage bucket, with a 4,000,000-byte object limit and MIME allowlist:
image/png, image/jpeg, image/webp, audio/mpeg, audio/wav
Do not grant anon/authenticated roles direct bucket read, insert, update or delete access. Delivery and uploads go through the authorized application server using its service credential. Review existing storage.objects policies for broad access that would include this bucket. The adapter also rejects a missing or public bucket on each operation. Credentials, bucket IDs and direct storage URLs never reach editor responses.
The adapter follows the official upload, private download and bucket details APIs (checked 2026-09-22). Live Supabase delivery still needs hosted acceptance with real credentials and bucket policies.
There is deliberately no replace/delete endpoint: removing a block must not break saved attempts. Abandoned uploads can remain after canceled editing or an audit-write failure. Any future cleanup must check live lessons/questions and every historical attempt before deleting an object, with a retention window and operator approval. Back up the bucket together with database snapshots.
Large video uploads, resumable uploads, media transcoding and a searchable asset library are outside this increment. Existing YouTube embed support remains. Passage blocks are immutable question content rather than a separate shared passage catalogue. Accessibility accommodations for listening assessments need content review; do not put answer-revealing transcripts in visible question blocks.