handleFileUpload has no reader.onerror / img.onerror handlers and no client-side 20MB size check — uploads fail silently, large files hang the browser #149

Closed
opened 2026-10-03 05:45:14 +00:00 by Mark · 1 comment
Owner

Finding

handleFileUpload in src/components/workspace/useWorkspace.ts:638-680 does not set reader.onerror or img.onerror. The FileReader and Image are loaded with only onload handlers:

const reader = new FileReader();
reader.onload = (e) => { /* build the Image */ };
reader.readAsDataURL(file);

If the FileReader fails (corrupt file, browser security policy denial, out-of-memory for a multi-hundred-MB image, etc.), nothing happens. The dispatch never fires, the state never changes, the user sees the upload area as if they hadn't dropped a file. No error toast. No UI feedback.

Same for img.onload and img.onerror: if the image data URL is decoded but the image is invalid (truncated PNG, corrupt JPEG), the user gets no feedback.

The 20MB size claim on the dropzone (CanvasArea.tsx:328: "PNG, JPG, SVG, BMP up to 20MB") is also unenforced client-side. A user who drops a 100MB file watches the FileReader chew CPU and then either succeed (silently scaling it to 300px) or fail (with no error toast).

Where

  • src/components/workspace/useWorkspace.ts:638-680 — handleFileUpload
  • Size claim: src/components/workspace/CanvasArea.tsx:328

Suggested fix shape

Three additions:

  1. Enforce the 20MB size claim client-side before the FileReader. Drop or reject files larger than 20MB with a clear toast:

    if (file.size > 20 * 1024 * 1024) {
      addToast('error', `${file.name} is too large (max 20MB)`);
      return;
    }
    
  2. Set reader.onerror to dispatch an error toast on FileReader failure. The onerror event has an error property that names the reason; show that in the toast for diagnostic value.

  3. Set img.onerror to dispatch an error toast when the decoded image data is invalid. The Image's onerror fires for truncated/corrupt files; the user needs feedback.

  4. Enforce a more restrictive MIME type list matching the accept attribute. Today, handleFileUpload checks file.type.startsWith('image/') which is loose (accepts HEIC, WebP, GIF, AVIF). The Workspace's accept attribute is image/png,image/jpeg,image/svg+xml,image/bmp; the type check should match.

Severity

Medium. The user has no feedback when an upload fails silently. The 20MB claim is unenforced, so a 200MB photo hangs the browser.

Discovered by

Gap analysis (2026-10-02) of upload affordances.

  • #131 (closed) — parseDesignFile was made honest. The upstream upload flow that feeds it the file still has silent failures.
## Finding `handleFileUpload` in `src/components/workspace/useWorkspace.ts:638-680` does not set `reader.onerror` or `img.onerror`. The FileReader and Image are loaded with only `onload` handlers: ```ts const reader = new FileReader(); reader.onload = (e) => { /* build the Image */ }; reader.readAsDataURL(file); ``` If the FileReader fails (corrupt file, browser security policy denial, out-of-memory for a multi-hundred-MB image, etc.), nothing happens. The dispatch never fires, the state never changes, the user sees the upload area as if they hadn't dropped a file. No error toast. No UI feedback. Same for `img.onload` and `img.onerror`: if the image data URL is decoded but the image is invalid (truncated PNG, corrupt JPEG), the user gets no feedback. The 20MB size claim on the dropzone (`CanvasArea.tsx:328`: "PNG, JPG, SVG, BMP up to 20MB") is also unenforced client-side. A user who drops a 100MB file watches the FileReader chew CPU and then either succeed (silently scaling it to 300px) or fail (with no error toast). ## Where - `src/components/workspace/useWorkspace.ts:638-680` — `handleFileUpload` - Size claim: `src/components/workspace/CanvasArea.tsx:328` ## Suggested fix shape Three additions: 1. **Enforce the 20MB size claim client-side** before the FileReader. Drop or reject files larger than 20MB with a clear toast: ```ts if (file.size > 20 * 1024 * 1024) { addToast('error', `${file.name} is too large (max 20MB)`); return; } ``` 2. **Set `reader.onerror`** to dispatch an error toast on FileReader failure. The `onerror` event has an `error` property that names the reason; show that in the toast for diagnostic value. 3. **Set `img.onerror`** to dispatch an error toast when the decoded image data is invalid. The Image's `onerror` fires for truncated/corrupt files; the user needs feedback. 4. **Enforce a more restrictive MIME type list** matching the `accept` attribute. Today, `handleFileUpload` checks `file.type.startsWith('image/')` which is loose (accepts HEIC, WebP, GIF, AVIF). The Workspace's accept attribute is `image/png,image/jpeg,image/svg+xml,image/bmp`; the type check should match. ## Severity Medium. The user has no feedback when an upload fails silently. The 20MB claim is unenforced, so a 200MB photo hangs the browser. ## Discovered by Gap analysis (2026-10-02) of upload affordances. ## Related - #131 (closed) — `parseDesignFile` was made honest. The upstream upload flow that feeds it the file still has silent failures.
Author
Owner

Fixed in commit 551f786 on origin/main.

  • reader.onerror surfaces a toast with the FileReader-reported reason
  • img.onerror surfaces a toast when the decoded image is corrupt/truncated
  • 20MB size guard short-circuits before FileReader so large drops fail fast with feedback
  • MIME type list tightened from image/* to PNG/JPG/SVG/BMP to match the accept attribute

5 new tests cover the error paths (rejected size, rejected MIME, accepted types, FileReader onerror, img.onerror). Lint clean, typecheck clean, 604/604 tests pass.

Fixed in commit 551f786 on origin/main. - reader.onerror surfaces a toast with the FileReader-reported reason - img.onerror surfaces a toast when the decoded image is corrupt/truncated - 20MB size guard short-circuits before FileReader so large drops fail fast with feedback - MIME type list tightened from image/* to PNG/JPG/SVG/BMP to match the accept attribute 5 new tests cover the error paths (rejected size, rejected MIME, accepted types, FileReader onerror, img.onerror). Lint clean, typecheck clean, 604/604 tests pass.
Mark closed this issue 2026-10-03 06:37:49 +00:00
Sign in to join this conversation.
No description provided.