Arrowheads carry meaning: a crow's foot IS one-to-many, a hollow diamond IS
aggregation. The engine allowed exactly one arrowhead; this opens the
vocabulary the same way shapes were opened:
- LinkSpec gains head/tail (pass-through to endArrow/startArrow, charset-
gated against style injection) and headFill/tailFill — fill is written
explicitly whenever a head is declared, because UML composition and
aggregation differ ONLY by fill and draw.io's per-head default would flip
the meaning. bold (4px amber, for THE key relationship) included.
- Parallel edges: a second link between the same pair is allowed when it
carries an id (ER's 'places' and 'cancels' between the same two entities);
without one it stays an error, since two identical overlapping lines is a
mistake. Edge ids are also what later operations address.
- Sequence messages respect a declared head (an async message's open arrow
is UML notation) while defaulting to the solid block as before.
- draw_graph's edge schema extended to match; GraphEdge passes the new
fields through to the link operations it generates.
5 new tests: crow's foot style emission, hollow-vs-filled round trip,
injection rejection, parallel-edge gating, bold round trip. 561 unit tests
green. ER+UML acceptance diagram (crow's foot, zero-to-one, hollow
inheritance triangle, filled composition diamond) verified in the real
editor.