A user's approval-workflow flowchart came out with the return arrow drawn
straight through two unrelated steps, and a second report showed arrows
leaving a box and bending straight back across it. Measured over 250
generated flowcharts (2722 edges): 347 arrows crossed an unrelated box and
215 waypoints landed inside a shape. Four defects, each measured in
isolation:
1. The invisible layer containers draw_graph emits were handed to the
router as frames, so every clean return path was rejected for
"trespassing" on a border that is not drawn, and the fallback cut
through two boxes. Excluding invisible containers: 347 -> 218 crossing
arrows, no diagram made worse.
2. The router chose the horizontal-vs-vertical axis BEFORE searching, so
when the only clean corridor ran along the other axis it was never
looked at. A complete two-bend candidate generator that tries both
trunk axes, all four sides at each end, and the port fractions:
218 -> 27. (An independent ablation measured the axis pre-choice alone
at a 40% per-edge failure rate.)
3. Nothing stopped a route's first leg from turning back across its own
source shape - the obstacle test exempts an edge's own endpoints, and
must, since the line has to touch them. A terminal-leg rule refuses
such routes outright: 215 -> 4 hooks.
4. A two-bend search cannot express the staircase needed when a box sits
directly between two vertically aligned nodes (21 of the last 27
crossings). Added the orthogonal visibility graph + A* from Wybrow,
Marriott & Stuckey, "Orthogonal Connector Routing" (GD 2009) - the
libavoid algorithm - as the backstop when the candidate search finds
nothing. The interesting-points grid is provably sufficient: any valid
route shrinks onto it without getting longer or gaining bends. The A*
state is (point, incoming direction) with libavoid's bend cost of 10,
and the admissible bends-remaining heuristic, so it returns a cheapest
route, not merely a route. Implemented from the paper, not ported.
After all four: 0 crossing arrows and 0 hooks over the same 250 diagrams,
page area unchanged (494k px^2 mean), 260ms for the whole corpus, mean
0.82 bends per edge. The shape ladder still runs first, so routes that
were already clean are byte-identical.
Also post-nudge validation now checks the whole path (the nudge pass only
reverts the single segment it moved, judged in isolation) and restores the
search's route if nudging made it dirty.
Extends the declarative engine past cloud architecture. The tool routing was
divided by icon library — AWS through the engine, everything else hand-written
XML — which is the wrong axis. What matters is the LAYOUT SHAPE.
Measured first: a six-step approval flow declared in its natural order comes out
as one column, because the layout only arranges what nesting tells it to and
never looked at the arrows. That forces the arrow from the decision to its second
branch to jump over the first branch.
graph.ts computes what the layout should have looked at: layer assignment by
longest path, cycle breaking so a loop is drawn without setting the order, and
barycentre sweeping to cut edge crossings. It emits ordinary container
operations, so layout, routing and round-tripping are unchanged — reaching zero
arrows-through-boxes on a 14-node pipeline and zero crossings on a bipartite
graph whose declared order forces three.
Three new container kinds, each because one layout rule cannot serve them all:
pool — swimlanes. Lanes are real cells and each step is parented to its
band, so dragging a step to another role records the change.
sequence — participants across the top, one lifeline cell per participant so
head and line stay together on a drag. Messages bypass the router:
a message's height IS its order.
radial — mind maps and org charts. Children are a flat list and the
hierarchy comes from the links, because a branch is a box and a box
cannot hold children.
Flowchart box shapes (diamond, stadium, parallelogram, document) so a reader can
tell a branch from a step.
Two bugs the new tests caught: the duplicate-link guard blocked a sequence
diagram from having two messages between the same pair, and the fallback message
numbering was shared across containers, pushing a second diagram's messages off
its own lifelines.
523 unit tests and 17 diagram e2e tests pass. Every kind verified round-trip
stable to a fixed point, and rendered in a real browser — draw.io keeps the
lifeline shape and the lane markers.