{"record":{"id":"3979347dfde53882","repo":"linshenkx/prompt-optimizer","slug":"image-storage-quota-exceeded-projected-size-nex-397934","errorCode":null,"errorMessage":"Image storage quota exceeded: projected size ${nextTotalBytes} exceeds maxCacheSize ${config.maxCacheSize}","messagePattern":"Image storage quota exceeded: projected size (.+?) exceeds maxCacheSize (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"packages/ui/src/utils/image-asset-storage.ts","lineNumber":202,"sourceCode":"  const nextCount = stats.count + 1\n  const nextTotalBytes = stats.totalBytes + Math.floor(payload.b64.length * 0.75)\n\n  if (\n    typeof config.maxCount === 'number' &&\n    Number.isFinite(config.maxCount) &&\n    nextCount > config.maxCount\n  ) {\n    throw new Error(\n      `Image storage quota exceeded: projected count ${nextCount} exceeds maxCount ${config.maxCount}`,\n    )\n  }\n\n  if (\n    typeof config.maxCacheSize === 'number' &&\n    Number.isFinite(config.maxCacheSize) &&\n    nextTotalBytes > config.maxCacheSize\n  ) {\n    throw new Error(\n      `Image storage quota exceeded: projected size ${nextTotalBytes} exceeds maxCacheSize ${config.maxCacheSize}`,\n    )\n  }\n}\n\nexport const persistImagePayloadAsAssetId = async (\n  opts: PersistImagePayloadOptions,\n): Promise<string | null> => {\n  const { payload, storageService, sourceType = 'uploaded', metadata } = opts\n  if (!storageService || !payload?.b64) return null\n\n  const imageId = await computeStableImageId(payload.b64, payload.mimeType)\n  await assertStorageQuotaForPayload(storageService, imageId, payload)\n  const existing = await storageService.getMetadata(imageId)\n  if (!existing) {\n    await storageService.saveImage({\n      metadata: {\n        id: imageId,","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/linshenkx/prompt-optimizer/blob/3e677b1d9f7e0493c142c175560531e7ae786dce/packages/ui/src/utils/image-asset-storage.ts#L184-L220","documentation":"The sibling size check in assertStorageQuotaForPayload: it projects total cached bytes after adding the new payload and throws when the sum would exceed config.maxCacheSize. Large images (or many small ones) can blow a byte-level budget even when the count is fine.","triggerScenarios":"Persisting an image whose byte length pushes nextTotalBytes over maxCacheSize — e.g. a multi-MB photo into a 5MB cache; several large uploads accumulating to the cap; base64 size accounting overshooting due to a lowered config.","commonSituations":"High-resolution uploads without downscaling; config.maxCacheSize set in MB but code comparing bytes or vice versa; users pasting screenshots repeatedly; IndexedDB-backed caches nearing device limits.","solutions":["Downscale/compress images before persisting (canvas resize, quality reduction)","Evict oldest assets by size until the projected total fits under maxCacheSize","Raise maxCacheSize or store large assets out-of-cache (blob URLs / server-side)","Check projected size before insert and surface a quota UI to the user"],"exampleFix":"// before\nawait persistImagePayloadAsAssetId(payload) // 4MB image, 5MB cache mostly full -> throws\n\n// after\nconst compressed = await downscaleToMaxBytes(payload, 512 * 1024)\nwhile (projectedTotalBytes(compressed) > config.maxCacheSize) {\n  await deleteOldestImageAsset()\n}\nawait persistImagePayloadAsAssetId(compressed)","handlingStrategy":"validation","validationCode":"const projected = currentTotalBytes() + payload.b64.length\nif (projected > config.maxCacheSize) {\n  await evictOldestUntilFits(payload.b64.length)\n  // or: payload = await downscaleToMaxBytes(payload, budget)\n}","typeGuard":"null","tryCatchPattern":"try { await persistImagePayloadAsAssetId(payload) } catch (e) { if (e.message.startsWith('Image storage quota exceeded: projected size')) { payload = await downscaleToMaxBytes(payload, 512 * 1024); return persistImagePayloadAsAssetId(payload) } throw e }","preventionTips":["Compress/downscale images before persisting","Keep unit consistency: compare bytes with bytes in config","Evict by size proactively when nearing maxCacheSize"],"tags":["quota","storage","size-limit","image-compression"],"backgroundTag":"storage-quota-exceeded","analyzedSha":"3e677b1d9f7e0493c142c175560531e7ae786dce","analyzedAt":"2026-08-27T21:29:16.709Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}