Skip to content

feat(drawio): export Archify diagrams as editable draw.io files - #72

Open
kidoln wants to merge 7 commits into
tt-a1i:mainfrom
kidoln:feat/drawio-export
Open

feat(drawio): export Archify diagrams as editable draw.io files#72
kidoln wants to merge 7 commits into
tt-a1i:mainfrom
kidoln:feat/drawio-export

Conversation

@kidoln

@kidoln kidoln commented Aug 14, 2026

Copy link
Copy Markdown

Problem and value

Archify artifacts are self-contained HTML, but readers who want to keep editing a diagram in standard tooling had no way out. This adds draw.io export to every artifact, in two variants:

  • draw.io — editable: draw.io-native shapes per component kind, real source/target edge bindings (dragging a box keeps its connections), authored route waypoints preserved.
  • draw.io strict — 1:1: exact Archify corner radii, composited fill/stroke colors, stroke widths, dash patterns, and edge anchor points, always resolved to the light palette so the file reads correctly under any draw.io theme.

Sequence diagrams route through a dedicated UML-lifeline builder so the timeline survives export: participants become shape=umlLifeline cells spanning header→foot, activation bars become lifeline children, and each message's authored y survives as an exit/entry fraction. A headless path is included: archify export-drawio <type> <input.json> [output] [--strict] [--json].

Scope

  • What changed:
    • New converter archify/renderers/shared/svg-to-drawio.mjs (zero-dependency regex parsing of the self-describing SVG; the SVG is the sole geometry source, coordinates never recomputed) plus archify/scripts/export-drawio.mjs and the export-drawio CLI subcommand.
    • Viewer Export menu gains the two draw.io entries (archify/assets/template.html), mirroring the Node converter; artifacts now carry data-diagram-type so the viewer can dispatch sequence exports.
    • render-sequence.mjs stamps activation bars with data-graph-role="activation" + participant identity.
    • Examples: all 12 sample HTMLs re-rendered with the new shell; a .drawio + -strict.drawio pair for each of the 10 static samples; the five per-type demo pairs refreshed (byte-identical); gallery artifacts and showcase proof regenerated; archify.zip rebuilt.
    • Post-review hardening: consistent attribute escaping on edge labels, frame-id-based (not positional) boundary↔wraps matching, sequence foot fallback against empty participant sets, -strict.drawio default naming, documented attribute-order contract, and a parity test that extracts the viewer converter from the live template and asserts byte-identical output with the Node converter.
  • What deliberately did not change:
    • Existing SVG/PNG/JPEG/WebP/share-card export paths, rendering/layout output for existing diagrams, JSON IR schemas, and the viewer's canonical-state receipt semantics.
    • No mxfile/diagram wrapper (draw.io opens the bare mxGraphModel directly) and no new runtime dependencies.
    • Delta (before/after compare) artifacts are not draw.io-exportable: their states are viewer-driven, and static SVG extraction yields an empty shell — exporting one would ship a broken diagram.
  • No unrelated changes: the only files touched outside the exporter surface are regenerated artifacts (examples, gallery, showcase proof, zip).

Stability impact

  • Compatibility and migration risk: additive only. No schema or typed-JSON contract changes; artifacts without the new menu entries remain fully functional. archify check passes on every re-rendered sample.
  • Renderer, validator, package, or generated-artifact risk: renderers gain one attribute (activation identity) and the HTML shell gains data-diagram-type; both are consumed only by the exporter. The packaged skill (archify.zip) is rebuilt so installed skills match the source tree; release identity still checks 2.14.0.
  • Failure behavior and rollback path: unresolvable edge endpoints are skipped (never dangling cell references); a missing SVG raises with a clear message; viewer failures surface through the existing toast/alert + data-last-export-* receipt attributes; the CLI exits non-zero. Rollback is reverting the branch — no data or contract migrations.

Tests run

  • cd archify && npm test666/666 pass (validators, release identity, golden, full suite; includes 39 drawio tests: parsing units, builder units, 5-type CLI integrations, strict/sequence regressions, viewer↔Node parity).
  • node --test archify/test/export-drawio.test.mjs → 39/39 pass.
  • ./scripts/build-zip.sh archify.zip && node scripts/check-release-identity.mjsrelease identity ok: 2.14.0.
  • xmllint --noout on all 30 example .drawio files → all well-formed.
  • After the hardening fixes, all ten sample pairs re-exported and diffed against the committed files → byte-identical (no unintended output change).

