Two gaps where generic S3 trailed Cloudflare R2:
1. Object cleanup on delete/edit didn't work for generic S3. deleteVideoStorage
built its client via makeR2Client, which hard-forces path-style and ignores
the connection's forcePathStyle -- so vhost-style S3 deletes were misaddressed
and cleanup silently failed (S3 users' deleted/edited files orphaned). It is
now provider-aware (mirrors uploadFile): R2 -> makeR2Client (path-style),
generic S3 -> makeS3Client honoring forcePathStyle. makeS3Client is now
injectable for testing.
2. CORS guidance on upload failure was R2-only. Extracted isLikelyCorsError +
a generic-S3 guidance builder into js/utils/uploadErrorHints.js (re-exported
from r2Service for back-compat); s3UploadService now appends actionable CORS
guidance on an opaque "Failed to fetch" too.
Generic S3 is now at functional parity with R2 for upload -> publish -> cleanup.
Tests: r2-delete-video-storage (adds generic-S3 forcePathStyle routing),
s3-upload-cors-guidance (new). Full suite green.
Co-Authored-By: Claude Opus 4.8 <[email protected]>