Fix image uploads using the wrong format (#2380)

* fix(app): preserve image attachment MIME types

Image intake discarded clipboard and desktop path metadata, then guessed JPEG when a later file object had no type. Resolve MIME once at the platform boundary and require it downstream.

* fix(app): honor native picker MIME metadata
This commit is contained in:
Mohamed Boudra
2026-07-24 21:11:41 +08:00
committed by GitHub
parent 967edab497
commit 21597bdc1b
13 changed files with 198 additions and 54 deletions
@@ -92,7 +92,7 @@ describe("collectImageFilesFromClipboardData", () => {
],
});
expect(files).toEqual([imagePng]);
expect(files).toEqual([{ file: imagePng, mimeType: "image/png" }]);
});
it("ignores SVG clipboard files", () => {
@@ -127,7 +127,10 @@ describe("filesToImageAttachments", () => {
type: "",
});
const attachments = await filesToImageAttachments([pngFile, typeLessFile]);
const attachments = await filesToImageAttachments([
{ file: pngFile, mimeType: "image/png" },
{ file: typeLessFile, mimeType: "image/png" },
]);
expect(attachments).toEqual([
{
@@ -141,7 +144,7 @@ describe("filesToImageAttachments", () => {
},
{
id: "att-2",
mimeType: "image/jpeg",
mimeType: "image/png",
storageType: "web-indexeddb",
storageKey: "att-2",
fileName: "second",
@@ -156,7 +159,7 @@ describe("filesToImageAttachments", () => {
type: "image/png",
});
const [attachment] = await filesToImageAttachments([large]);
const [attachment] = await filesToImageAttachments([{ file: large, mimeType: "image/png" }]);
expect(attachment?.storageType).toBe("web-indexeddb");
expect(attachment?.byteSize).toBe(4 * 1024 * 1024);
@@ -1,6 +1,6 @@
import type { AttachmentMetadata } from "@/attachments/types";
import { persistAttachmentFromBlob } from "@/attachments/service";
import { isRasterImageMimeType } from "@/attachments/file-types";
import { resolveRasterImageMimeType } from "@/attachments/file-types";
export interface ClipboardItemLike {
kind?: string;
@@ -14,40 +14,46 @@ export interface ClipboardDataLike {
export type ImageAttachmentFromFile = AttachmentMetadata;
export interface ClipboardImageFile {
file: File;
mimeType: string;
}
export function collectImageFilesFromClipboardData(
clipboardData?: ClipboardDataLike | null,
): File[] {
): ClipboardImageFile[] {
if (!clipboardData?.items) {
return [];
}
const files: File[] = [];
const files: ClipboardImageFile[] = [];
for (const item of Array.from(clipboardData.items)) {
if (item?.kind !== "file") {
continue;
}
if (!isRasterImageMimeType(item.type)) {
const mimeType = resolveRasterImageMimeType({ mimeType: item.type });
if (!mimeType) {
continue;
}
const file = item.getAsFile?.();
if (!file) {
continue;
}
files.push(file);
files.push({ file, mimeType });
}
return files;
}
export async function filesToImageAttachments(
files: readonly File[],
files: readonly ClipboardImageFile[],
): Promise<ImageAttachmentFromFile[]> {
const attachments = await Promise.all(
files.map(async (file) => {
files.map(async ({ file, mimeType }) => {
try {
return await persistAttachmentFromBlob({
blob: file,
mimeType: file.type || "image/jpeg",
mimeType,
fileName: file.name,
});
} catch (error) {