@principal-ai/subsystems-react 0.36.0 → 0.36.2
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/subsystem/SubsystemAggregateGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemAggregateGraph.js +35 -1
- package/dist/subsystem/SubsystemAggregateGraph.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +14 -13
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/model.d.ts +7 -0
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +18 -2
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts +0 -3
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +39 -44
- package/dist/subsystem/nodes.js.map +1 -1
- package/dist/utils/elkLayout.d.ts.map +1 -1
- package/dist/utils/elkLayout.js +4 -1
- package/dist/utils/elkLayout.js.map +1 -1
- package/package.json +1 -1
- package/src/stories/Subsystem/ComponentGraph/ModuleBadges.stories.tsx +3 -3
- package/src/stories/Subsystem/ComponentGraph/Processes.stories.tsx +19 -11
- package/src/stories/Subsystem/ComponentGraph/WorkspacePackageFrames.stories.tsx +137 -0
- package/src/subsystem/SubsystemAggregateGraph.tsx +38 -0
- package/src/subsystem/SubsystemComponentGraph.tsx +16 -12
- package/src/subsystem/model.test.ts +14 -0
- package/src/subsystem/model.ts +25 -2
- package/src/subsystem/nodes.tsx +42 -60
- package/src/utils/elkLayout.ts +4 -1
package/src/subsystem/nodes.tsx
CHANGED
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
storageBadgeLabel,
|
|
28
28
|
storageBadgeColor,
|
|
29
29
|
deriveNameFromSymbol,
|
|
30
|
-
BADGE_EDGE_INSET,
|
|
31
30
|
nodeMinWidthForBadges,
|
|
32
31
|
MODULE_BADGE_INSET,
|
|
33
32
|
moduleBadgeLabel,
|
|
@@ -70,9 +69,6 @@ function breakWords(s: string): string {
|
|
|
70
69
|
export interface SubsystemGraphCallbacks {
|
|
71
70
|
/** Click a component — open its file/entry point. */
|
|
72
71
|
onSelect?: (componentAlias: string) => void;
|
|
73
|
-
/** Click the filename badge — open that component's file in the drawer
|
|
74
|
-
* (same path as clicking the file link in the declaration panel). */
|
|
75
|
-
onOpenFile?: (componentAlias: string) => void;
|
|
76
72
|
/** Click an edge (or its label) — select the relationship. */
|
|
77
73
|
onEdgeSelect?: (edgeId: string) => void;
|
|
78
74
|
/** Hover a component (null on leave) — associates it with the file tree. */
|
|
@@ -121,6 +117,7 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
121
117
|
const callbacks = useSubsystemCallbacks();
|
|
122
118
|
const { data, selected, width: nodeWidth, height: nodeHeight } = props;
|
|
123
119
|
const c = data.component;
|
|
120
|
+
const [hover, setHover] = useState(false);
|
|
124
121
|
// Construct owns node color, derived from the active Pierre syntax theme —
|
|
125
122
|
// the same palette the declaration panel and file drawer render with. Role
|
|
126
123
|
// shows as the hover badge, not as color — for now; a role glyph/accent may
|
|
@@ -129,7 +126,6 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
129
126
|
// Left badge: framework brand (e.g. React cyan) when the label is a
|
|
130
127
|
// framework stereotype; otherwise the same construct color as the border.
|
|
131
128
|
const badgeColor = constructBadgeColor(c) ?? color;
|
|
132
|
-
const [hover, setHover] = useState(false);
|
|
133
129
|
const configuredMax = callbacks.maxNodeWidth;
|
|
134
130
|
const maxWidth = configuredMax ?? 300;
|
|
135
131
|
// `symbol` is the source of truth; `name` is derived from it consistently.
|
|
@@ -145,6 +141,11 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
145
141
|
// Set while a file is open in the drawer: true → spotlight, false → dim,
|
|
146
142
|
// absent (no file open) → neutral.
|
|
147
143
|
const fileMatch = data.fileMatch as boolean | undefined;
|
|
144
|
+
// Node fill lifts on hover as the clickability affordance; badges share it
|
|
145
|
+
// so they read as tabs on the same surface rather than floating patches.
|
|
146
|
+
const nodeBg = theme.colors.backgroundSecondary ?? theme.colors.background;
|
|
147
|
+
const hoverBg =
|
|
148
|
+
theme.colors.backgroundHover ?? theme.colors.backgroundTertiary ?? nodeBg;
|
|
148
149
|
// Border: proposed uses the goldenrod accent (dashed); construct color stays
|
|
149
150
|
// on the left badge. File-open spotlight still wins with primary.
|
|
150
151
|
const borderColor = fileMatch
|
|
@@ -159,6 +160,14 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
159
160
|
const isExternal = c.construct === 'external';
|
|
160
161
|
const nodeRadius = isExternal ? 0 : 8;
|
|
161
162
|
const badgeRadius = isExternal ? 0 : 4;
|
|
163
|
+
// The border thickens on selection / file-match. Absolutely-positioned
|
|
164
|
+
// badges are laid out from the padding box (inside the border), so a raw
|
|
165
|
+
// `top` / `left` would slide with the border. Anchor them to the border box
|
|
166
|
+
// instead, offsetting by the border width. Side badges sit flush with the
|
|
167
|
+
// node's left / right edge (`-borderW`); the top reference stays at -9.
|
|
168
|
+
const borderW = isSelected || fileMatch ? 4 : 2;
|
|
169
|
+
const badgeTop = -9 - borderW;
|
|
170
|
+
const badgeEdge = -borderW;
|
|
162
171
|
|
|
163
172
|
return (
|
|
164
173
|
<div
|
|
@@ -187,15 +196,15 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
187
196
|
maxWidth,
|
|
188
197
|
padding: '6px 10px',
|
|
189
198
|
borderRadius: nodeRadius,
|
|
190
|
-
background:
|
|
199
|
+
background: hover ? hoverBg : nodeBg,
|
|
191
200
|
// Selected / file-matched nodes get a thicker border. Proposed nodes
|
|
192
201
|
// use a dashed goldenrod border; left construct badge keeps construct color.
|
|
193
|
-
border: `${
|
|
202
|
+
border: `${borderW}px ${c.proposed ? 'dashed' : 'solid'} ${borderColor}`,
|
|
194
203
|
boxShadow: fileMatch
|
|
195
204
|
? `0 1px 4px rgba(0,0,0,0.25), 0 0 12px ${theme.colors.primary}55`
|
|
196
205
|
: '0 1px 4px rgba(0,0,0,0.25)',
|
|
197
206
|
opacity: fileMatch === false || data.dimmed === true ? 0.18 : 1,
|
|
198
|
-
transition: 'opacity 150ms ease',
|
|
207
|
+
transition: 'opacity 150ms ease, background-color 120ms ease',
|
|
199
208
|
cursor: 'pointer',
|
|
200
209
|
fontFamily: theme.fonts.body,
|
|
201
210
|
}}
|
|
@@ -207,8 +216,8 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
207
216
|
<div
|
|
208
217
|
style={{
|
|
209
218
|
position: 'absolute',
|
|
210
|
-
top:
|
|
211
|
-
left:
|
|
219
|
+
top: badgeTop,
|
|
220
|
+
left: badgeEdge,
|
|
212
221
|
zIndex: 1,
|
|
213
222
|
fontFamily: theme.fonts.monospace,
|
|
214
223
|
fontSize: theme.fontSizes[1],
|
|
@@ -217,7 +226,7 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
217
226
|
lineHeight: '17px',
|
|
218
227
|
whiteSpace: 'nowrap',
|
|
219
228
|
color: badgeColor,
|
|
220
|
-
background:
|
|
229
|
+
background: nodeBg,
|
|
221
230
|
border: `2px solid ${badgeColor}`,
|
|
222
231
|
borderRadius: badgeRadius,
|
|
223
232
|
padding: '2px 8px',
|
|
@@ -226,15 +235,18 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
226
235
|
{constructBadgeLabel(c)}
|
|
227
236
|
</div>
|
|
228
237
|
|
|
229
|
-
{/*
|
|
238
|
+
{/* Secondary badge cluster — proposed wins over role when both are set;
|
|
230
239
|
store storage (retention backing) sits beside, since it is
|
|
231
|
-
orthogonal to topology. Persistent; pointer-events pass through.
|
|
240
|
+
orthogonal to topology. Persistent; pointer-events pass through.
|
|
241
|
+
Sits astride the bottom edge, centered (mirroring the top construct
|
|
242
|
+
badge's straddle), so the top edge stays free for the construct tag. */}
|
|
232
243
|
{(topRightLabel != null || storageLabel != null) && (
|
|
233
244
|
<div
|
|
234
245
|
style={{
|
|
235
246
|
position: 'absolute',
|
|
236
|
-
|
|
237
|
-
|
|
247
|
+
bottom: badgeTop,
|
|
248
|
+
left: '50%',
|
|
249
|
+
transform: 'translateX(-50%)',
|
|
238
250
|
zIndex: 1,
|
|
239
251
|
display: 'flex',
|
|
240
252
|
gap: 6,
|
|
@@ -250,7 +262,7 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
250
262
|
textTransform: 'uppercase',
|
|
251
263
|
lineHeight: '17px',
|
|
252
264
|
color: storageColor,
|
|
253
|
-
background:
|
|
265
|
+
background: nodeBg,
|
|
254
266
|
border: `2px solid ${storageColor}`,
|
|
255
267
|
borderRadius: badgeRadius,
|
|
256
268
|
padding: '2px 8px',
|
|
@@ -268,7 +280,7 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
268
280
|
textTransform: 'uppercase',
|
|
269
281
|
lineHeight: '17px',
|
|
270
282
|
color: topRightColor,
|
|
271
|
-
background:
|
|
283
|
+
background: nodeBg,
|
|
272
284
|
border: `2px solid ${topRightColor}`,
|
|
273
285
|
borderRadius: badgeRadius,
|
|
274
286
|
padding: '2px 8px',
|
|
@@ -280,41 +292,6 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
280
292
|
</div>
|
|
281
293
|
)}
|
|
282
294
|
|
|
283
|
-
{/* Filename badge — a strip across the bottom border, shown on hover
|
|
284
|
-
and pinned while this node's file is open in the drawer (fileMatch).
|
|
285
|
-
Mirrors the construct/role tab badges; hidden for fileless nodes. */}
|
|
286
|
-
{(hover || fileMatch === true) && c.file ? (
|
|
287
|
-
<div
|
|
288
|
-
style={{
|
|
289
|
-
position: 'absolute',
|
|
290
|
-
bottom: -14,
|
|
291
|
-
left: '50%',
|
|
292
|
-
transform: 'translateX(-50%)',
|
|
293
|
-
zIndex: 1,
|
|
294
|
-
fontFamily: theme.fonts.monospace,
|
|
295
|
-
fontSize: theme.fontSizes[1],
|
|
296
|
-
letterSpacing: 0.5,
|
|
297
|
-
lineHeight: '17px',
|
|
298
|
-
whiteSpace: 'nowrap',
|
|
299
|
-
// filename stays neutral — the construct color belongs to the
|
|
300
|
-
// borders and badges, not the file name
|
|
301
|
-
color: theme.colors.text ?? theme.colors.textSecondary,
|
|
302
|
-
background: theme.colors.backgroundSecondary ?? theme.colors.background,
|
|
303
|
-
border: `2px solid ${color}`,
|
|
304
|
-
borderRadius: badgeRadius,
|
|
305
|
-
padding: '2px 8px',
|
|
306
|
-
cursor: 'pointer',
|
|
307
|
-
}}
|
|
308
|
-
onClick={(e) => {
|
|
309
|
-
// Open the file directly; don't also toggle node selection.
|
|
310
|
-
e.stopPropagation();
|
|
311
|
-
callbacks.onOpenFile?.(c.alias);
|
|
312
|
-
}}
|
|
313
|
-
>
|
|
314
|
-
{c.file.split('/').pop()}
|
|
315
|
-
</div>
|
|
316
|
-
) : null}
|
|
317
|
-
|
|
318
295
|
{/* Purpose is no longer shown as a hover tooltip below the node — it
|
|
319
296
|
lives in the declaration panel and the graphify detail payload. */}
|
|
320
297
|
|
|
@@ -454,12 +431,13 @@ export function SubsystemGroupNode(props: NodeProps<Node<SubsystemGroupNodeData,
|
|
|
454
431
|
} : undefined}
|
|
455
432
|
style={{
|
|
456
433
|
position: 'absolute',
|
|
457
|
-
// Process:
|
|
458
|
-
// frame border)
|
|
459
|
-
// Module/package are centred astride the
|
|
460
|
-
// badges. Always set left/transform
|
|
461
|
-
// group DOM nodes and `undefined`
|
|
462
|
-
|
|
434
|
+
// Process: the tab sits flush over the frame's top border (`top:
|
|
435
|
+
// -2` — the frame border is 2px) and carries its own full border, so
|
|
436
|
+
// the two read as one piece. Module/package are centred astride the
|
|
437
|
+
// top edge like component badges. Always set left/transform
|
|
438
|
+
// explicitly — React Flow reuses group DOM nodes and `undefined`
|
|
439
|
+
// does not clear a prior value.
|
|
440
|
+
top: isProcess ? -2 : 0,
|
|
463
441
|
left: isProcess ? '50%' : 12,
|
|
464
442
|
transform: isProcess ? 'translateX(-50%)' : 'translateY(-50%)',
|
|
465
443
|
zIndex: canToggle ? 10 : undefined,
|
|
@@ -475,7 +453,11 @@ export function SubsystemGroupNode(props: NodeProps<Node<SubsystemGroupNodeData,
|
|
|
475
453
|
cursor: canToggle ? 'pointer' : undefined,
|
|
476
454
|
// The trailing halo squares the rounded corners back off in the fill
|
|
477
455
|
// colour, so an edge crossing a corner can't show through the notch.
|
|
478
|
-
|
|
456
|
+
// Square (process) badges have no notch, and the halo would erase the
|
|
457
|
+
// frame border they now sit flush on, so they skip it.
|
|
458
|
+
boxShadow: isProcess
|
|
459
|
+
? undefined
|
|
460
|
+
: `${canToggle && hover ? `0 2px 10px ${color}55, ` : ''}0 0 0 1.5px ${badgeBg}`,
|
|
479
461
|
fontFamily: theme.fonts.monospace,
|
|
480
462
|
fontSize: theme.fontSizes[3],
|
|
481
463
|
fontWeight: 700,
|
|
@@ -483,7 +465,7 @@ export function SubsystemGroupNode(props: NodeProps<Node<SubsystemGroupNodeData,
|
|
|
483
465
|
color,
|
|
484
466
|
background: badgeBg,
|
|
485
467
|
border: `2px solid ${color}`,
|
|
486
|
-
borderTopWidth:
|
|
468
|
+
borderTopWidth: 2,
|
|
487
469
|
borderRadius: badgeRadius,
|
|
488
470
|
padding: '3px 9px',
|
|
489
471
|
whiteSpace: 'nowrap',
|
package/src/utils/elkLayout.ts
CHANGED
|
@@ -642,7 +642,10 @@ export async function computeElkLayout(
|
|
|
642
642
|
const compoundLayoutOptions: LayoutOptions = {
|
|
643
643
|
'elk.algorithm': 'layered',
|
|
644
644
|
'elk.direction': direction,
|
|
645
|
-
|
|
645
|
+
// `top` is the header band a boundary badge sits in. It has to clear the
|
|
646
|
+
// badge (~28px tall, and a nested frame's badge can overhang its own top
|
|
647
|
+
// edge by ~13px) plus a gap before the first child.
|
|
648
|
+
'elk.padding': '[top=64,left=24,bottom=24,right=24]',
|
|
646
649
|
'elk.spacing.nodeNode': '40',
|
|
647
650
|
};
|
|
648
651
|
|