{"record":{"id":"eb93d8e944643f93","repo":"odysseus-dev/odysseus","slug":"http-resp-status-detail-detail","errorCode":null,"errorMessage":"HTTP ${resp.status}${detail ? `: ${detail}` : ''}","messagePattern":"HTTP (.+?)(.+?)` : ''\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/galleryEditor.js","lineNumber":3306,"sourceCode":"      const flat = flatten();\n      const ext = (state.originalExt || 'png').toLowerCase();\n      const isJpeg = ext === 'jpg' || ext === 'jpeg';\n      const mime = isJpeg ? 'image/jpeg' : 'image/png';\n      const quality = isJpeg ? 0.92 : undefined;\n      blob = await new Promise((resolve, reject) => {\n        flat.toBlob(b => b ? resolve(b) : reject(new Error('Canvas encode failed')), mime, quality);\n      });\n      const fd = new FormData();\n      fd.append('image', blob, `edited.${isJpeg ? 'jpg' : 'png'}`);\n      const resp = await fetch(`${API_BASE}/api/gallery/${state.imageId}/replace`, {\n        method: 'POST',\n        credentials: 'same-origin',\n        body: fd,\n      });\n      if (!resp.ok) {\n        let detail = '';\n        try { const j = await resp.json(); detail = j.detail || j.error || ''; } catch {}\n        throw new Error(`HTTP ${resp.status}${detail ? `: ${detail}` : ''}`);\n      }\n      const totalMs = Math.round(performance.now() - t0);\n      if (uiModule) uiModule.showToast(`Saved over original (${(blob.size / 1024 / 1024).toFixed(1)}MB · ${(totalMs / 1000).toFixed(1)}s)`, 4000);\n      window.dispatchEvent(new CustomEvent('gallery-refresh'));\n      savedOk = true;\n    } catch (e) {\n      console.error('[save] error:', e);\n      const sizeMB = blob ? ` (${(blob.size / 1024 / 1024).toFixed(1)}MB)` : '';\n      let msg = e?.message || 'unknown';\n      if (e?.name === 'TypeError' || /fetch|network|load failed/i.test(msg)) {\n        msg = `network dropped${sizeMB} — try \"Save as copy\" or check connection`;\n      } else {\n        msg += sizeMB;\n      }\n      if (uiModule) uiModule.showToast('Failed to save: ' + msg, 6000);\n    } finally {\n      endBusy();\n      if (savedOk) _flashSaveButtonOk();","sourceCodeStart":3288,"sourceCodeEnd":3324,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/galleryEditor.js#L3288-L3324","documentation":"Thrown by the 'Save over original' flow in galleryEditor.js when POST /api/gallery/{imageId}/replace fails. The client tries to parse the error body for detail/error and appends it to the status code. The surrounding catch specifically recognizes network TypeErrors and suggests 'Save as copy', so this message is for genuine HTTP failures (4xx/5xx).","triggerScenarios":"Replacing an original photo when the file on disk is read-only or missing (404/500); payload exceeds a reverse proxy's client_max_body_size (413) for multi-MB PNGs; session cookie expired (401); server-side storage full.","commonSituations":"Editing large photos saved as high-quality PNG (tens of MB) behind nginx/caddy defaults; photo library moved or on removable media that is unmounted; permissions changed after restore.","solutions":["Use 'Save as copy' (the sibling flow) to confirm the upload path itself works.","If 413, raise the proxy's body-size limit or save as JPEG at slightly lower quality to shrink the blob.","Verify the original file still exists at the path the server expects and is writable.","Check server logs for the detail string; re-authenticate if 401."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"const blob = await canvasToBlob(flat, mime, quality);\nif (blob.size > PROXY_LIMIT) { quality *= 0.8; /* re-encode */ }","typeGuard":null,"tryCatchPattern":"try { ... } catch (e) { if (/fetch|network/i.test(e.message)) msg = 'network dropped — try Save as copy'; if (is413) retry with lower quality; offer 'Save as copy' as fallback path; }","preventionTips":["Cap export size below the proxy's body limit","Keep the draft saved so a failed replace does not lose edits","Offer the copy path automatically when replace fails"],"tags":["http","upload","gallery","proxy-limits","editor"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}