v0.4.0
Released: 2026-07-12
Changes since v0.3.7 (a2e62f5..merge of #112).
Features
- Drag-and-drop media uploads (PR #112 by @gabrielforster, hardened on integration): Both the image picker and the video dialog gain a Drop tab (now the default) — drag a file onto the zone, or click/press it to open the native file picker, to insert an image or video without leaving the editor. Honors the existing S3 upload option. Ideal when the file is on your device but FrankMD runs on a remote server (e.g. over Tailscale), where the Folder tab can't reach it.
- Accepted extensions are configurable per install via
image_upload_extensions/video_upload_extensions(comma-separated), withIMAGE_UPLOAD_EXTENSIONS/VIDEO_UPLOAD_EXTENSIONSenv defaults. - Dropzones are keyboard accessible (focus on open, Enter/Space to pick).
- Accepted extensions are configurable per install via
Security
- Server-side upload extension enforcement. Uploads are validated against the configured allow-list on the server, not just the browser. Both the image and video paths now share a single
UploadStoragemodule (extension allow-list, size cap, filename sanitizing, safe temp handling), closing a stored-XSS vector where a non-image file uploaded via the image endpoint could be stored and served inline. Uploads also use random temp names and streamed copies, and upload errors no longer leak internal exception details to the client. - net-imap / websocket-driver and other transitive advisories cleared as part of the release audit (websocket-driver 0.8.0 -> 0.8.2).
Fixes
- Video upload status label used a nonexistent i18n key and rendered the raw key to every user; fixed, and
drop_rejected/uploading/upload_failedare now present in all 7 locales (a parity test guards against future drift). - Guard against concurrent uploads on a rapid double drop/pick.
- Clear a stale image selection when a second file is dropped, so Insert can't upload a hidden, no-longer-shown file.
- Drag-leave highlight no longer flickers when the pointer moves over the dropzone's icon/text.
Test Coverage
- 530 Ruby tests, 1,415 JavaScript tests (1,945 total).