feat: implement Stories 2.4-2.7 — E-R, Org Chart, Architecture, Sequence diagram type renderers

Adds four diagram type renderers completing the core diagram type suite:
- Story 2.4: E-R entity nodes with column tables, relationship edges with cardinality labels
- Story 2.5: Org chart person nodes with role/department tags, hierarchy edges
- Story 2.6: Architecture nodes (service, database, queue, load balancer, external), connection edges
- Story 2.7: Sequence participant nodes with lifelines + activation bars, fragment nodes,
  3 custom edge types (sync/async/return), custom time-ordered layout (not ELK)

Story 2.7 includes code review fixes: computeLayout returns LayoutResult so enriched
sequence edges flow through useAutoLayout, activation bar computation in layout,
immutable layout function, self-message U-shaped loop rendering, sequence node size
tests in buildElkGraph.

476 tests passing across 29 test files, zero regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-02-27 01:24:50 +00:00
parent 0a7838aa60
commit 1ff8ff8f06
38 changed files with 5620 additions and 22 deletions

View File

@@ -32,6 +32,21 @@ import {
BpmnMessageEdge,
BpmnAssociationEdge,
} from "../../types/bpmn";
import { ErEntityNode } from "../../types/er/ErEntityNode";
import { ErRelationshipEdge } from "../../types/er/ErRelationshipEdge";
import { OrgchartPersonNode } from "../../types/orgchart/OrgchartPersonNode";
import { OrgchartHierarchyEdge } from "../../types/orgchart/OrgchartHierarchyEdge";
import { ArchServiceNode } from "../../types/architecture/ArchServiceNode";
import { ArchDatabaseNode } from "../../types/architecture/ArchDatabaseNode";
import { ArchQueueNode } from "../../types/architecture/ArchQueueNode";
import { ArchLoadBalancerNode } from "../../types/architecture/ArchLoadBalancerNode";
import { ArchExternalNode } from "../../types/architecture/ArchExternalNode";
import { ArchConnectionEdge } from "../../types/architecture/ArchConnectionEdge";
import { SeqParticipantNode } from "../../types/sequence/SeqParticipantNode";
import { SeqFragmentNode } from "../../types/sequence/SeqFragmentNode";
import { SeqSyncEdge } from "../../types/sequence/SeqSyncEdge";
import { SeqAsyncEdge } from "../../types/sequence/SeqAsyncEdge";
import { SeqReturnEdge } from "../../types/sequence/SeqReturnEdge";
const nodeTypes = {
bpmnActivity: BpmnActivityNode,
@@ -46,21 +61,37 @@ const nodeTypes = {
bpmnPool: BpmnPoolNode,
bpmnLane: BpmnLaneNode,
bpmnGroup: BpmnGroupNode,
erEntity: ErEntityNode,
orgchartPerson: OrgchartPersonNode,
archService: ArchServiceNode,
archDatabase: ArchDatabaseNode,
archQueue: ArchQueueNode,
archLoadBalancer: ArchLoadBalancerNode,
archExternal: ArchExternalNode,
seqParticipant: SeqParticipantNode,
seqFragment: SeqFragmentNode,
};
const edgeTypes = {
bpmnSequence: BpmnSequenceEdge,
bpmnMessage: BpmnMessageEdge,
bpmnAssociation: BpmnAssociationEdge,
erRelationship: ErRelationshipEdge,
orgchartHierarchy: OrgchartHierarchyEdge,
archConnection: ArchConnectionEdge,
seqSync: SeqSyncEdge,
seqAsync: SeqAsyncEdge,
seqReturn: SeqReturnEdge,
};
/** Container node types that should not participate in BFS highlighting */
const CONTAINER_TYPES = new Set(["bpmnPool", "bpmnLane", "bpmnGroup"]);
const CONTAINER_TYPES = new Set(["bpmnPool", "bpmnLane", "bpmnGroup", "seqFragment"]);
function BpmnMarkerDefs() {
function MarkerDefs() {
return (
<svg style={{ position: "absolute", width: 0, height: 0 }}>
<defs>
{/* BPMN markers */}
<marker
id="bpmn-arrow-filled"
viewBox="0 0 10 10"
@@ -91,6 +122,67 @@ function BpmnMarkerDefs() {
strokeWidth={1.5}
/>
</marker>
{/* E-R markers */}
<marker
id="er-arrow"
viewBox="0 0 10 10"
refX={10}
refY={5}
markerWidth={8}
markerHeight={8}
orient="auto-start-reverse"
>
<path
d="M 0 0 L 10 5 L 0 10 Z"
fill="var(--diagram-er, #7c3aed)"
/>
</marker>
{/* Architecture markers */}
<marker
id="arch-arrow"
viewBox="0 0 10 10"
refX={10}
refY={5}
markerWidth={8}
markerHeight={8}
orient="auto-start-reverse"
>
<path
d="M 0 0 L 10 5 L 0 10 Z"
fill="var(--diagram-architecture, #71717a)"
/>
</marker>
{/* Sequence markers */}
<marker
id="seq-arrow-filled"
viewBox="0 0 10 10"
refX={10}
refY={5}
markerWidth={8}
markerHeight={8}
orient="auto-start-reverse"
>
<path
d="M 0 0 L 10 5 L 0 10 Z"
fill="var(--diagram-sequence, #f59e0b)"
/>
</marker>
<marker
id="seq-arrow-open"
viewBox="0 0 10 10"
refX={10}
refY={5}
markerWidth={8}
markerHeight={8}
orient="auto-start-reverse"
>
<path
d="M 0 0 L 10 5 L 0 10"
fill="none"
stroke="var(--diagram-sequence, #f59e0b)"
strokeWidth={1.5}
/>
</marker>
</defs>
</svg>
);
@@ -168,7 +260,7 @@ function CanvasInner() {
return (
<div className="w-full h-full">
<BpmnMarkerDefs />
<MarkerDefs />
<ReactFlow
nodes={nodes}
edges={edges}