ADR-0072: Diagrams — Self-Hosted draw.io Replaces Excalidraw¶
- Status: accepted (Nicolas 2026-07-15: "let's switch then", after the GO-with-conditions spike; layout directive: maximize the canvas — the editor chrome must not sacrifice space)
- Date: 2026-07-15
- Deciders: Nicolas Burri, spike + design by Claude.
- Supersedes: ADR-0065 (Excalidraw). The ADR-0065 integration shape (attachment pair,
tree
diagramnode,attachment:→<img>display, full-frame editor, slash entry) carries over; only the editor engine and the artifact format change. - Relates to: ADR-0069 §5 (the conversion QA that triggered reconsideration), ADR-0068 (the imported corpus), ADR-0062 (SBOM/notices).
Context¶
The company's 94 real diagrams are draw.io; conversion to Excalidraw was shown unusable on the real corpus (labels/routing off, dropped stencil cells — Nicolas's visual QA). Zero production Excalidraw content exists (valve-for-all), so the switching window is uniquely cheap. A spike (pinned v30.3.12) re-proved the ADR-0065 C-1 property for a self-hosted draw.io embed and showed native-lossless rendering of the stencil-heavy corpus.
Decision¶
Replace the Excalidraw React island with a SELF-HOSTED draw.io embed (sandboxed iframe, JSON postMessage protocol), under these conditions from the spike:
- D-1 (C-1 pin): iframe URL
embed=1&proto=json&offline=1&stealth=1&gapi=0&db=0&od=0&gh=0& gl=0&tr=0&picker=0&plugins=0&math=0; "zero external requests over load→edit→export→save" pinned in the fullstack smoke (prod-bundle-gate discipline). - D-2 (CSP): the iframe response carries its own CSP —
script-src 'self'(no unsafe-eval, proven sufficient),style-src 'self' 'unsafe-inline'(proven required), img/font data:/blob:,object-src 'none'; served from a dedicated static path; main app addsframe-src 'self'. - D-3 (minimal vendor subset): ~21–25 MB runtime set (app.min.js, stencils.min.js, extensions.min.js, shapes, css, resources, bootstrap files); EXCLUDE integrate.min.js, diagramly sources, viewer builds, templates, math4, WEB-INF, .map, libavoid-js (LGPL, unused). Fetched at build from the pinned GitHub release, SHA-verified, never committed to git.
- D-4 (SBOM/notices): draw.io Apache-2.0 + the restrictive stencil/shape/img asset license (Atlassian field-of-use restriction — inapplicable to LQMS; end-user diagram output exempt) + bundled MIT/EPL libs; pako exclusion reused.
- D-5 (upgrade discipline): version pinned by SHA; any draw.io upgrade re-runs the D-1 smoke.
Artifact format: single xmlsvg — the display SVG with the editable source XML embedded —
replaces the scene+SVG pair (one attachment per diagram; the display path is unchanged).
Layout (Nicolas): the editor takes over the full frame with the CANVAS maximized — one slim top bar (back, title, save); caption/alt move out of the permanent footer into a compact affordance (popover/expander) so no permanent chrome competes with the drawing surface.
Importer flip (ADR-0068/0069 amendment): imported draw.io diagrams become EDITABLE diagram nodes (their source XML as the node's artifact) instead of static preview images; the .drawio FILE attachment valve is no longer needed for them.
Consequences¶
- The React island, esm.sh fetch-guard, react-dom interop shims, and the @excalidraw/excalidraw + mermaid-to-excalidraw npm dependencies (and their CVE overrides) are removed; Mermaid diagrams keep working via the existing mermaid code-block path.
- +~21–25 MB self-hosted static assets (lazy; not in the app bundle), one scoped style-src 'unsafe-inline' on an isolated iframe.
- Existing demo Excalidraw diagrams (if any) are not migrated — they remain displayable via their stored SVG part; editing recreates them in draw.io. No production content is affected.
- drawio2excalidraw.py (the opt-in converter CLI) is obsolete — retained in-tree as historical tooling, marked deprecated.