{"record":{"id":"bd58c849e5f15590","repo":"odysseus-dev/odysseus","slug":"t-r2-statustext","errorCode":null,"errorMessage":"t || r2.statusText","messagePattern":"t \\|\\| r2\\.statusText","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/document.js","lineNumber":1838,"sourceCode":"          x: Math.max(0, Math.min(100, parseFloat(a.x) || 0)),\n          y: Math.max(0, Math.min(100, parseFloat(a.y) || 0)),\n          w: Math.max(0.5, Math.min(100, parseFloat(a.w) || 22)),\n          h: Math.max(0.3, Math.min(100, parseFloat(a.h) || 3.5)),\n          value: String(a.value || ''),\n        });\n      }\n      const newMd = _writeAnnotations(doc.content || '', combined);\n      doc.content = newMd;\n      const ta = document.getElementById('doc-editor-textarea');\n      if (ta) ta.value = newMd;\n      const r2 = await fetch(`${API_BASE}/api/document/${docId}`, {\n        method: 'PUT',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ content: newMd }),\n      });\n      if (!r2.ok) {\n        const t = await r2.text().catch(() => r2.statusText);\n        throw new Error(t || r2.statusText);\n      }\n      _setPdfSaveStatus('saved');\n      if (uiModule && uiModule.showToast) uiModule.showToast(`AI added ${proposed.length} annotations`);\n      _renderPdfPane();\n    } catch (e) {\n      console.error('AI fill failed:', e);\n      _setPdfSaveStatus('error', `AI fill failed: ${e.message || e}`);\n    } finally {\n      if (btn) { btn.disabled = false; btn.textContent = 'AI fill'; }\n    }\n  }\n\n  function _schedulePdfPaneSave() {\n    _setPdfSaveStatus('dirty');\n    if (_pdfPaneSaveTimer) clearTimeout(_pdfPaneSaveTimer);\n    _pdfPaneSaveTimer = setTimeout(() => _savePdfPaneToMarkdown(), 600);\n  }\n","sourceCodeStart":1820,"sourceCodeEnd":1856,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/document.js#L1820-L1856","documentation":"Error thrown in the second phase of AI fill: after annotations were proposed, persisting the merged markdown via PUT /api/document/{docId} returned non-OK. Body text or statusText becomes the message; the UI shows 'AI fill failed: ...' even though the AI part succeeded — only the save failed.","triggerScenarios":"PUT /api/document/{id} returns 404 (doc deleted mid-flow), 409 (concurrent modification), 413 (annotation-inflated content exceeds a body size limit), 500 (storage failure).","commonSituations":"Another tab saved the same doc between preview and persist; large signature/annotation payloads crossing proxy limits; disk full; docId invalidated by a session switch during the async flow.","solutions":["Re-run AI fill after reloading the document to clear stale ids/conflicts","Raise proxy client_max_body_size if content grew past the limit","Avoid editing the same document from two tabs concurrently","Check server disk/permissions for 500s"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if (!docs.has(docId)) { _setPdfSaveStatus('idle'); return; } // doc vanished mid-flow","typeGuard":null,"tryCatchPattern":"try {\n  const r2 = await fetch(`${API_BASE}/api/document/${docId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: newMd }) });\n  if (!r2.ok && (r2.status === 409 || r2.status === 503)) {\n    await new Promise(r => setTimeout(r, 800));\n    const r3 = await fetch(`${API_BASE}/api/document/${docId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: newMd }) });\n    if (!r3.ok) throw new Error((await r3.text().catch(() => '')) || `HTTP ${r3.status}`);\n  } else if (!r2.ok) {\n    throw new Error((await r2.text().catch(() => '')) || r2.statusText || `HTTP ${r2.status}`);\n  }\n  _setPdfSaveStatus('saved');\n} catch (e) {\n  _setPdfSaveStatus('error', `AI fill failed: ${e.message || e}`);\n}","preventionTips":["Retry once on transient statuses (409/503) since the expensive AI step already succeeded","Keep the merged markdown in doc.content so a failed save doesn't lose the AI output","Parse the error body for detail instead of trusting statusText"],"tags":["fetch","http-error","save-conflict","ai-fill","document"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}