@dr2rai/raid-canvas 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/X6Shapes.ts CHANGED
@@ -9,12 +9,15 @@
9
9
  * - Cascais Heraldry design tokens.
10
10
  */
11
11
 
12
- import { Graph, Node, Edge } from '@antv/x6';
12
+ import { Graph, Shape, Node, Edge } from '@antv/x6';
13
13
  import type {
14
+ AimOntologyKind,
14
15
  AimEdgeKind,
16
+ AimRoutingMode,
15
17
  RaidNodeData,
16
18
  RaidEdgeData,
17
19
  OrthogonalPortId,
20
+ Bounds,
18
21
  } from './types.js';
19
22
 
20
23
  /**
@@ -105,9 +108,9 @@ export function registerAimShapes(): void {
105
108
  }
106
109
 
107
110
  // 1. AimUseCaseNode ('uc') — Ellipse with Net Gold border
108
- Node.define({
111
+ Shape.Ellipse.define({
109
112
  shape: 'aim-uc',
110
- inherit: 'ellipse',
113
+ overwrite: true,
111
114
  width: 140,
112
115
  height: 70,
113
116
  attrs: {
@@ -115,8 +118,6 @@ export function registerAimShapes(): void {
115
118
  fill: CascaisPalette.ChalkWhite,
116
119
  stroke: CascaisPalette.NetGold,
117
120
  strokeWidth: 2,
118
- rx: 70,
119
- ry: 35,
120
121
  class: 'aim-node aim-uc',
121
122
  },
122
123
  label: {
@@ -133,9 +134,9 @@ export function registerAimShapes(): void {
133
134
  });
134
135
 
135
136
  // 2. AimActivityNode ('act') — Rounded rectangle with Heraldic Green border
136
- Node.define({
137
+ Shape.Rect.define({
137
138
  shape: 'aim-act',
138
- inherit: 'rect',
139
+ overwrite: true,
139
140
  width: 150,
140
141
  height: 60,
141
142
  attrs: {
@@ -161,9 +162,9 @@ export function registerAimShapes(): void {
161
162
  });
162
163
 
163
164
  // 3. AimClassNode ('cls') — Compartmentalized class card
164
- Node.define({
165
+ Shape.Rect.define({
165
166
  shape: 'aim-cls',
166
- inherit: 'rect',
167
+ overwrite: true,
167
168
  width: 180,
168
169
  height: 100,
169
170
  markup: [
@@ -258,9 +259,9 @@ export function registerAimShapes(): void {
258
259
  });
259
260
 
260
261
  // 4. AimObjectNode ('obj') — Instance card with underlined title
261
- Node.define({
262
+ Shape.Rect.define({
262
263
  shape: 'aim-obj',
263
- inherit: 'rect',
264
+ overwrite: true,
264
265
  width: 160,
265
266
  height: 80,
266
267
  attrs: {
@@ -271,7 +272,7 @@ export function registerAimShapes(): void {
271
272
  class: 'aim-node aim-obj',
272
273
  },
273
274
  label: {
274
- text: '<u>instance: Type</u>',
275
+ text: 'instance: Type',
275
276
  fill: CascaisPalette.TextPrimary,
276
277
  fontSize: 12,
277
278
  fontFamily: 'Inter, system-ui, -apple-system, sans-serif',
@@ -283,9 +284,9 @@ export function registerAimShapes(): void {
283
284
  });
284
285
 
285
286
  // 5. AimPersonNode ('per') — Person / Actor role card
286
- Node.define({
287
+ Shape.Rect.define({
287
288
  shape: 'aim-per',
288
- inherit: 'rect',
289
+ overwrite: true,
289
290
  width: 120,
290
291
  height: 70,
291
292
  attrs: {
@@ -311,9 +312,9 @@ export function registerAimShapes(): void {
311
312
  });
312
313
 
313
314
  // 6. AimEdge — Orthogonal Manhattan edge with rounded corners
314
- Edge.define({
315
+ Shape.Edge.define({
315
316
  shape: 'aim-edge',
316
- inherit: 'edge',
317
+ overwrite: true,
317
318
  router: {
318
319
  name: 'manhattan',
319
320
  args: {
@@ -457,6 +458,34 @@ export function createAimNode(data: RaidNodeData): Node.Metadata {
457
458
  }
458
459
  }
459
460
 
461
+ /**
462
+ * Configures the router and connector for an X6 Edge based on AimRoutingMode.
463
+ * - 'manhattan': Obstacle-avoiding 90° orthogonal router with rounded corners (radius: 8).
464
+ * - 'normal': Direct straight line point-to-point connection.
465
+ * - 'smooth': Curved cubic bezier spline between ports.
466
+ */
467
+ export function applyEdgeRouting(edge: Edge, routing: AimRoutingMode = 'manhattan'): void {
468
+ switch (routing) {
469
+ case 'normal':
470
+ edge.setRouter('normal');
471
+ edge.setConnector('normal');
472
+ break;
473
+ case 'smooth':
474
+ edge.setRouter('normal');
475
+ edge.setConnector('smooth');
476
+ break;
477
+ case 'manhattan':
478
+ default:
479
+ edge.setRouter('manhattan', {
480
+ padding: 20,
481
+ startDirections: ['top', 'right', 'bottom', 'left'],
482
+ endDirections: ['top', 'right', 'bottom', 'left'],
483
+ });
484
+ edge.setConnector('rounded', { radius: 8 });
485
+ break;
486
+ }
487
+ }
488
+
460
489
  /**
461
490
  * Factory creating an AntV X6 Edge model from a RaidEdgeData specification.
462
491
  */