Visual evidence

Visual review passed during development: each feature commit documents its draw.io-viewer comparison (strict always-light palette under light and dark themes; sequence timeline, activations, segments, labels, and return dashes restored via UML lifelines). The committed examples/*.drawio pairs can be opened directly for re-verification; screenshots can be attached on request.

Generated artifacts

  • examples/: 12 sample HTMLs re-rendered, 20 new per-sample .drawio pairs, receipt for the delta sample.
  • docs/: gallery artifacts (11), docs/gallery.html, docs/gallery/manifest.json, showcase proof (docs/assets/archify-live-proof.gif + .json) — all rebuilt from the new shell.
  • archify.zip: rebuilt in a dedicated commit; previously it did not ship the exporter at all.

Checklist

  • I used a minimal focused change and preserved existing typed JSON behavior unless the issue requires a contract change.
  • I ran the relevant targeted tests and npm test in archify/.
  • I added or updated a regression test for behavioral changes.
  • I checked generated artifacts and package freshness when their sources changed.
  • I removed secrets, private repository content, and customer data from fixtures and screenshots.

kidoln added 7 commits August 14, 2026 21:38
…orthogonal routing

- New CLI: archify export-drawio <type> <input.json> [output.drawio] [--json]
- New viewer menu item: Export -> draw.io (browser-side conversion)
- Geometry extracted from the rendered SVG (renderer-computed layout, 100% fidelity)
- Edges bind source/target to vertex cell ids: dragging a box keeps connections
- Interior route waypoints preserved via <Array as=points>
- Nodes parented into boundary containers with parent-relative coordinates
- Component/state types mapped to draw.io built-in shapes (cylinder, cloud, shield...)
- Edge style: orthogonalEdgeStyle with rounded corners
- Verified: 51 nodes across all 5 diagram types match SVG coordinates exactly;
  all edges topologically bound; 653 tests pass
…uals

- CLI: archify export-drawio <type> <input.json> [output] --strict
- Viewer: Export -> draw.io strict (exact shapes & colors) button
- Strict mode keeps the authored rounded rectangles (no built-in shape
  substitution) with the exact corner radius (absoluteArcSize=1;arcSize=rx)
- Colors are pre-composited from the artifact CSS: translucent fills are
  alpha-blended over the opaque mask/page background so draw.io renders the
  same pixel color; stroke widths and dash patterns come from the CSS classes
- Edge corner radius is measured from the SVG path Q-curves and emitted as
  arcSize = 2 x radius (draw.io convention), reproducing radius 8
  (architecture) and 10 (lifecycle) while workflow/dataflow stay sharp
- Connection points pinned via exitX/exitY/entryX/entryY at the exact
  authored border anchors; edge labels placed at their authored position
  along the polyline
- Dark page background (#020617) and JetBrains Mono font stack preserved
- Browser variant resolves colors from live computed styles, so it follows
  the reader's current theme/preset
- Verified: 51/51 nodes match SVG coordinates across all 5 types; colors,
  radii, and dash patterns cross-checked against the CSS; 657 tests pass
- CLI (export-drawio --strict): extractPalette resolves the
  [data-theme="light"] variable block first; the dark :root block is now
  only a fallback for custom templates without a light theme, so the
  .drawio file is a stable light-themed diagram regardless of the
  artifact's dark default or any viewer theme.
- Viewer button (draw.io strict): drawioBuildStrictXml wraps the build in
  withLightTheme — a synchronous data-theme swap restored in finally — and
  drawioParseColor additionally parses #rrggbb/#rgb. Custom-property
  reads return authored hex tokens, which previously failed the rgba()-only
  parser and silently fell back to the hardcoded dark mask/bg/text, so
  light fills were composited over a dark mask and the boxes stayed dark
  even after the prior light-forcing attempt.
- Palette fallbacks flipped to light values; call-site comments updated.
- Regenerated all rendered examples (both examples/ copies, web-app.html),
  docs gallery, README showcase GIF/receipt, and the ten sample .drawio
  files (strict now background="#f8fafc", pastel fills, dark ink).
- Verified: 658 tests pass; light-forcing and hex parsing extracted from
  the shipped artifact pass a Node harness; CLI strict output visually
  verified in draw.io light and dark themes.
Sequence geometry is a timeline, not a node graph: message endpoints hang on
lifelines far below the participant header boxes, so the generic border-
anchored edge model snapped every message to the header bottom and dropped
the only y-carrying points — all 12 messages collapsed into a knot under the
participant row, labels/activations/segment names were lost entirely, and
return messages lost their dash.

Both export paths now route sequence artifacts through a dedicated builder:

- Participants become shape=umlLifeline cells spanning header→timeline foot
  (perimeter=lifelinePerimeter, container for activation children), so each
  message's authored y survives as exitY/entryY fractions of the full
  timeline; labels sit one label-height above the line like the artifact.
- Activation bars render as kind-colored child rects of their lifeline; the
  sequence renderer now stamps them with data-graph-role="activation" +
  data-activation-participant identity.
- Segment bands keep their authored labels (matched from the separate
  segment-label groups by id) instead of the literal kind "segment".
- Message labels resolve from the outer focus group (data-edge-label) when
  the path carries none; return-message dashes read the path's inline
  stroke-dasharray.
- Viewer export: drawio and drawio-strict buttons dispatch sequence
  artifacts to the same builder; strict still force-resolves light.

Non-strict sequence uses the identical topology with draw.io's default
palette. Verified: 661 tests pass; exported examples/sequence-strict.drawio
visually compared against the artifact render in draw.io viewer — timeline,
activations, segments, labels, and return dashes all restored.
…ple drawio pairs

Six samples were last rendered at v2.14.0 or earlier and still embedded the
pre-drawio viewer shell, so their Export menus had no draw.io entries while
the five artifacts refreshed alongside the exporter work did.

- Re-rendered all 12 example HTMLs with the current pipeline.
  sequence-cache-miss.html and workflow-agent-tool-call.html were v2.9.0-era
  renders of cache-miss-request.sequence.json / agent-tool-call.workflow.json
  and now match their sibling artifacts byte-for-byte.
- checkout-platform-delta.html regenerated via compare; the receipt diff is
  only the artifact sha256/bytes of the new template (28/28 checks).
- Added a drawio + drawio-strict pair for each of the 10 static samples
  (delta excluded: its before/delta/after states are JS-driven and static
  SVG extraction yields an empty shell).
- Re-exported the five type-named drawio demo pairs; output is
  byte-identical to the committed versions, so they stay unchanged.

Verified: archify check passes on all 12 HTMLs, all 30 .drawio files parse
as XML with full node content, and the export-drawio suite passes 34/34.
Review of the four drawio feature commits surfaced one escaping
inconsistency, two silent-corruption paths, and the open risk of the two
hand-mirrored implementations drifting apart. Fixes, all verified to leave
the 15 committed example pairs byte-identical:

- Non-strict edge labels now go through escapeAttr like every other value
  path. The real CLI pipeline was safe only because the SVG emission
  pre-escapes attributes; direct builder calls (the unit tests' own usage)
  with a '"' in a label emitted malformed XML. escapeText had no other
  callers and is gone.
- Wraps parenting matches JSON boundaries to parsed boundaries by
  data-composition-frame-id (the renderer stamps the array index) instead
  of raw position. A boundary the SVG parser skips (zero-size) used to
  shift every later boundary onto the wrong frame and onto a parent cell
  id that was never emitted; now only that boundary's own wraps are lost.
  Positional pairing remains the fallback for inputs without frame-ids.
- Sequence footY falls back 0 → headers+60 → 400 like the viewer, so an
  empty participant set no longer feeds -Infinity into lifeline heights.
- Default --strict output is now <name>-strict.drawio, matching the viewer
  button and the examples, instead of <name>.strict.drawio.
- The node-group attribute-order contract (id first, data-node-* after) is
  now documented at both ends: focusNodeAttrs and NODE_G_RE.
- New parity test extracts the viewer's non-strict converter section from
  the live template.html and asserts byte-identical XML against the Node
  converter over the rendered web-app artifact, so future one-sided edits
  fail loudly. Plus regressions for the quote label, skipped-boundary
  parenting, empty-sequence NaN, and the strict default name.

Verified: drawio suite 39/39, full suite 666/666, and all ten re-exported
sample pairs diff clean against the committed files.
The draw.io export work changed skill runtime files (template.html,
renderers/shared/{cli,svg-to-drawio}.mjs, scripts/export-drawio.mjs) but
left the checked-in archive at the pre-drawio tree, which no longer
shipped the exporter at all. Rebuilt per CONTRIBUTING so the packaged
skill matches the source; release identity still checks 2.14.0.

@tt-a1i tt-a1i left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking on draw.io export - this capability is still absent from main, so I would like to keep moving it forward.

However, this branch has diverged too far to safely rebase as-is. It now contains 74 changed files and about 95% of the textual diff is generated output. A merge simulation produces seven direct conflicts, including archify.zip, gallery/proof assets, and delta artifacts. The package is still based on Archify 2.14.0 while main is already 2.15.0.

Could you rebuild the change from current main, either by replacing this branch or opening a clean follow-up PR? Ideally split the work into:

  1. converter, CLI, and tests;
  2. viewer integration and documentation;
  3. regenerated examples, gallery, and ZIP only once, after the source is stable.

Before that version is ready, I found several correctness issues that need addressing:

  • In archify/renderers/shared/svg-to-drawio.mjs:1183-1186, strict export writes a second <mxGeometry as="geometry"> inside the edge's existing geometry. The sequence implementation at line 796 puts label coordinates on the single geometry element instead. XML parsing alone does not verify that diagrams.net applies the nested geometry, so this needs a real import or round-trip assertion.
  • Current main supports authored brand marks, but the converter only reads and emits id, kind, label, sublabel, and tag (svg-to-drawio.mjs:182-187, 552-560, 1108-1117). Merging this implementation would silently drop brand identity, including in the advertised strict 1:1 export.
  • The Node converter and the large inline viewer converter are hand-maintained copies. The parity test explicitly covers only one non-strict architecture artifact and excludes strict and sequence paths (export-drawio.test.mjs:624-647). Please derive both surfaces from one canonical implementation, or add parity coverage for all five diagram types, strict/non-strict, and browser/CLI paths.

Two smaller follow-ups:

  • All boundaries currently use parent="1" while nodes are assigned only to their innermost boundary. In nested region/security-group diagrams, moving the outer region therefore does not move the inner group and its nodes.
  • Please document the viewer export and archify export-drawio command in the English/Chinese README and packaged Skill docs.

The 39 draw.io-specific tests pass locally. The remaining request is mainly to reduce generated churn, rebuild the implementation semantically on current main, and add diagrams.net-level correctness coverage.

@YunyueLi

Copy link
Copy Markdown
Collaborator

Thank you for the substantial work on editable draw.io export. The capability remains valuable and is still absent from main; the recommendation below is about creating a safe path to merge it, not rejecting the product direction.

The current branch should not be rebased or extended further. It is based on an older Archify release, now conflicts with main, changes 74 files, and contains a very large amount of generated output. Continuing to patch this branch would make it difficult to distinguish the converter's behavior from stale generated artifacts and conflict resolutions.

Please open a clean follow-up PR from the latest main, reference #72, and use this PR as implementation history. A staged delivery would make the work reviewable:

  1. Converter, CLI, and correctness tests.

    Introduce one canonical SVG-to-draw.io conversion implementation and the archify export-drawio CLI seam. Keep viewer integration and bulk generated artifacts out of this first step. Add a real diagrams.net import or round-trip assertion for strict output; XML parsing alone cannot prove that diagrams.net applies edge geometry and label placement correctly.

  2. Viewer integration from the same implementation.

    The browser export and Node CLI must not remain two independently maintained converters. Generate or share the browser implementation from the same canonical source, and add parity coverage for all five diagram types in strict and non-strict modes. Sequence diagrams need dedicated coverage for lifelines, message positions, activations, segment labels, and return-message dashes.

  3. Documentation and generated artifacts only after the source is accepted.

    Document both the viewer export and CLI command in the English README, Chinese README, and packaged Skill. Regenerate examples, Gallery artifacts, proof assets, and archify.zip once from the final accepted source rather than carrying old generated output through the review.

The clean implementation should also meet these correctness requirements:

  • emit a single valid mxGeometry contract per edge; do not rely on nested geometry elements that diagrams.net may ignore;
  • preserve authored brand marks, node and relationship identity, labels, routing, dash semantics, and strict-mode geometry;
  • preserve nested boundary hierarchy so moving an outer boundary also moves its inner boundaries and contained nodes;
  • cover architecture, workflow, sequence, data-flow, and lifecycle with representative import-level fixtures;
  • use the current package and release identity rather than rebuilding from the previous 2.14.0 state;
  • keep the initial source diff focused and defer generated files until the behavior is stable.

Once the clean follow-up is open, the current PR can be closed in favor of it. This preserves the design work already completed while giving the feature a realistic route to approval and merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants