Archify version or commit
2.17.0-dev.1 (10722002bb8777ecb639d93c49586fae4adf3ae4)
Installation method
Git clone of https://github.com/tt-a1i/archify
Diagram type
Architecture
Exact command
node archify/bin/archify.mjs deliver architecture hub-spoke.architecture.json /tmp/hub-spoke.html --quality standard --json
Minimal redacted JSON reproduction
{
"schema_version": 1,
"diagram_type": "architecture",
"meta": {
"title": "Hub above an offset spoke",
"output": "hub-spoke.html",
"quality_profile": "standard",
"viewBox": [640, 360]
},
"components": [
{ "id": "hub", "type": "cloud", "label": "Hub", "pos": [200, 40], "size": [176, 52] },
{ "id": "spoke", "type": "cloud", "label": "Spoke", "pos": [40, 240], "size": [168, 52] }
],
"connections": [
{ "id": "hub-spoke", "from": "hub", "to": "spoke" }
]
}
Hub center x is 288; spoke center x is 124. defaultFromSide therefore returns "left" even though the dominant gap is vertical.
Expected behavior
Automatic routing should leave the hub from the bottom and enter the spoke from the top (dominant axis), or it should infer sides from the path it actually drew. deliver should succeed without authored fromSide/toSide.
Suggested direction: make defaultFromSide / defaultToSide pick the axis with the larger center delta so hub-and-spoke above a row works. A follow-up: edge-through-node supportedFixes should include a concrete via candidate.
Actual behavior
Architecture auto-routing infers fromSide/toSide from horizontal center order first. A hub sitting above an offset spoke is therefore treated as “leave from the left / enter from the right.” The automatic router then draws a vertical dogleg, and Clean Flow rejects the path it just produced (clean-flow/endpoint-side-direction, sideOrigin: "inferred").
The suggested fix in the diagnostic is “keep automatic routing so the renderer can use a side-aware bridge,” but this JSON already uses automatic routing (no fromSide, toSide, route, or via).
This shows up on ordinary hub-and-spoke fabric (one gateway above a row of peers). Explicit fromSide: "bottom" / toSide: "top" plus a single-row layout is a workaround; the auto path should not fail.
Related: when a lower spoke sits in the same column as an unrelated node, the same inference also yields clean-flow/edge-through-node with no concrete via in supportedFixes.
deliver fails at stage render. Auto-router draws a vertical first segment [200, 66] -> [200, 166] and then reports that this does not honor inferred fromSide: "left".
Validation receipt or exact error
{
"ok": false,
"command": "deliver",
"stage": "render",
"type": "architecture",
"diagnostics": [
{
"code": "clean-flow/endpoint-side-direction",
"severity": "error",
"evidence": {
"endpoint": "source",
"authoredField": "fromSide",
"sideOrigin": "inferred",
"side": "left",
"segmentIndex": 0,
"from": [200, 66],
"to": [200, 166],
"expectedAxis": "horizontal",
"expectedDirection": "leftward"
},
"supportedFixes": [
"keep automatic routing so the renderer can use a side-aware bridge, or set truthful fromSide/toSide with perpendicular via segments"
]
},
{
"code": "clean-flow/endpoint-side-direction",
"severity": "error",
"evidence": {
"endpoint": "target",
"authoredField": "toSide",
"sideOrigin": "inferred",
"side": "right",
"segmentIndex": 2,
"from": [208, 166],
"to": [208, 266],
"expectedAxis": "horizontal",
"expectedDirection": "leftward from the right"
}
}
]
}
Workaround
{ "id": "hub-spoke", "from": "hub", "to": "spoke", "fromSide": "bottom", "toSide": "top" }
That delivers. Auto mode should not require it for this geometry.
Environment
macOS, Node.js 26.7.0, Cursor. No private topology in this reproduction.
Final checks
Archify version or commit
2.17.0-dev.1(10722002bb8777ecb639d93c49586fae4adf3ae4)Installation method
Git clone of https://github.com/tt-a1i/archify
Diagram type
Architecture
Exact command
Minimal redacted JSON reproduction
{ "schema_version": 1, "diagram_type": "architecture", "meta": { "title": "Hub above an offset spoke", "output": "hub-spoke.html", "quality_profile": "standard", "viewBox": [640, 360] }, "components": [ { "id": "hub", "type": "cloud", "label": "Hub", "pos": [200, 40], "size": [176, 52] }, { "id": "spoke", "type": "cloud", "label": "Spoke", "pos": [40, 240], "size": [168, 52] } ], "connections": [ { "id": "hub-spoke", "from": "hub", "to": "spoke" } ] }Hub center x is 288; spoke center x is 124.
defaultFromSidetherefore returns"left"even though the dominant gap is vertical.Expected behavior
Automatic routing should leave the hub from the bottom and enter the spoke from the top (dominant axis), or it should infer sides from the path it actually drew.
delivershould succeed without authoredfromSide/toSide.Suggested direction: make
defaultFromSide/defaultToSidepick the axis with the larger center delta so hub-and-spoke above a row works. A follow-up:edge-through-nodesupportedFixesshould include a concreteviacandidate.Actual behavior
Architecture auto-routing infers
fromSide/toSidefrom horizontal center order first. A hub sitting above an offset spoke is therefore treated as “leave from the left / enter from the right.” The automatic router then draws a vertical dogleg, and Clean Flow rejects the path it just produced (clean-flow/endpoint-side-direction,sideOrigin: "inferred").The suggested fix in the diagnostic is “keep automatic routing so the renderer can use a side-aware bridge,” but this JSON already uses automatic routing (no
fromSide,toSide,route, orvia).This shows up on ordinary hub-and-spoke fabric (one gateway above a row of peers). Explicit
fromSide: "bottom"/toSide: "top"plus a single-row layout is a workaround; the auto path should not fail.Related: when a lower spoke sits in the same column as an unrelated node, the same inference also yields
clean-flow/edge-through-nodewith no concreteviainsupportedFixes.deliverfails at stagerender. Auto-router draws a vertical first segment[200, 66] -> [200, 166]and then reports that this does not honor inferredfromSide: "left".Validation receipt or exact error
{ "ok": false, "command": "deliver", "stage": "render", "type": "architecture", "diagnostics": [ { "code": "clean-flow/endpoint-side-direction", "severity": "error", "evidence": { "endpoint": "source", "authoredField": "fromSide", "sideOrigin": "inferred", "side": "left", "segmentIndex": 0, "from": [200, 66], "to": [200, 166], "expectedAxis": "horizontal", "expectedDirection": "leftward" }, "supportedFixes": [ "keep automatic routing so the renderer can use a side-aware bridge, or set truthful fromSide/toSide with perpendicular via segments" ] }, { "code": "clean-flow/endpoint-side-direction", "severity": "error", "evidence": { "endpoint": "target", "authoredField": "toSide", "sideOrigin": "inferred", "side": "right", "segmentIndex": 2, "from": [208, 166], "to": [208, 266], "expectedAxis": "horizontal", "expectedDirection": "leftward from the right" } } ] }Workaround
{ "id": "hub-spoke", "from": "hub", "to": "spoke", "fromSide": "bottom", "toSide": "top" }That delivers. Auto mode should not require it for this geometry.
Environment
macOS, Node.js 26.7.0, Cursor. No private topology in this reproduction.
Final checks
endpoint-side-direction,defaultFromSide). Closest neighbors are [Bug]: Prefer straight routes over unnecessary Port Spread doglegs #137 (Port Spread doglegs) and Feature — via direction contract is only discoverable from error messages #357 (via direction contract discoverability); neither covers inferred left/right on a hub above an offset spoke.