@@ -464,10 +493,34 @@ export function createAimEdge(data: RaidEdgeData): Edge.Metadata {
464
493
  registerAimShapes();
465
494
 
466
495
  const edgeAttrs = getEdgeStyling(data.kind);
496
+ const routing = data.routing ?? 'manhattan';
497
+
498
+ let routerConfig: Edge.Metadata['router'] = {
499
+ name: 'manhattan',
500
+ args: {
501
+ padding: 20,
502
+ startDirections: ['top', 'right', 'bottom', 'left'],
503
+ endDirections: ['top', 'right', 'bottom', 'left'],
504
+ },
505
+ };
506
+ let connectorConfig: Edge.Metadata['connector'] = {
507
+ name: 'rounded',
508
+ args: { radius: 8 },
509
+ };
510
+
511
+ if (routing === 'normal') {
512
+ routerConfig = { name: 'normal' };
513
+ connectorConfig = { name: 'normal' };
514
+ } else if (routing === 'smooth') {
515
+ routerConfig = { name: 'normal' };
516
+ connectorConfig = { name: 'smooth' };
517
+ }
467
518
 
468
519
  return {
469
520
  id: data.id,
470
521
  shape: 'aim-edge',
522
+ router: routerConfig,
523
+ connector: connectorConfig,
471
524
  source: {
472
525
  cell: data.sourceId,
473
526
  ...(data.sourcePort !== undefined ? { port: data.sourcePort } : {}),
@@ -509,8 +562,11 @@ function getEdgeStyling(kind: AimEdgeKind): Record<string, unknown> {
509
562
  strokeWidth: 1.5,
510
563
  strokeDasharray: '5,5',
511
564
  targetMarker: {
512
- name: 'open',
513
- size: 8,
565
+ name: 'block',
566
+ args: {
567
+ size: 8,
568
+ open: true,
569
+ },
514
570
  },
515
571
  };
516
572
 
@@ -573,3 +629,48 @@ function getEdgeStyling(kind: AimEdgeKind): Record<string, unknown> {
573
629
  };
574
630
  }
575
631
  }
632
+
633
+ /**
634
+ * Returns default Cartesian bounds for an instantiated AOAIM archetype.
635
+ */
636
+ export function getDefaultNodeBounds(
637
+ kind: AimOntologyKind | string,
638
+ x: number = 100,
639
+ y: number = 100,
640
+ ): Bounds {
641
+ switch (kind) {
642
+ case 'uc':
643
+ return { x, y, width: 140, height: 70 };
644
+ case 'act':
645
+ return { x, y, width: 150, height: 60 };
646
+ case 'cls':
647
+ return { x, y, width: 180, height: 110 };
648
+ case 'obj':
649
+ return { x, y, width: 160, height: 80 };
650
+ case 'per':
651
+ return { x, y, width: 120, height: 70 };
652
+ default:
653
+ return { x, y, width: 140, height: 60 };
654
+ }
655
+ }
656
+
657
+ /**
658
+ * Returns a canonical default display name for an instantiated AOAIM archetype.
659
+ */
660
+ export function getDefaultNodeName(kind: AimOntologyKind | string): string {
661
+ switch (kind) {
662
+ case 'uc':
663
+ return 'New UseCase';
664
+ case 'act':
665
+ return 'New Activity';
666
+ case 'cls':
667
+ return 'NewClass';
668
+ case 'obj':
669
+ return 'new Object';
670
+ case 'per':
671
+ return 'Actor';
672
+ default:
673
+ return 'Entity';
674
+ }
675
+ }
676
+
package/src/index.ts CHANGED
@@ -7,12 +7,15 @@
7
7
  * - 4-way orthogonal ports and Manhattan router integration.
8
8
  * - Cascais Heraldry design tokens and theme styling.
9
9
  * - Bidirectional RaiBridge for hydrating from and serializing to the aim-* SVG contract.
10
+ * - Anti-entropy semantic connection validation and wiring rules.
11
+ * - Reusable <RaidCanvas /> React component with Dnd and imperative control handle.
10
12
  */
11
13
 
12
14
  // Core Metamodel & SVG Contract Types
13
15
  export {
14
16
  AimSvgContract,
15
17
  type AimOntologyKind,
18
+ type AimRoutingMode,
16
19
  type AimEdgeKind,
17
20
  type Point,
18
21
  type SvgBendPoint,
@@ -33,7 +36,29 @@ export {
33
36
  configureAimGraph,
34
37
  createAimNode,
35
38
  createAimEdge,
39
+ applyEdgeRouting,
40
+ getDefaultNodeBounds,
41
+ getDefaultNodeName,
36
42
  } from './X6Shapes.js';
37
43
 
44
+ // Anti-Entropy Semantic Connection Rules
45
+ export {
46
+ SEMANTIC_RULES_MATRIX,
47
+ validateSemanticConnection,
48
+ getSemanticEdgeKind,
49
+ getSemanticEdgeStereotype,
50
+ getSemanticRuleDescription,
51
+ getAvailableStereotypes,
52
+ type SemanticRule,
53
+ type StereotypeOption,
54
+ } from './semanticRules.js';
55
+
38
56
  // Bidirectional SVG <-> X6 Synchronization Bridge
39
57
  export { RaiBridge } from './RaiBridge.js';
58
+
59
+ // Reusable React Canvas Component & Ref Handle
60
+ export {
61
+ RaidCanvas,
62
+ type RaidCanvasProps,
63
+ type RaidCanvasHandle,
64
+ } from './RaidCanvas.js';
@@ -0,0 +1,237 @@
1
+ /**
2
+ * @file semanticRules.ts
3
+ * @description Anti-entropy semantic connection validation and wiring rules
4
+ * for the AOAIM (Activity-Object-AI Model) graphical modeling contract.
5
+ *
6
+ * Enforces ontological integrity during edge creation:
7
+ * - 'per' -> 'uc' : 'association' («initiates», «owns» [0..1], «participates» [0..*])
8
+ * - 'per' -> 'act' : 'association' («executes»)
9
+ * - 'uc' -> 'uc' : 'dependency' («includes», «extends», «dependsOn»)
10
+ * - 'uc' -> 'act' : 'dependency' («includes»)
11
+ * - 'act' -> 'act' : 'association' («sequence» / process flow)
12
+ * - 'cls' -> 'cls' : 'association' («relatesTo»), 'generalization', 'composition', 'aggregation'
13
+ * - 'cls' -> 'obj' : 'dependency' («instantiates»)
14
+ * - 'obj' -> 'obj' : 'association' («link»)
15
+ */
16
+
17
+ import type { AimOntologyKind, AimEdgeKind } from './types.js';
18
+
19
+ export interface StereotypeOption {
20
+ readonly stereotype: string;
21
+ readonly defaultCardinality?: string;
22
+ readonly description: string;
23
+ }
24
+
25
+ export interface SemanticRule {
26
+ readonly valid: boolean;
27
+ readonly defaultEdgeKind: AimEdgeKind;
28
+ readonly defaultStereotype: string;
29
+ readonly defaultCardinality?: string;
30
+ readonly availableStereotypes: readonly StereotypeOption[];
31
+ readonly description: string;
32
+ }
33
+
34
+ /**
35
+ * Ontological connection rules matrix.
36
+ * Key: `${sourceKind}->${targetKind}`
37
+ */
38
+ export const SEMANTIC_RULES_MATRIX: Readonly<Record<string, SemanticRule>> = {
39
+ 'per->uc': {
40
+ valid: true,
41
+ defaultEdgeKind: 'association',
42
+ defaultStereotype: '«initiates»',
43
+ description: 'Actor initiates, owns, or participates in UseCase',
44
+ availableStereotypes: [
45
+ {
46
+ stereotype: '«initiates»',
47
+ defaultCardinality: '1',
48
+ description: 'Initiating role triggering the UseCase flow',
49
+ },
50
+ {
51
+ stereotype: '«owns»',
52
+ defaultCardinality: '0..1',
53
+ description: 'Ownership relationship representing responsible party (cardinality 0..1)',
54
+ },
55
+ {
56
+ stereotype: '«participates»',
57
+ defaultCardinality: '0..*',
58
+ description: 'Participating role supporting the UseCase execution (cardinality 0..*)',
59
+ },
60
+ ],
61
+ },
62
+ 'per->act': {
63
+ valid: true,
64
+ defaultEdgeKind: 'association',
65
+ defaultStereotype: '«executes»',
66
+ description: 'Actor directly performs or executes Activity',
67
+ availableStereotypes: [
68
+ {
69
+ stereotype: '«executes»',
70
+ description: 'Actor performs this process step',
71
+ },
72
+ {
73
+ stereotype: '«approves»',
74
+ description: 'Actor reviews or approves activity completion',
75
+ },
76
+ ],
77
+ },
78
+ 'uc->uc': {
79
+ valid: true,
80
+ defaultEdgeKind: 'dependency',
81
+ defaultStereotype: '«includes»',
82
+ description: 'UseCase includes or extends sub-UseCase',
83
+ availableStereotypes: [
84
+ {
85
+ stereotype: '«includes»',
86
+ description: 'Mandatory sub-flow inclusion dependency',
87
+ },
88
+ {
89
+ stereotype: '«extends»',
90
+ description: 'Optional conditional extension point',
91
+ },
92
+ {
93
+ stereotype: '«dependsOn»',
94
+ description: 'Prerequisite functional dependency',
95
+ },
96
+ ],
97
+ },
98
+ 'uc->act': {
99
+ valid: true,
100
+ defaultEdgeKind: 'dependency',
101
+ defaultStereotype: '«includes»',
102
+ description: 'UseCase elaborates downflow process Activity',
103
+ availableStereotypes: [
104
+ {
105
+ stereotype: '«includes»',
106
+ description: 'Process activity required by UseCase',
107
+ },
108
+ {
109
+ stereotype: '«triggers»',
110
+ description: 'UseCase execution triggers activity',
111
+ },
112
+ ],
113
+ },
114
+ 'act->act': {
115
+ valid: true,
116
+ defaultEdgeKind: 'association',
117
+ defaultStereotype: '«sequence»',
118
+ description: 'Activity transitions to next process step',
119
+ availableStereotypes: [
120
+ {
121
+ stereotype: '«sequence»',
122
+ description: 'Sequential process transition',
123
+ },
124
+ {
125
+ stereotype: '«branch»',
126
+ description: 'Conditional decision branch',
127
+ },
128
+ ],
129
+ },
130
+ 'cls->cls': {
131
+ valid: true,
132
+ defaultEdgeKind: 'association',
133
+ defaultStereotype: '«relatesTo»',
134
+ description: 'Domain structural relationship between Classes',
135
+ availableStereotypes: [
136
+ {
137
+ stereotype: '«relatesTo»',
138
+ defaultCardinality: '1..*',
139
+ description: 'General domain association',
140
+ },
141
+ {
142
+ stereotype: '«generalization»',
143
+ description: 'Inheritance classification (subclass is-a superclass)',
144
+ },
145
+ {
146
+ stereotype: '«composition»',
147
+ defaultCardinality: '1',
148
+ description: 'Composite whole-part lifecycle binding',
149
+ },
150
+ {
151
+ stereotype: '«aggregation»',
152
+ defaultCardinality: '0..*',
153
+ description: 'Shared aggregation relationship',
154
+ },
155
+ ],
156
+ },
157
+ 'cls->obj': {
158
+ valid: true,
159
+ defaultEdgeKind: 'dependency',
160
+ defaultStereotype: '«instantiates»',
161
+ description: 'Class specifies runtime Object instance',
162
+ availableStereotypes: [
163
+ {
164
+ stereotype: '«instantiates»',
165
+ description: 'Class defines schema for runtime instance',
166
+ },
167
+ ],
168
+ },
169
+ 'obj->obj': {
170
+ valid: true,
171
+ defaultEdgeKind: 'association',
172
+ defaultStereotype: '«link»',
173
+ description: 'Runtime communication link between Objects',
174
+ availableStereotypes: [
175
+ {
176
+ stereotype: '«link»',
177
+ description: 'Runtime object collaboration message link',
178
+ },
179
+ ],
180
+ },
181
+ };
182
+
183
+ /**
184
+ * Validates whether an edge connection between source and target archetypes is ontologically allowed.
185
+ */
186
+ export function validateSemanticConnection(
187
+ sourceKind: AimOntologyKind | string,
188
+ targetKind: AimOntologyKind | string,
189
+ ): boolean {
190
+ if (!sourceKind || !targetKind) return false;
191
+ const key = `${sourceKind}->${targetKind}`;
192
+ return SEMANTIC_RULES_MATRIX[key]?.valid ?? false;
193
+ }
194
+
195
+ /**
196
+ * Determines the canonical default relationship edge kind for a connected pair.
197
+ */
198
+ export function getSemanticEdgeKind(
199
+ sourceKind: AimOntologyKind | string,
200
+ targetKind: AimOntologyKind | string,
201
+ ): AimEdgeKind {
202
+ const key = `${sourceKind}->${targetKind}`;
203
+ return SEMANTIC_RULES_MATRIX[key]?.defaultEdgeKind ?? 'association';
204
+ }
205
+
206
+ /**
207
+ * Determines the canonical default stereotype annotation for a connected pair.
208
+ */
209
+ export function getSemanticEdgeStereotype(
210
+ sourceKind: AimOntologyKind | string,
211
+ targetKind: AimOntologyKind | string,
212
+ ): string {
213
+ const key = `${sourceKind}->${targetKind}`;
214
+ return SEMANTIC_RULES_MATRIX[key]?.defaultStereotype ?? '';
215
+ }
216
+
217
+ /**
218
+ * Returns available ontological stereotypes and their default cardinalities for a pair.
219
+ */
220
+ export function getAvailableStereotypes(
221
+ sourceKind: AimOntologyKind | string,
222
+ targetKind: AimOntologyKind | string,
223
+ ): readonly StereotypeOption[] {
224
+ const key = `${sourceKind}->${targetKind}`;
225
+ return SEMANTIC_RULES_MATRIX[key]?.availableStereotypes ?? [];
226
+ }
227
+
228
+ /**
229
+ * Returns a human-readable description explaining the ontological relationship.
230
+ */
231
+ export function getSemanticRuleDescription(
232
+ sourceKind: AimOntologyKind | string,
233
+ targetKind: AimOntologyKind | string,
234
+ ): string {
235
+ const key = `${sourceKind}->${targetKind}`;
236
+ return SEMANTIC_RULES_MATRIX[key]?.description ?? 'Invalid ontological connection';
237
+ }
package/src/types.ts CHANGED
@@ -16,7 +16,15 @@
16
16
  * - 'obj' : Object / Instance (runtime instance card with underlined title)
17
17
  * - 'per' : Person / Actor (Initiating or Defined role stick-figure/card)
18
18
  */
19
- export type AimOntologyKind = 'uc' | 'act' | 'cls' | 'obj' | 'per';
19
+ export type AimOntologyKind = 'act' | 'uc' | 'cls' | 'obj' | 'per';
20
+
21
+ /**
22
+ * Routing strategy for diagram edges.
23
+ * - 'manhattan': Obstacle-avoiding 90° orthogonal routing with rounded corners (default).
24
+ * - 'normal': Direct straight line point-to-point connection.
25
+ * - 'smooth': Curved cubic bezier spline between ports.
26
+ */
27
+ export type AimRoutingMode = 'manhattan' | 'normal' | 'smooth';
20
28
 
21
29
  /**
22
30
  * Ontological relationship classifications in AOAIM.
@@ -126,6 +134,9 @@ export interface RaidEdgeData {
126
134
  /** Multiplicity / Cardinality at the target end (e.g., '0..1', '*'). */
127
135
  readonly targetCardinality?: string;
128
136
 
137
+ /** Routing strategy for this edge ('manhattan', 'normal', 'smooth'). */
138
+ readonly routing?: AimRoutingMode;
139
+
129
140
  /** User-editable or router-computed Manhattan bend points. */
130
141
  readonly bendPoints: readonly SvgBendPoint[];
131
142
  }
@@ -172,6 +183,7 @@ export const AimSvgContract = {
172
183
  ATTR_TARGET: 'aim-target',
173
184
  ATTR_SOURCE_PORT: 'aim-source-port',
174
185
  ATTR_TARGET_PORT: 'aim-target-port',
186
+ ATTR_ROUTING: 'aim-routing',
175
187
  ATTR_BENDS: 'aim-bends',
176
188
 
177
189
  // Selectors for DOM queries