{"record":{"id":"d7829e95a8855953","repo":"odysseus-dev/odysseus","slug":"export-failed-d7829e","errorCode":null,"errorMessage":"Export failed","messagePattern":"Export failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/settings.js","lineNumber":4116,"sourceCode":"      // name aren't useful as a contact.\n      if (!name && !email) { (name ? el('cm-add-email') : el('cm-add-name')).focus(); return; }\n      try {\n        await fetch('/api/contacts/add', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, email, phone, address }) });\n      } catch (_) {}\n      el('cm-add-name').value = '';\n      el('cm-add-email').value = '';\n      if (el('cm-add-phone')) el('cm-add-phone').value = '';\n      if (el('cm-add-address')) el('cm-add-address').value = '';\n      el('cm-add-row').style.display = 'none';\n      await _renderContactsManager();\n    });\n    const _downloadContacts = async (format) => {\n      const btn = el(format === 'csv' ? 'cm-export-csv-btn' : 'cm-export-vcf-btn');\n      const orig = btn ? btn.textContent : '';\n      if (btn) { btn.textContent = 'Exporting...'; btn.disabled = true; }\n      try {\n        const res = await fetch(`/api/contacts/export?format=${encodeURIComponent(format)}`, { credentials: 'same-origin' });\n        if (!res.ok) throw new Error('Export failed');\n        const blob = await res.blob();\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement('a');\n        a.href = url;\n        a.download = format === 'csv' ? 'odysseus-contacts.csv' : 'odysseus-contacts.vcf';\n        document.body.appendChild(a);\n        a.click();\n        a.remove();\n        setTimeout(() => URL.revokeObjectURL(url), 1000);\n      } catch (_) {\n        uiModule.showError ? uiModule.showError('Export failed') : alert('Export failed');\n      } finally {\n        if (btn) { btn.textContent = orig; btn.disabled = false; }\n      }\n    };\n    el('cm-export-vcf-btn')?.addEventListener('click', () => _downloadContacts('vcf'));\n    el('cm-export-csv-btn')?.addEventListener('click', () => _downloadContacts('csv'));\n","sourceCodeStart":4098,"sourceCodeEnd":4134,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/settings.js#L4098-L4134","documentation":"Error toast/alert when exporting the address book fails. _downloadContacts GETs /api/contacts/export?format=csv|vcf and throws 'Export failed' on any non-OK response; the same catch also covers blob/download/URL errors. The button is disabled and shows 'Exporting...' during the run and restored in finally.","triggerScenarios":"GET /api/contacts/export returns 4xx/5xx (invalid format param, auth expired, server-side serialization failure of a malformed contact) or the fetch/blob rejects (offline, server down, oversized body).","commonSituations":"Expired auth cookie on a long-lived settings tab; backend restarted; a malformed contact row breaking the server-side CSV/vCard generation; proxy timeouts on large address books.","solutions":["Check the export request's status in the network tab","Re-login and retry; confirm the backend is up","If 500, look for a specific contact the serializer chokes on (check server logs)","Surface res.status in the thrown error instead of the fixed string"],"exampleFix":"// before\n        if (!res.ok) throw new Error('Export failed');\n\n// after\n        if (!res.ok) throw new Error(`Export failed (HTTP ${res.status})`);","handlingStrategy":"try-catch","validationCode":"if (!['csv','vcf'].includes(format)) return;\nif (!navigator.onLine) { uiModule.showError('Offline'); return; }","typeGuard":null,"tryCatchPattern":"try { const res = await fetch(url, { credentials: 'same-origin' }); if (!res.ok) throw new Error(`Export failed (HTTP ${res.status})`); ... } catch (e) { (uiModule.showError || alert)(e.message); } finally { if (btn) { btn.textContent = orig; btn.disabled = false; } }","preventionTips":["Include res.status in the thrown error","Verify auth before export on long-lived tabs","Keep the finally-based button restore (already correct)","Handle blob creation failures separately from HTTP failures"],"tags":["contacts","export","fetch","settings"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}