{"record":{"id":"4264590466b9fd08","repo":"Wei-Shaw/sub2api","slug":"profile-avatar-giftoolarge","errorCode":null,"errorMessage":"profile.avatar.gifTooLarge","messagePattern":"profile\\.avatar\\.gifTooLarge","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"frontend/src/components/user/profile/ProfileAvatarCard.vue","lineNumber":197,"sourceCode":"    for (const quality of avatarQualitySteps) {\n      const blob = await canvasToBlob(canvas, 'image/webp', quality)\n      if (blob.size <= targetAvatarUploadBytes) {\n        const fileName = file.name.replace(/\\.[^.]+$/, '') || 'avatar'\n        return new File([blob], `${fileName}.webp`, { type: 'image/webp' })\n      }\n    }\n  }\n\n  throw new Error(t('profile.avatar.compressTooLarge'))\n}\n\nasync function prepareAvatarUpload(file: File): Promise<File> {\n  if (!file.type.startsWith('image/')) {\n    throw new Error(t('profile.avatar.invalidType'))\n  }\n  if (file.type === 'image/gif') {\n    if (file.size > targetAvatarUploadBytes) {\n      throw new Error(t('profile.avatar.gifTooLarge'))\n    }\n    return file\n  }\n  if (file.size <= targetAvatarUploadBytes) {\n    return file\n  }\n  return compressAvatarFile(file)\n}\n\nasync function handleAvatarFileChange(event: Event) {\n  const input = event.target as HTMLInputElement | null\n  const file = input?.files?.[0]\n  if (input) {\n    input.value = ''\n  }\n  if (!file) {\n    return\n  }","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/Wei-Shaw/sub2api/blob/073e92d17178a1ccdb0a27017f572f10c9c7ab62/frontend/src/components/user/profile/ProfileAvatarCard.vue#L179-L215","documentation":"In frontend/src/components/user/profile/ProfileAvatarCard.vue:197, prepareAvatarUpload() throws the localized 'profile.avatar.gifTooLarge' for image/gif files whose size exceeds targetAvatarUploadBytes. GIFs are passed through untouched (no canvas recompression, since re-encoding would kill animation), so oversized GIFs are rejected outright.","triggerScenarios":"Selecting an animated GIF larger than targetAvatarUploadBytes (the upload budget). The file passes the image/* check and enters the gif branch, where only the size gate applies.","commonSituations":"Users uploading full-resolution animated stickers exported from messaging apps; a low server avatar size limit; users expecting the app to auto-compress GIFs like it does JPEG/PNG.","solutions":["Show the actual size limit in the UI next to the file picker so users know the GIF budget before selecting.","Optionally offer server-side or wasm-based GIF downscaling (gifsicle) instead of a hard rejection.","Increase targetAvatarUploadBytes for gif specifically if the backend accepts it.","Client-side pre-check file.size before the picker even opens (some browsers support showOpenFilePicker with size hints via UI copy)."],"exampleFix":"// before\nif (file.type === 'image/gif') {\n  if (file.size > targetAvatarUploadBytes) {\n    throw new Error(t('profile.avatar.gifTooLarge'))\n  }\n  return file\n}\n\n// after\nif (file.type === 'image/gif') {\n  if (file.size > targetAvatarUploadBytes) {\n    throw new Error(t('profile.avatar.gifTooLarge', { max: formatBytes(targetAvatarUploadBytes) }))\n  }\n  return file\n}","handlingStrategy":"validation","validationCode":"if (file.type === 'image/gif' && file.size > targetAvatarUploadBytes) {\n  showGifSizeError(formatBytes(targetAvatarUploadBytes)); // before any upload attempt\n  return;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Display the GIF size limit next to the picker","Offer animated-avatar alternatives with server-side downscaling if GIFs are common","Pre-check file.size immediately on selection for all types"],"tags":["gif","upload-limits","validation","avatar","frontend"],"backgroundTag":null,"analyzedSha":"073e92d17178a1ccdb0a27017f572f10c9c7ab62","analyzedAt":"2026-08-15T14:33:00.750Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}