odysseus-dev/odysseus · error · Error
t || r.statusText
Error message
t || r.statusText
What it means
Error thrown when the direct PDF export GET /api/document/{id}/export-pdf returns non-OK. The response body text is used verbatim as the message (fallback: statusText). Because the endpoint returns a PDF blob on success, an error body is typically plain text or JSON from the backend (e.g. 'document has no PDF backing').
Source
Thrown at static/js/document.js:624
const ae = document.activeElement;
const el = (ae && (ae.tagName === 'INPUT' || ae.tagName === 'TEXTAREA')) ? ae : ta;
if (!el) return;
try {
el.setAttribute('readonly', 'readonly');
el.blur();
setTimeout(() => { try { el.removeAttribute('readonly'); } catch (_) {} }, 120);
} catch (_) { try { el.blur(); } catch (_) {} }
}
async function _downloadFilledPdf() {
if (!activeDocId) return;
_dismissDocKb(); // export shouldn't leave the keyboard up
await _saveActiveDocBeforeExport();
try {
const r = await fetch(`${API_BASE}/api/document/${activeDocId}/export-pdf`);
if (!r.ok) {
const t = await r.text();
throw new Error(t || r.statusText);
}
const blob = await r.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
const cd = r.headers.get('Content-Disposition') || '';
const m = cd.match(/filename\*?=(?:UTF-8'')?"?([^"';]+)/i);
const _slug = (s) => (s || 'form').replace(/\.pdf$/i, '').replace(/\s+/g, '_').replace(/[^A-Za-z0-9._-]/g, '').replace(/_+/g, '_').replace(/^_|_$/g, '') || 'form';
a.download = (m && decodeURIComponent(m[1])) || (_slug(docs.get(activeDocId)?.title) + '_annotated.pdf');
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(() => URL.revokeObjectURL(url), 1000);
} catch (e) {
if (uiModule) uiModule.showError('Export failed: ' + e.message);
else alert('Export failed: ' + e.message);
}
}View on GitHub (pinned to f9235ebbf1)
Solutions
- Read the thrown text — it is the endpoint's literal error body
- Confirm the document actually has a PDF backing (it must have been imported from a PDF)
- If the PDF is fillable-form-based, verify it opens in a viewer and is not password-protected
- Check backend logs for the PDF-generation exception
Example fix
// before
const t = await r.text();
throw new Error(t || r.statusText);
// after
const t = await r.text().catch(() => '');
let msg = t;
try { msg = JSON.parse(t).detail || t; } catch {}
throw new Error(msg || r.statusText || `HTTP ${r.status}`); Defensive patterns
Strategy: try-catch
Validate before calling
const doc = docs.get(activeDocId);
if (!doc || !doc.hasPdf) { uiModule?.showToast?.('This document has no PDF to export'); return; } Try / catch
try {
const r = await fetch(`${API_BASE}/api/document/${activeDocId}/export-pdf`);
if (!r.ok) {
const t = await r.text().catch(() => '');
let msg = t;
try { msg = JSON.parse(t).detail || t; } catch {}
throw new Error(msg || r.statusText || `HTTP ${r.status}`);
}
const blob = await r.blob();
// ... trigger download
} catch (e) {
uiModule?.showError?.('PDF export failed: ' + e.message);
} Prevention
- Parse JSON detail out of the error body — raw text bodies make ugly toasts
- Verify the document is PDF-backed before showing the export action
- Always pass a text() fallback so empty error bodies still yield a status
When it happens
Trigger: GET export-pdf returns 404 (unknown docId), 409/400 (document is markdown-only with no PDF to fill), 500 (pdf processing crash — malformed PDF, missing fillable fields), or a proxy HTML error page that then shows as raw HTML in the toast.
Common situations: Doc was created from text/markdown so no source PDF exists; the uploaded PDF is encrypted/corrupt; backend pdf library dependency missing; docId stale after a reload/deletion.
Related errors
- err || res.statusText
- res.statusText || String(res.status)
- await _pdfResponseErrorMessage(res)
- t || res.statusText
- t || r2.statusText
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/6ee86a87749a093a.
Report an issue: GitHub.