Initial deliverable of the architecture phase: 16 ADRs (stack, CRDT collaboration, plugin sandbox, import/export, backups, CI/CD), data model, permission model, real-time collaboration and plugin concepts, deployment/operations/security documentation, and the milestone roadmap that the implementation issues are derived from. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2.7 KiB
2.7 KiB
ADR 0009: Pandoc + Gotenberg sidecars for import/export
- Status: accepted
- Date: 2026-07-04
Context
Requirements: Markdown as the primary import/export format (including
copy/paste), import of Word (.docx) and OpenOffice/LibreOffice (.odt)
documents, export to Word, OpenOffice, and PDF. Kickoff decision: import is
best-effort structural — headings, paragraphs, lists, tables, images,
links, bold/italic are preserved reliably; layout fidelity (columns, text
boxes, exact spacing) is explicitly out of scope. Document conversion is
CPU-heavy and full of parser edge cases; it must not run inside the Node API
process.
Decision
- Markdown conversion (both directions, including clipboard paste) is
implemented in-process in TypeScript via
prosemirror-markdownwith extensions for our custom nodes (wikilinks, section styles). This is the primary, lossless-as-possible path. pandoc-server(official pandoc image, HTTP server mode, internal network only) handles:- import:
.docx/.odt→ Markdown (+ extracted media, which the API stores as uploads and rewrites to image nodes), - export: Markdown →
.docx/.odt(structure-true best effort).
- import:
- Gotenberg (internal only) handles PDF export: the API renders the page to standalone HTML (same renderer as the public read-only HTML endpoint, with the pond's fonts inlined) and sends it to Gotenberg's Chromium route. PDF output is for reading/sharing, not print production.
- The API's
import-exportmodule orchestrates conversions asynchronously (job table + polling endpoint) with size limits and timeouts; sidecar failures degrade gracefully into a user-visible error, never a crash. - Plugin blocks render their manifest-declared static
fallbackin all exports (ADR 0008).
Consequences
- Two extra containers in the Compose stack; both are stateless, official
images, and internal-only (no ingress). Self-hosting stays
docker compose up. - Import fidelity is testable: a fixture corpus of
.docx/.odtfiles with expected Markdown output lives in the repo (stories reference it). - Copy/paste from Word into the editor goes through the editor's HTML paste handling (structural, same fidelity philosophy), not through pandoc.
Alternatives considered
- mammoth.js in-process for docx: good HTML output, but no
.odtsupport and no export direction; pandoc covers all four directions with one tool. - LibreOffice headless for everything: heavyweight, slower startup, layout-oriented rather than structure-oriented output.
- Browser-print PDF (client-side): inconsistent results across clients; server-side Chromium (Gotenberg) gives reproducible PDFs and enables "export without opening the page".