@principal-ai/subsystems-react 0.37.12 → 0.39.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/dist/graphify/consolidated.d.ts +8 -0
- package/dist/graphify/consolidated.d.ts.map +1 -1
- package/dist/graphify/construct.d.ts +18 -0
- package/dist/graphify/construct.d.ts.map +1 -1
- package/dist/graphify/construct.js +23 -0
- package/dist/graphify/construct.js.map +1 -1
- package/dist/graphify/index.d.ts +2 -2
- package/dist/graphify/index.d.ts.map +1 -1
- package/dist/graphify/index.js +1 -1
- package/dist/graphify/index.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -1
- package/dist/stories/Subsystem/ComponentGraph/fixtures.js +0 -1
- package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -1
- package/dist/subsystem/IssueList.d.ts +49 -3
- package/dist/subsystem/IssueList.d.ts.map +1 -1
- package/dist/subsystem/IssueList.js +180 -79
- package/dist/subsystem/IssueList.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts +23 -2
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +492 -23
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/formatDeclaration.js +19 -5
- package/dist/subsystem/formatDeclaration.js.map +1 -1
- package/dist/subsystem/model.d.ts +161 -7
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +91 -12
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +133 -72
- package/dist/subsystem/nodes.js.map +1 -1
- package/dist/subsystem/symbolRefs.d.ts.map +1 -1
- package/dist/subsystem/symbolRefs.js +2 -0
- package/dist/subsystem/symbolRefs.js.map +1 -1
- package/dist/utils/elkLayout.d.ts +9 -0
- package/dist/utils/elkLayout.d.ts.map +1 -1
- package/dist/utils/elkLayout.js +21 -2
- package/dist/utils/elkLayout.js.map +1 -1
- package/package.json +3 -3
- package/src/graphify/__fixtures__/ego-samples.json +545 -0
- package/src/graphify/consolidated.ts +8 -0
- package/src/graphify/construct.test.ts +61 -1
- package/src/graphify/construct.ts +33 -0
- package/src/graphify/index.ts +6 -1
- package/src/index.ts +13 -1
- package/src/stories/Subsystem/ComponentGraph/Basics.stories.tsx +2 -2
- package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +9 -9
- package/src/stories/Subsystem/ComponentGraph/EdgeViews.stories.tsx +4 -4
- package/src/stories/Subsystem/ComponentGraph/IssueOverlay.stories.tsx +625 -0
- package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +7 -1
- package/src/stories/Subsystem/ComponentGraph/ModuleBadges.stories.tsx +5 -5
- package/src/stories/Subsystem/ComponentGraph/Modules.stories.tsx +5 -5
- package/src/stories/Subsystem/ComponentGraph/NodeAnatomy.stories.tsx +40 -73
- package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +6 -6
- package/src/stories/Subsystem/ComponentGraph/StoreValueType.stories.tsx +261 -0
- package/src/stories/Subsystem/ComponentGraph/fixtures.ts +0 -1
- package/src/stories/Subsystem/EgoGraph/EgoGraph.stories.tsx +354 -0
- package/src/stories/Subsystem/StoreValueTypeDeclaration.stories.tsx +182 -0
- package/src/subsystem/IssueList.focus.test.tsx +113 -0
- package/src/subsystem/IssueList.icon.test.tsx +95 -0
- package/src/subsystem/IssueList.tsx +267 -111
- package/src/subsystem/SubsystemComponentGraph.tsx +582 -27
- package/src/subsystem/formatDeclaration.test.ts +64 -0
- package/src/subsystem/formatDeclaration.ts +20 -5
- package/src/subsystem/model.test.ts +75 -2
- package/src/subsystem/model.ts +247 -17
- package/src/subsystem/nodes.group.test.tsx +52 -0
- package/src/subsystem/nodes.tsx +112 -8
- package/src/subsystem/symbolRefs.ts +2 -0
- package/src/subsystem/toC4.test.ts +3 -3
- package/src/utils/elkLayout.ts +32 -1
|
@@ -62,3 +62,55 @@ describe('SubsystemGroupNode frame color', () => {
|
|
|
62
62
|
expect(frame.style.border).toContain('#ff6b35');
|
|
63
63
|
});
|
|
64
64
|
});
|
|
65
|
+
|
|
66
|
+
describe('SubsystemGroupNode boundary badge', () => {
|
|
67
|
+
// A boundary finding is a property of the region's shape, so it badges the
|
|
68
|
+
// frame with its own icon rather than borrowing the verification-rung chips
|
|
69
|
+
// that leaf constructs wear.
|
|
70
|
+
const moduleRegion = {
|
|
71
|
+
kind: 'module' as const,
|
|
72
|
+
key: 'src/bun/index.ts',
|
|
73
|
+
label: 'src/bun/index.ts',
|
|
74
|
+
memberAliases: ['a', 'b', 'c'],
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
test('a boundary finding badges the frame with its own icon', () => {
|
|
78
|
+
const { container } = renderGroup({
|
|
79
|
+
region: moduleRegion,
|
|
80
|
+
issue: {
|
|
81
|
+
severity: 'info',
|
|
82
|
+
kind: 'boundary_process_nest_disagree',
|
|
83
|
+
count: 1,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
// The chip is aria-hidden and inert; its lucide icon carries the class.
|
|
87
|
+
expect(container.querySelector('.lucide-split')).not.toBeNull();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('a finding with no dedicated frame icon is not badged', () => {
|
|
91
|
+
// `construct_unconfirmed` belongs on a leaf node, not the frame — badging
|
|
92
|
+
// the region with it would imply the region is at fault.
|
|
93
|
+
const { container } = renderGroup({
|
|
94
|
+
region: moduleRegion,
|
|
95
|
+
issue: { severity: 'error', kind: 'construct_unconfirmed', count: 1 },
|
|
96
|
+
});
|
|
97
|
+
expect(container.querySelector('svg')).toBeNull();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('a region with no findings renders no chip', () => {
|
|
101
|
+
const { container } = renderGroup({ region: moduleRegion });
|
|
102
|
+
expect(container.querySelector('svg')).toBeNull();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('the chip shows a count when the region has more than one finding', () => {
|
|
106
|
+
const { container } = renderGroup({
|
|
107
|
+
region: moduleRegion,
|
|
108
|
+
issue: {
|
|
109
|
+
severity: 'error',
|
|
110
|
+
kind: 'boundary_process_nest_disagree',
|
|
111
|
+
count: 3,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
expect(container.textContent).toContain('3');
|
|
115
|
+
});
|
|
116
|
+
});
|
package/src/subsystem/nodes.tsx
CHANGED
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
} from '@xyflow/react';
|
|
18
18
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
19
19
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
edgeColor,
|
|
21
|
+
edgeStrokeStyle,
|
|
22
22
|
PROPOSED_COLOR,
|
|
23
23
|
constructBadgeColor,
|
|
24
24
|
constructBadgeLabel,
|
|
@@ -36,9 +36,12 @@ import {
|
|
|
36
36
|
type SubsystemGraphNodeData,
|
|
37
37
|
type SubsystemGroupNodeData,
|
|
38
38
|
type SubsystemGraphEdge,
|
|
39
|
+
type SubsystemNodeIssue,
|
|
39
40
|
} from './model';
|
|
40
41
|
import { componentColor } from '../pierre/constructColors';
|
|
41
42
|
import { resolvePierreSyntaxThemeName } from '../pierre/pierreSyntaxTheme';
|
|
43
|
+
import { ISSUE_KIND_ICON, ISSUE_RUNG_ICON } from './IssueList';
|
|
44
|
+
import type { LucideIcon } from 'lucide-react';
|
|
42
45
|
|
|
43
46
|
export const CONSTRUCT_LABEL: Record<string, string> = {
|
|
44
47
|
class: 'class',
|
|
@@ -112,6 +115,80 @@ function useSubsystemCallbacks(): SubsystemGraphCallbacks {
|
|
|
112
115
|
return useContext(SubsystemCallbacksContext) ?? SUBSYSTEM_CALLBACKS;
|
|
113
116
|
}
|
|
114
117
|
|
|
118
|
+
/**
|
|
119
|
+
* The diagnostics overlay for a component node: the node's own border turns
|
|
120
|
+
* dotted (see the node's `borderStyle`) and an earliest-failing-rung corner
|
|
121
|
+
* chip (file → symbol → declaration → type → signature) carries the severity
|
|
122
|
+
* color, with a count when the node has more than one finding. The chip is
|
|
123
|
+
* purely additive and non-interactive; the border stays construct-colored, so
|
|
124
|
+
* severity never has to compete with the construct palette.
|
|
125
|
+
*/
|
|
126
|
+
function NodeIssueOverlay({ issue }: { issue: SubsystemNodeIssue }) {
|
|
127
|
+
const { theme } = useTheme();
|
|
128
|
+
const isError = issue.severity === 'error';
|
|
129
|
+
const color = isError
|
|
130
|
+
? (theme.colors.error ?? '#e5534b')
|
|
131
|
+
: (theme.colors.warning ?? '#d4a017');
|
|
132
|
+
return (
|
|
133
|
+
<IssueChip
|
|
134
|
+
color={color}
|
|
135
|
+
Icon={ISSUE_RUNG_ICON[issue.rung]}
|
|
136
|
+
count={issue.count}
|
|
137
|
+
anchor={{ right: -9, bottom: -8 }}
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The shared severity chip: a severity-colored ring around the finding's icon,
|
|
144
|
+
* with a count when there is more than one. Component nodes anchor it to the
|
|
145
|
+
* bottom-right corner; a region frame anchors it to the top-right so it never
|
|
146
|
+
* collides with the frame's own name badge. `aria-hidden` and inert — the
|
|
147
|
+
* canvas badge never competes with the sidebar card for the interaction.
|
|
148
|
+
*/
|
|
149
|
+
function IssueChip({
|
|
150
|
+
color,
|
|
151
|
+
Icon,
|
|
152
|
+
count,
|
|
153
|
+
anchor,
|
|
154
|
+
}: {
|
|
155
|
+
color: string;
|
|
156
|
+
Icon: LucideIcon;
|
|
157
|
+
count: number;
|
|
158
|
+
anchor: React.CSSProperties;
|
|
159
|
+
}) {
|
|
160
|
+
const { theme } = useTheme();
|
|
161
|
+
const badgeBg = theme.colors.backgroundSecondary ?? theme.colors.background;
|
|
162
|
+
return (
|
|
163
|
+
<div
|
|
164
|
+
aria-hidden
|
|
165
|
+
style={{
|
|
166
|
+
position: 'absolute',
|
|
167
|
+
zIndex: 2,
|
|
168
|
+
display: 'inline-flex',
|
|
169
|
+
alignItems: 'center',
|
|
170
|
+
justifyContent: 'center',
|
|
171
|
+
gap: 4,
|
|
172
|
+
minWidth: 22,
|
|
173
|
+
padding: '3px 5px',
|
|
174
|
+
borderRadius: 5,
|
|
175
|
+
border: `2px solid ${color}`,
|
|
176
|
+
background: badgeBg,
|
|
177
|
+
color,
|
|
178
|
+
fontFamily: theme.fonts.monospace,
|
|
179
|
+
fontSize: theme.fontSizes[1],
|
|
180
|
+
fontWeight: 700,
|
|
181
|
+
lineHeight: 1.1,
|
|
182
|
+
pointerEvents: 'none',
|
|
183
|
+
...anchor,
|
|
184
|
+
}}
|
|
185
|
+
>
|
|
186
|
+
<Icon size={14} color={color} />
|
|
187
|
+
{count > 1 ? <span>{count}</span> : null}
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
115
192
|
export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeData, 'subsystem-component'>>) {
|
|
116
193
|
const { theme, mode } = useTheme();
|
|
117
194
|
const callbacks = useSubsystemCallbacks();
|
|
@@ -168,6 +245,12 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
168
245
|
const borderW = isSelected || fileMatch ? 4 : 2;
|
|
169
246
|
const badgeTop = -9 - borderW;
|
|
170
247
|
const badgeEdge = -borderW;
|
|
248
|
+
// A node with diagnostics turns its border dotted — a "something's off"
|
|
249
|
+
// sibling of `proposed`'s dashed, but distinct so the two don't read alike.
|
|
250
|
+
// The border keeps its construct color; the severity rides on the chip.
|
|
251
|
+
// Verification skips `proposed` nodes, so the two rarely stack — issue wins.
|
|
252
|
+
const hasIssue = data.issue != null;
|
|
253
|
+
const borderStyle = hasIssue ? 'dotted' : c.proposed ? 'dashed' : 'solid';
|
|
171
254
|
|
|
172
255
|
return (
|
|
173
256
|
<div
|
|
@@ -198,8 +281,9 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
198
281
|
borderRadius: nodeRadius,
|
|
199
282
|
background: hover ? hoverBg : nodeBg,
|
|
200
283
|
// Selected / file-matched nodes get a thicker border. Proposed nodes
|
|
201
|
-
// use a dashed goldenrod border
|
|
202
|
-
|
|
284
|
+
// use a dashed goldenrod border, issue nodes a dotted construct-colored
|
|
285
|
+
// one; the left construct badge keeps construct color either way.
|
|
286
|
+
border: `${borderW}px ${borderStyle} ${borderColor}`,
|
|
203
287
|
boxShadow: fileMatch
|
|
204
288
|
? `0 1px 4px rgba(0,0,0,0.25), 0 0 12px ${theme.colors.primary}55`
|
|
205
289
|
: '0 1px 4px rgba(0,0,0,0.25)',
|
|
@@ -339,6 +423,8 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
339
423
|
</div>
|
|
340
424
|
)}
|
|
341
425
|
|
|
426
|
+
{data.issue && <NodeIssueOverlay issue={data.issue} />}
|
|
427
|
+
|
|
342
428
|
<Handle type="target" position={Position.Left} style={{ opacity: 0 }} />
|
|
343
429
|
<Handle type="source" position={Position.Right} style={{ opacity: 0 }} />
|
|
344
430
|
</div>
|
|
@@ -494,6 +580,20 @@ export function SubsystemGroupNode(props: NodeProps<Node<SubsystemGroupNodeData,
|
|
|
494
580
|
label
|
|
495
581
|
)}
|
|
496
582
|
</div>
|
|
583
|
+
{/* Boundary diagnostics badge — top-right, opposite the name badge, so the
|
|
584
|
+
two never collide however long the module path grows. */}
|
|
585
|
+
{data.issue && ISSUE_KIND_ICON[data.issue.kind] && (
|
|
586
|
+
<IssueChip
|
|
587
|
+
color={
|
|
588
|
+
data.issue.severity === 'error'
|
|
589
|
+
? (theme.colors.error ?? '#e5534b')
|
|
590
|
+
: (theme.colors.warning ?? '#d4a017')
|
|
591
|
+
}
|
|
592
|
+
Icon={ISSUE_KIND_ICON[data.issue.kind]!}
|
|
593
|
+
count={data.issue.count}
|
|
594
|
+
anchor={{ right: 12, top: 0, transform: 'translateY(-50%)' }}
|
|
595
|
+
/>
|
|
596
|
+
)}
|
|
497
597
|
</div>
|
|
498
598
|
);
|
|
499
599
|
}
|
|
@@ -560,10 +660,14 @@ export function SubsystemEdge({
|
|
|
560
660
|
}: EdgeProps<SubsystemGraphEdge>) {
|
|
561
661
|
const path = data?.elkPath ?? '';
|
|
562
662
|
const mechanism = data?.mechanism ?? 'uses';
|
|
563
|
-
|
|
564
|
-
//
|
|
565
|
-
//
|
|
566
|
-
const
|
|
663
|
+
// Color + dash resolve from provenance: subsystem mechanisms use the
|
|
664
|
+
// MECHANISM_* tables, graphify-native relations use the separate
|
|
665
|
+
// GRAPHIFY_RELATION_* palette (see edgeColor / edgeStrokeStyle).
|
|
666
|
+
const edgeRef = { mechanism, provenance: data?.provenance };
|
|
667
|
+
const color = edgeColor(edgeRef);
|
|
668
|
+
// Dash style encodes provenance (graphify) and relationship kind (dashed =
|
|
669
|
+
// inverted-control or observational: hierarchy, registration, watches).
|
|
670
|
+
const isDashed = edgeStrokeStyle(edgeRef) === 'dashed';
|
|
567
671
|
const dimmed = data?.dimmed === true;
|
|
568
672
|
// Dim the stroke by color, never via path `opacity`. SVG markers are shared
|
|
569
673
|
// by id; opacity on the referencing path paints every arrowhead that uses
|
|
@@ -335,6 +335,8 @@ export function extractDeclarationSymbolRefs(
|
|
|
335
335
|
for (const alt of declaration.unionOf ?? []) addType(alt, undefined, 'union');
|
|
336
336
|
break;
|
|
337
337
|
case 'store':
|
|
338
|
+
// The store's own value type (`valueTypeRef`), then its members.
|
|
339
|
+
addType(declaration.valueType, declaration.valueTypeRef, 'field');
|
|
338
340
|
for (const p of declaration.properties ?? []) {
|
|
339
341
|
addType(p.type, p.typeRef, 'field');
|
|
340
342
|
}
|
|
@@ -20,9 +20,9 @@ const doc: SubsystemModelDocument = {
|
|
|
20
20
|
],
|
|
21
21
|
relations: [
|
|
22
22
|
{ id: 'r1', from: 'a', to: 'b', relationType: 'method' }, // intra p1
|
|
23
|
-
{ id: 'r2', from: 'a', to: 'c', relationType: '
|
|
24
|
-
{ id: 'r3', from: 'b', to: 'c', relationType: '
|
|
25
|
-
{ id: 'r4', from: 'a', to: 'x', relationType: '
|
|
23
|
+
{ id: 'r2', from: 'a', to: 'c', relationType: 'method' },
|
|
24
|
+
{ id: 'r3', from: 'b', to: 'c', relationType: 'method' },
|
|
25
|
+
{ id: 'r4', from: 'a', to: 'x', relationType: 'method' },
|
|
26
26
|
],
|
|
27
27
|
walkthroughs: [
|
|
28
28
|
{
|
package/src/utils/elkLayout.ts
CHANGED
|
@@ -84,6 +84,16 @@ export interface ElkLayoutOptions {
|
|
|
84
84
|
*/
|
|
85
85
|
direction?: 'RIGHT' | 'LEFT' | 'DOWN' | 'UP';
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Order same-layer nodes by their source line (ascending) instead of leaving
|
|
89
|
+
* it to the crossing-minimizer. Nodes must carry a `data.component.line`
|
|
90
|
+
* (used when set; nodes without one keep ELK's neutral ordering). Off by
|
|
91
|
+
* default — the generic layered layout is tuned for crossings, and line order
|
|
92
|
+
* only makes sense for graphs that model a source region top-to-bottom.
|
|
93
|
+
* @default false
|
|
94
|
+
*/
|
|
95
|
+
orderByLine?: boolean;
|
|
96
|
+
|
|
87
97
|
/**
|
|
88
98
|
* Compound groups — each becomes an ELK parent whose `memberIds` are laid
|
|
89
99
|
* out inside it. `memberIds` may be leaf node ids or other group ids
|
|
@@ -323,11 +333,15 @@ function getElkOptions(options: ElkLayoutOptions): LayoutOptions {
|
|
|
323
333
|
interLayerSpacing = 0,
|
|
324
334
|
edgeLabels,
|
|
325
335
|
direction = 'RIGHT',
|
|
336
|
+
orderByLine = false,
|
|
326
337
|
} = options;
|
|
327
338
|
|
|
328
339
|
const baseOptions: LayoutOptions = {
|
|
329
340
|
'elk.algorithm': 'layered',
|
|
330
341
|
'elk.direction': direction,
|
|
342
|
+
// Keep same-layer ordering stable (not reversed / randomized) so a
|
|
343
|
+
// line-ordered rewrite or ELK's own ordering is predictable.
|
|
344
|
+
'elk.layered.crossingMinimization.semiInteractive': 'true',
|
|
331
345
|
// Spacing
|
|
332
346
|
'elk.spacing.nodeNode': String(nodeSpacing),
|
|
333
347
|
'elk.spacing.edgeEdge': String(edgeSpacing),
|
|
@@ -346,6 +360,14 @@ function getElkOptions(options: ElkLayoutOptions): LayoutOptions {
|
|
|
346
360
|
'elk.layered.thoroughness': '50',
|
|
347
361
|
};
|
|
348
362
|
|
|
363
|
+
// Model order (position hints) so a per-node `data.line` can drive same-layer
|
|
364
|
+
// ordering. ELK honours the `y` of each node as a position hint against the
|
|
365
|
+
// model order.
|
|
366
|
+
if (orderByLine) {
|
|
367
|
+
baseOptions['elk.layered.fixedAlignment'] = 'NONE';
|
|
368
|
+
baseOptions['elk.layered.considerModelOrder.strategy'] = 'NODES_AND_EDGES';
|
|
369
|
+
}
|
|
370
|
+
|
|
349
371
|
// Reserve space for inline edge labels so they don't overlap nodes/edges.
|
|
350
372
|
if (edgeLabels?.enabled !== false) {
|
|
351
373
|
const placement = edgeLabels?.placement ?? 'CENTER';
|
|
@@ -507,6 +529,7 @@ export async function computeElkLayout(
|
|
|
507
529
|
const edgeLabels = options.edgeLabels;
|
|
508
530
|
const endpointInset = options.endpointInset ?? 0;
|
|
509
531
|
const direction = options.direction ?? 'RIGHT';
|
|
532
|
+
const orderByLine = options.orderByLine ?? false;
|
|
510
533
|
|
|
511
534
|
// Build a map of original node positions BEFORE passing to ELK
|
|
512
535
|
// (ELK mutates the input nodes in place, so we must save positions first)
|
|
@@ -527,12 +550,20 @@ export async function computeElkLayout(
|
|
|
527
550
|
const nd = node.data as { component?: { layer?: number }; layer?: number } | undefined;
|
|
528
551
|
layer = nd?.layer ?? nd?.component?.layer;
|
|
529
552
|
|
|
553
|
+
// Optional source-line ordering: seed each node's `y` hint from its line so
|
|
554
|
+
// ELK's model-order placement puts lower lines further down within a layer.
|
|
555
|
+
let y = node.position.y;
|
|
556
|
+
if (orderByLine) {
|
|
557
|
+
const line = (node.data as { component?: { line?: number } } | undefined)?.component?.line;
|
|
558
|
+
if (typeof line === 'number') y = line;
|
|
559
|
+
}
|
|
560
|
+
|
|
530
561
|
return {
|
|
531
562
|
id: node.id,
|
|
532
563
|
width,
|
|
533
564
|
height,
|
|
534
565
|
x: node.position.x,
|
|
535
|
-
y
|
|
566
|
+
y,
|
|
536
567
|
// Add ports on each side for edge connections
|
|
537
568
|
ports: [
|
|
538
569
|
{ id: `${node.id}_top`, properties: { 'port.side': 'NORTH' } },
|