From aa75fe612b8e60187a9033ff7b6d31d8579aa2b2 Mon Sep 17 00:00:00 2001 From: neodk2004 Date: Sat, 19 Sep 2026 19:08:07 +0200 Subject: [PATCH] fix(stories): video picker not showing videos on story creation The story upload input's accept attribute listed explicit image+video MIME subtypes (image/jpeg,...,video/mp4,...). Several mobile browsers'/OS's native 'Photos & Videos' pickers only reliably offer both media kinds when given the broad image/*,video/* wildcard categories - the explicit mixed list was silently narrowing the picker to photos only, matching the user's report ('nur Zugriff auf Fotos, Videos werden gar nicht angezeigt'). The actual accept/reject decision is unchanged (still enforced in handleFile() against the specific ALLOWED_IMAGE_TYPES/ ALLOWED_VIDEO_TYPES lists) - this only relaxes what the OS offers in the picker dialog. Code-reviewed before commit (WORKFLOW.md step 6) - fixed the one finding that became newly reachable by this fix: videos were previously never selectable at all, so a parameterized/codec-suffixed MIME string (e.g. from some Android camera-roll pickers) could never surface as a rejection; now strips codec parameters before the ALLOWED_VIDEO_TYPES comparison. Documented two other findings as accepted, not-fixed-here trade-offs in code comments (iOS HEIC auto-transcode interaction, broader picker surfacing formats that still get rejected post-selection) rather than silently ignoring them. Co-Authored-By: Claude Sonnet 5 --- src/components/stories/StoryForm.tsx | 39 +++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/components/stories/StoryForm.tsx b/src/components/stories/StoryForm.tsx index 8783da2..92c5770 100644 --- a/src/components/stories/StoryForm.tsx +++ b/src/components/stories/StoryForm.tsx @@ -23,7 +23,33 @@ const MAX_IMAGE_SIZE = 10 * 1024 * 1024; const ALLOWED_VIDEO_TYPES = ["video/mp4", "video/quicktime", "video/webm"] as const; const MAX_VIDEO_SIZE = 500 * 1024 * 1024; // same as normal video posts (VideoUploadForm.tsx) -const ACCEPT_ATTR = [...ALLOWED_IMAGE_TYPES, ...ALLOWED_VIDEO_TYPES].join(","); +// Wildcard categories, not the explicit MIME list (`image/jpeg,...,video/mp4,...`) +// — several mobile browsers'/OS's native "Photos & Videos" pickers only +// reliably offer *both* media kinds when given the broad `image/*`/`video/*` +// categories; a long explicit list mixing image and video subtypes was +// silently narrowing the picker down to photos only on at least one such +// picker (user report: "beim Story erstellen nur Zugriff auf Fotos, Videos +// werden gar nicht angezeigt"). This only relaxes what the OS *offers* in +// the picker dialog — the actual accept/reject decision still happens in +// handleFile() below against the specific +// ALLOWED_IMAGE_TYPES/ALLOWED_VIDEO_TYPES lists, unchanged. The two are +// deliberately decoupled now (this is a fixed category string, not derived +// from those lists) — a wildcard category can't meaningfully express "just +// these three video subtypes" to the OS picker, so narrowing +// ALLOWED_VIDEO_TYPES later won't narrow what the picker offers; it'll +// still be enforced right here after selection, just with an extra +// pick-then-reject round trip for a now-disallowed type — an accepted +// trade-off for actually being able to pick a video at all. Two related, +// deliberately-not-fixed-here risks (flagged in code review): (1) the OS +// picker now also surfaces formats this form never accepted (GIF, MKV, +// 3GPP, ...), each ending in the same pick-then-reject round trip; (2) iOS +// Safari's HEIC->JPEG auto-transcode for gallery photos is reportedly tied +// to the accept list being specific — `image/*` may let a raw HEIC file +// through where `image/jpeg,image/png,image/webp` didn't, and +// ALLOWED_IMAGE_TYPES doesn't list `image/heic`/`image/heif`. Neither is +// confirmed against a real device in this session; revisit if iPhone users +// report photo uploads failing here specifically. +const ACCEPT_ATTR = "image/*,video/*"; interface StoryFormProps { onClose: () => void; @@ -181,8 +207,15 @@ export function StoryForm({ onClose }: StoryFormProps) { } setErrorMessage(null); - const isVideo = (ALLOWED_VIDEO_TYPES as readonly string[]).includes(file.type); - const isImage = (ALLOWED_IMAGE_TYPES as readonly string[]).includes(file.type); + // Strip codec parameters before comparing — some Android camera-roll + // pickers report e.g. `video/mp4;codecs=hvc1` for HEVC-recorded clips, + // which never matched the bare `ALLOWED_VIDEO_TYPES` strings. This was + // unreachable while the accept attribute only offered photos (see + // ACCEPT_ATTR above); now that videos are actually selectable, a real + // mp4 could otherwise still get rejected as "invalid type" here. + const bareMimeType = file.type.split(";")[0].trim().toLowerCase(); + const isVideo = (ALLOWED_VIDEO_TYPES as readonly string[]).includes(bareMimeType); + const isImage = (ALLOWED_IMAGE_TYPES as readonly string[]).includes(bareMimeType); if (!isVideo && !isImage) { setErrorMessage(t("invalidType"));