@principal-ai/principal-view-react 0.16.56 → 0.16.58
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 +16 -1
- package/dist/graphify/consolidated.d.ts.map +1 -1
- package/dist/graphify/index.d.ts +1 -1
- package/dist/graphify/index.d.ts.map +1 -1
- package/dist/graphify/index.js.map +1 -1
- package/dist/graphify/kind.d.ts +1 -1
- package/dist/graphify/kind.d.ts.map +1 -1
- package/dist/graphify/kind.js +7 -1
- package/dist/graphify/kind.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/pierre/constructColors.d.ts +24 -0
- package/dist/pierre/constructColors.d.ts.map +1 -0
- package/dist/pierre/constructColors.js +91 -0
- package/dist/pierre/constructColors.js.map +1 -0
- package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts +8 -0
- package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -0
- package/dist/stories/Subsystem/ComponentGraph/fixtures.js +116 -0
- package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -0
- package/dist/subsystem/ComponentDeclaration.d.ts +2 -2
- package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
- package/dist/subsystem/ComponentDeclaration.js +12 -11
- package/dist/subsystem/ComponentDeclaration.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts +10 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +401 -40
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/formatDeclaration.d.ts.map +1 -1
- package/dist/subsystem/formatDeclaration.js +49 -10
- package/dist/subsystem/formatDeclaration.js.map +1 -1
- package/dist/subsystem/model.d.ts +89 -12
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +55 -25
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts +32 -1
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +122 -24
- package/dist/subsystem/nodes.js.map +1 -1
- package/dist/subsystem/tokenizeComponent.js +1 -1
- package/dist/subsystem/tokenizeComponent.js.map +1 -1
- package/dist/utils/elkLayout.d.ts.map +1 -1
- package/dist/utils/elkLayout.js +4 -3
- package/dist/utils/elkLayout.js.map +1 -1
- package/package.json +1 -1
- package/src/graphify/consolidated.ts +18 -1
- package/src/graphify/index.ts +1 -0
- package/src/graphify/kind.ts +7 -1
- package/src/index.ts +3 -2
- package/src/pierre/constructColors.test.ts +52 -0
- package/src/pierre/constructColors.ts +112 -0
- package/src/stories/{BoundaryNodes.stories.tsx → Audit/BoundaryNodes.stories.tsx} +1 -1
- package/src/stories/{CanvasEdgeTypes.stories.tsx → Audit/CanvasEdgeTypes.stories.tsx} +1 -1
- package/src/stories/{CanvasNodeTypes.stories.tsx → Audit/CanvasNodeTypes.stories.tsx} +1 -1
- package/src/stories/{ColorPriority.stories.tsx → Audit/ColorPriority.stories.tsx} +1 -1
- package/src/stories/{DiamondBadges.stories.tsx → Audit/DiamondBadges.stories.tsx} +1 -1
- package/src/stories/{NodeDimensionsTesting.stories.tsx → Audit/NodeDimensionsTesting.stories.tsx} +2 -2
- package/src/stories/{NodeFieldsAudit.stories.tsx → Audit/NodeFieldsAudit.stories.tsx} +1 -1
- package/src/stories/{NodeFontSizeTesting.stories.tsx → Audit/NodeFontSizeTesting.stories.tsx} +2 -2
- package/src/stories/{NodeShapes.stories.tsx → Audit/NodeShapes.stories.tsx} +1 -1
- package/src/stories/{WideNodesTesting.stories.tsx → Audit/WideNodesTesting.stories.tsx} +2 -2
- package/src/stories/{dashboard → Components}/DashboardRenderer.stories.tsx +2 -2
- package/src/stories/{GraphRenderer.stories.tsx → Components/GraphRenderer.stories.tsx} +2 -2
- package/src/stories/{MultiCanvasRenderer.stories.tsx → Components/MultiCanvasRenderer.stories.tsx} +2 -2
- package/src/stories/{SequenceDiagram.stories.tsx → Components/SequenceDiagram.stories.tsx} +2 -2
- package/src/stories/{StateView.stories.tsx → Components/StateView.stories.tsx} +2 -2
- package/src/stories/{TraceViewer.stories.tsx → Components/TraceViewer.stories.tsx} +3 -3
- package/src/stories/{WorkflowSequenceDiagram.stories.tsx → Components/WorkflowSequenceDiagram.stories.tsx} +1 -1
- package/src/stories/{AnimationWorkshop.stories.tsx → Features/AnimationWorkshop.stories.tsx} +5 -5
- package/src/stories/{ChangeTypeVisual.stories.tsx → Features/ChangeTypeVisual.stories.tsx} +2 -2
- package/src/stories/{EventDrivenAnimations.stories.tsx → Features/EventDrivenAnimations.stories.tsx} +2 -2
- package/src/stories/{MultiConfig.stories.tsx → Features/MultiConfig.stories.tsx} +2 -2
- package/src/stories/{ElkEdgeRouting.stories.tsx → Layout/ElkEdgeRouting.stories.tsx} +2 -2
- package/src/stories/{GraphOrientation.stories.tsx → Layout/GraphOrientation.stories.tsx} +3 -3
- package/src/stories/{MultiDirectionalConnections.stories.tsx → Layout/MultiDirectionalConnections.stories.tsx} +2 -2
- package/src/stories/{NodeDefinitionComparison.stories.tsx → Nodes/NodeDefinitionComparison.stories.tsx} +2 -2
- package/src/stories/{NodeNameFallbacks.stories.tsx → Nodes/NodeNameFallbacks.stories.tsx} +2 -2
- package/src/stories/{NumericColors.stories.tsx → Nodes/NumericColors.stories.tsx} +2 -2
- package/src/stories/{EventNameDisplay.stories.tsx → OTEL/EventNameDisplay.stories.tsx} +2 -2
- package/src/stories/{EventNameFallback.stories.tsx → OTEL/EventNameFallback.stories.tsx} +2 -2
- package/src/stories/{OtelComponents.stories.tsx → OTEL/OtelComponents.stories.tsx} +3 -3
- package/src/stories/{OtelNodeTypesPrototype.stories.tsx → OTEL/OtelNodeTypesPrototype.stories.tsx} +2 -2
- package/src/stories/{RawEventCards.stories.tsx → OTEL/RawEventCards.stories.tsx} +3 -3
- package/src/stories/{SpanBadges.stories.tsx → OTEL/SpanBadges.stories.tsx} +1 -1
- package/src/stories/{ValidationEventsCanvas.stories.tsx → OTEL/ValidationEventsCanvas.stories.tsx} +3 -3
- package/src/stories/{BacklogMdCanvases.stories.tsx → RealWorld/BacklogMdCanvases.stories.tsx} +2 -2
- package/src/stories/{FileCitySequence.stories.tsx → RealWorld/FileCitySequence.stories.tsx} +4 -4
- package/src/stories/{RealCanvasFiles.stories.tsx → RealWorld/RealCanvasFiles.stories.tsx} +7 -7
- package/src/stories/{ComponentDeclarationAudit.stories.tsx → Subsystem/ComponentDeclarationAudit.stories.tsx} +19 -20
- package/src/stories/Subsystem/ComponentGraph/Appearance.stories.tsx +365 -0
- package/src/stories/Subsystem/ComponentGraph/Basics.stories.tsx +137 -0
- package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +238 -0
- package/src/stories/Subsystem/ComponentGraph/DetailPanel.stories.tsx +315 -0
- package/src/stories/Subsystem/ComponentGraph/Flows.stories.tsx +181 -0
- package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +959 -0
- package/src/stories/Subsystem/ComponentGraph/Spotlights.stories.tsx +646 -0
- package/src/stories/Subsystem/ComponentGraph/fixtures.ts +129 -0
- package/src/subsystem/ComponentDeclaration.tsx +13 -13
- package/src/subsystem/SubsystemComponentGraph.tsx +558 -44
- package/src/subsystem/formatDeclaration.ts +57 -11
- package/src/subsystem/model.test.ts +38 -6
- package/src/subsystem/model.ts +145 -32
- package/src/subsystem/nodes.test.ts +56 -0
- package/src/subsystem/nodes.tsx +161 -31
- package/src/subsystem/tokenizeComponent.ts +1 -1
- package/src/utils/elkLayout.ts +4 -3
- package/src/stories/SubsystemComponentGraph.stories.tsx +0 -1225
- /package/src/stories/{dashboard/sample-dashboards → data}/activity-feed-analytics.dashboard.json +0 -0
package/src/subsystem/nodes.tsx
CHANGED
|
@@ -16,18 +16,26 @@ import {
|
|
|
16
16
|
} from '@xyflow/react';
|
|
17
17
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
18
18
|
import {
|
|
19
|
-
KIND_COLOR,
|
|
20
19
|
MECHANISM_COLOR,
|
|
20
|
+
MECHANISM_STYLE,
|
|
21
|
+
ROLE_COLOR,
|
|
22
|
+
ROLE_LABEL,
|
|
21
23
|
deriveNameFromSymbol,
|
|
22
24
|
type SubsystemGraphNode,
|
|
23
25
|
type SubsystemGraphEdge,
|
|
24
26
|
} from './model';
|
|
27
|
+
import { constructColorsFromPierreTheme } from '../pierre/constructColors';
|
|
28
|
+
import { resolvePierreSyntaxThemeName } from '../pierre/pierreSyntaxTheme';
|
|
25
29
|
|
|
26
|
-
export const
|
|
30
|
+
export const CONSTRUCT_LABEL: Record<string, string> = {
|
|
27
31
|
class: 'class',
|
|
28
32
|
function: 'function',
|
|
29
|
-
|
|
33
|
+
method: 'method',
|
|
34
|
+
interface: 'interface',
|
|
35
|
+
type_alias: 'type alias',
|
|
36
|
+
enum: 'enum',
|
|
30
37
|
module: 'module',
|
|
38
|
+
store: 'store',
|
|
31
39
|
external: 'external',
|
|
32
40
|
};
|
|
33
41
|
|
|
@@ -48,6 +56,9 @@ function breakWords(s: string): string {
|
|
|
48
56
|
export interface SubsystemGraphCallbacks {
|
|
49
57
|
/** Click a component — open its file/entry point. */
|
|
50
58
|
onSelect?: (componentId: string) => void;
|
|
59
|
+
/** Click the filename badge — open that component's file in the drawer
|
|
60
|
+
* (same path as clicking the file link in the declaration panel). */
|
|
61
|
+
onOpenFile?: (componentId: string) => void;
|
|
51
62
|
/** Click an edge (or its label) — select the relationship. */
|
|
52
63
|
onEdgeSelect?: (edgeId: string) => void;
|
|
53
64
|
/** Hover a component (null on leave) — associates it with the file tree. */
|
|
@@ -60,15 +71,19 @@ export interface SubsystemGraphCallbacks {
|
|
|
60
71
|
export const SUBSYSTEM_CALLBACKS: SubsystemGraphCallbacks = {};
|
|
61
72
|
|
|
62
73
|
export function SubsystemComponentNode(props: NodeProps<SubsystemGraphNode>) {
|
|
63
|
-
const { theme } = useTheme();
|
|
74
|
+
const { theme, mode } = useTheme();
|
|
64
75
|
const { data, selected, width: nodeWidth, height: nodeHeight } = props;
|
|
65
76
|
const c = data.component;
|
|
66
|
-
|
|
77
|
+
// Construct owns node color, derived from the active Pierre syntax theme —
|
|
78
|
+
// the same palette the declaration panel and file drawer render with. Role
|
|
79
|
+
// shows as the hover badge, not as color — for now; a role glyph/accent may
|
|
80
|
+
// come later.
|
|
81
|
+
const color = constructColorsFromPierreTheme(resolvePierreSyntaxThemeName(mode))[c.construct];
|
|
67
82
|
const [hover, setHover] = useState(false);
|
|
68
83
|
const configuredMax = SUBSYSTEM_CALLBACKS.maxNodeWidth;
|
|
69
84
|
const maxWidth = configuredMax ?? 300;
|
|
70
85
|
// `symbol` is the source of truth; `name` is derived from it consistently.
|
|
71
|
-
const displayName = deriveNameFromSymbol(c.symbol, c.
|
|
86
|
+
const displayName = deriveNameFromSymbol(c.symbol, c.construct, c.name, c.file);
|
|
72
87
|
// Set while a file is open in the drawer: true → spotlight, false → dim,
|
|
73
88
|
// absent (no file open) → neutral.
|
|
74
89
|
const fileMatch = data.fileMatch as boolean | undefined;
|
|
@@ -111,42 +126,97 @@ export function SubsystemComponentNode(props: NodeProps<SubsystemGraphNode>) {
|
|
|
111
126
|
boxShadow: fileMatch
|
|
112
127
|
? `0 1px 4px rgba(0,0,0,0.25), 0 0 12px ${theme.colors.primary}55`
|
|
113
128
|
: '0 1px 4px rgba(0,0,0,0.25)',
|
|
114
|
-
opacity: fileMatch === false ? 0.18 : 1,
|
|
129
|
+
opacity: fileMatch === false || data.dimmed === true ? 0.18 : 1,
|
|
115
130
|
transition: 'opacity 150ms ease',
|
|
116
131
|
cursor: 'pointer',
|
|
117
132
|
fontFamily: theme.fonts.body,
|
|
118
133
|
}}
|
|
119
134
|
>
|
|
120
|
-
{/*
|
|
121
|
-
|
|
135
|
+
{/* Construct badge — a small tab riding the top-right border, in the
|
|
136
|
+
construct color. Persistent (no hover needed); pointer-events none so
|
|
137
|
+
clicks pass through to the node. */}
|
|
138
|
+
<div
|
|
139
|
+
style={{
|
|
140
|
+
position: 'absolute',
|
|
141
|
+
top: -9,
|
|
142
|
+
left: 10,
|
|
143
|
+
zIndex: 1,
|
|
144
|
+
fontFamily: theme.fonts.monospace,
|
|
145
|
+
fontSize: theme.fontSizes[0] * 1.1,
|
|
146
|
+
letterSpacing: 0.5,
|
|
147
|
+
textTransform: 'uppercase',
|
|
148
|
+
lineHeight: '17px',
|
|
149
|
+
color,
|
|
150
|
+
background: theme.colors.backgroundSecondary ?? theme.colors.background,
|
|
151
|
+
border: `1px solid ${color}`,
|
|
152
|
+
borderRadius: 4,
|
|
153
|
+
padding: '0 5px',
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
{CONSTRUCT_LABEL[c.construct] ?? c.construct}
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
{/* Role badge — top-right, only when the node carries a topology role.
|
|
160
|
+
Role color (entry orange / service blue) on the badge; the node
|
|
161
|
+
border stays construct-colored. */}
|
|
162
|
+
{c.role != null && (
|
|
122
163
|
<div
|
|
123
164
|
style={{
|
|
124
165
|
position: 'absolute',
|
|
125
|
-
top: -
|
|
126
|
-
|
|
127
|
-
zIndex:
|
|
128
|
-
display: 'flex',
|
|
129
|
-
alignItems: 'center',
|
|
130
|
-
gap: 6,
|
|
131
|
-
whiteSpace: 'nowrap',
|
|
132
|
-
fontSize: theme.fontSizes[0] * 0.8,
|
|
166
|
+
top: -9,
|
|
167
|
+
right: 10,
|
|
168
|
+
zIndex: 1,
|
|
133
169
|
fontFamily: theme.fonts.monospace,
|
|
170
|
+
fontSize: theme.fontSizes[0] * 1.1,
|
|
134
171
|
letterSpacing: 0.5,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
172
|
+
textTransform: 'uppercase',
|
|
173
|
+
lineHeight: '17px',
|
|
174
|
+
color: ROLE_COLOR[c.role],
|
|
175
|
+
background: theme.colors.backgroundSecondary ?? theme.colors.background,
|
|
176
|
+
border: `1px solid ${ROLE_COLOR[c.role]}`,
|
|
138
177
|
borderRadius: 4,
|
|
139
|
-
padding: '
|
|
178
|
+
padding: '0 5px',
|
|
140
179
|
}}
|
|
141
180
|
>
|
|
142
|
-
|
|
143
|
-
{KIND_LABEL[c.kind] ?? c.kind}
|
|
144
|
-
</span>
|
|
145
|
-
{c.file ? ` · ${c.file.split('/').pop()}` : ''}
|
|
146
|
-
{c.capture && c.capture !== 'edited' ? ` · ${c.capture}` : ''}
|
|
181
|
+
{ROLE_LABEL[c.role]}
|
|
147
182
|
</div>
|
|
148
183
|
)}
|
|
149
184
|
|
|
185
|
+
{/* Filename badge — a strip across the bottom border, shown on hover
|
|
186
|
+
and pinned while this node's file is open in the drawer (fileMatch).
|
|
187
|
+
Mirrors the construct/role tab badges; hidden for fileless nodes. */}
|
|
188
|
+
{(hover || fileMatch === true) && c.file ? (
|
|
189
|
+
<div
|
|
190
|
+
style={{
|
|
191
|
+
position: 'absolute',
|
|
192
|
+
bottom: -13,
|
|
193
|
+
left: '50%',
|
|
194
|
+
transform: 'translateX(-50%)',
|
|
195
|
+
zIndex: 1,
|
|
196
|
+
fontFamily: theme.fonts.monospace,
|
|
197
|
+
fontSize: theme.fontSizes[0] * 1.1,
|
|
198
|
+
letterSpacing: 0.5,
|
|
199
|
+
lineHeight: '17px',
|
|
200
|
+
whiteSpace: 'nowrap',
|
|
201
|
+
// filename stays neutral — the construct color belongs to the
|
|
202
|
+
// borders and badges, not the file name
|
|
203
|
+
color: theme.colors.text ?? theme.colors.textSecondary,
|
|
204
|
+
background: theme.colors.backgroundSecondary ?? theme.colors.background,
|
|
205
|
+
border: `1px solid ${color}`,
|
|
206
|
+
borderRadius: 4,
|
|
207
|
+
padding: '0 5px',
|
|
208
|
+
cursor: 'pointer',
|
|
209
|
+
}}
|
|
210
|
+
onClick={(e) => {
|
|
211
|
+
// Open the file directly; don't also toggle node selection.
|
|
212
|
+
e.stopPropagation();
|
|
213
|
+
SUBSYSTEM_CALLBACKS.onOpenFile?.(c.id);
|
|
214
|
+
}}
|
|
215
|
+
>
|
|
216
|
+
{c.file.split('/').pop()}
|
|
217
|
+
</div>
|
|
218
|
+
) : null}
|
|
219
|
+
|
|
150
220
|
{/* Purpose is no longer shown as a hover tooltip below the node — it
|
|
151
221
|
lives in the declaration panel and the graphify detail payload. */}
|
|
152
222
|
|
|
@@ -168,7 +238,11 @@ export function SubsystemComponentNode(props: NodeProps<SubsystemGraphNode>) {
|
|
|
168
238
|
{breakWords(displayName)}
|
|
169
239
|
</div>
|
|
170
240
|
|
|
171
|
-
{
|
|
241
|
+
{/* Hide the identity line when the symbol is just the title without its
|
|
242
|
+
decoration (`()` or ` {}`) — only show it when it adds information
|
|
243
|
+
(e.g. the dotted host on methods, or a different code identity). */}
|
|
244
|
+
{c.symbol &&
|
|
245
|
+
c.symbol !== displayName.replace(/ ?\{\}$/, '').replace(/\(\)$/, '') && (
|
|
172
246
|
<div
|
|
173
247
|
style={{
|
|
174
248
|
fontSize: theme.fontSizes[0] * 0.82,
|
|
@@ -193,6 +267,58 @@ export function SubsystemComponentNode(props: NodeProps<SubsystemGraphNode>) {
|
|
|
193
267
|
);
|
|
194
268
|
}
|
|
195
269
|
|
|
270
|
+
/** `#rrggbb` + alpha → `#rrggbbaa`. Used to dim a stroke/marker by color so
|
|
271
|
+
* each opacity gets its own SVG marker id — path `opacity` leaks across every
|
|
272
|
+
* edge that shares a `url(#marker)` (the focused edge's arrowhead dims). */
|
|
273
|
+
export function hexWithAlpha(hex: string, alpha: number): string {
|
|
274
|
+
const raw = hex.replace('#', '');
|
|
275
|
+
const full = raw.length === 3 ? [...raw].map((c) => c + c).join('') : raw;
|
|
276
|
+
if (!/^[0-9a-fA-F]{6}$/.test(full)) return hex;
|
|
277
|
+
const a = Math.round(Math.min(1, Math.max(0, alpha)) * 255)
|
|
278
|
+
.toString(16)
|
|
279
|
+
.padStart(2, '0');
|
|
280
|
+
return `#${full}${a}`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* File-open spotlight flag for a node. `true` = lives in the open file,
|
|
285
|
+
* `false` = dim, `undefined` = render neutrally.
|
|
286
|
+
*
|
|
287
|
+
* Focused-edge endpoints stay neutral when they don't live in the open file
|
|
288
|
+
* (the target of a focused edge must not dim with the rest).
|
|
289
|
+
*/
|
|
290
|
+
export function fileMatchForNode(
|
|
291
|
+
nodeFile: string | undefined,
|
|
292
|
+
openFile: string | null,
|
|
293
|
+
isFocusEndpoint: boolean,
|
|
294
|
+
): boolean | undefined {
|
|
295
|
+
if (!openFile) return undefined;
|
|
296
|
+
if (nodeFile === openFile) return true;
|
|
297
|
+
if (isFocusEndpoint) return undefined;
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Hide / dim a node or edge while flows are open.
|
|
303
|
+
* - not in any opened flow → hidden
|
|
304
|
+
* - in an opened flow, but not the selected flow/step → dimmed
|
|
305
|
+
* - in the selected flow or step (or opened with nothing selected) → full
|
|
306
|
+
*/
|
|
307
|
+
export function flowElementVisibility(opts: {
|
|
308
|
+
inOpened: boolean;
|
|
309
|
+
inSelected: boolean;
|
|
310
|
+
anyOpened: boolean;
|
|
311
|
+
anySelected: boolean;
|
|
312
|
+
}): { hidden: boolean; dimmed: boolean } {
|
|
313
|
+
const { inOpened, inSelected, anyOpened, anySelected } = opts;
|
|
314
|
+
if (!anyOpened && !anySelected) return { hidden: false, dimmed: false };
|
|
315
|
+
if (!inOpened && !inSelected) return { hidden: true, dimmed: false };
|
|
316
|
+
if (anySelected && !inSelected) return { hidden: false, dimmed: true };
|
|
317
|
+
return { hidden: false, dimmed: false };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export const EDGE_DIM_ALPHA = 0.15;
|
|
321
|
+
|
|
196
322
|
/** Subsystem edge — SVG path only. The mechanism label is rendered as an
|
|
197
323
|
* absolutely-positioned HTML overlay OUTSIDE the ReactFlow tree (by the
|
|
198
324
|
* parent Inner component) so it sits above the pane and receives pointer
|
|
@@ -204,9 +330,14 @@ export function SubsystemEdge({
|
|
|
204
330
|
const path = data?.elkPath ?? '';
|
|
205
331
|
const mechanism = data?.mechanism ?? 'imports';
|
|
206
332
|
const color = MECHANISM_COLOR[mechanism] ?? '#888';
|
|
207
|
-
|
|
333
|
+
// Dash style comes from the mechanism table (dashed = inverted-control or
|
|
334
|
+
// observational relationships: hierarchy, registration, watches).
|
|
335
|
+
const isDashed = MECHANISM_STYLE[mechanism] === 'dashed';
|
|
208
336
|
const dimmed = data?.dimmed === true;
|
|
209
|
-
|
|
337
|
+
// Dim the stroke by color, never via path `opacity`. SVG markers are shared
|
|
338
|
+
// by id; opacity on the referencing path paints every arrowhead that uses
|
|
339
|
+
// the same marker — including the focused edge's target.
|
|
340
|
+
const stroke = dimmed ? hexWithAlpha(color, EDGE_DIM_ALPHA) : color;
|
|
210
341
|
|
|
211
342
|
return (
|
|
212
343
|
<>
|
|
@@ -225,10 +356,9 @@ export function SubsystemEdge({
|
|
|
225
356
|
<path
|
|
226
357
|
d={path}
|
|
227
358
|
fill="none"
|
|
228
|
-
stroke={
|
|
359
|
+
stroke={stroke}
|
|
229
360
|
strokeWidth={1.5}
|
|
230
361
|
strokeDasharray={isDashed ? '6 4' : undefined}
|
|
231
|
-
opacity={opacity}
|
|
232
362
|
markerEnd={markerEnd}
|
|
233
363
|
style={{ pointerEvents: 'none' }}
|
|
234
364
|
/>
|
|
@@ -50,7 +50,7 @@ export async function tokenizeComponent(
|
|
|
50
50
|
if (component.tokens) return component.tokens;
|
|
51
51
|
|
|
52
52
|
// External kind — not valid TypeScript, bypass Prettier.
|
|
53
|
-
const kind = component.detail?.kind ?? component.
|
|
53
|
+
const kind = component.detail?.kind ?? component.construct;
|
|
54
54
|
if (kind === 'external') {
|
|
55
55
|
const label = component.detail?.kind === 'external' ? component.detail.label : component.name;
|
|
56
56
|
const escaped = label.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
package/src/utils/elkLayout.ts
CHANGED
|
@@ -529,9 +529,10 @@ export async function computeElkLayout(
|
|
|
529
529
|
// and apply the same coordinate offset.
|
|
530
530
|
if (edge.labels && edge.labels.length > 0) {
|
|
531
531
|
const elkLabel = edge.labels[0];
|
|
532
|
-
//
|
|
533
|
-
|
|
534
|
-
let
|
|
532
|
+
// ELK reports the label's top-left; convert to center so screen-space
|
|
533
|
+
// overlays can anchor with translate(-50%, -50%) at any zoom.
|
|
534
|
+
let lx = (elkLabel.x ?? 0) + (elkLabel.width ?? 0) / 2;
|
|
535
|
+
let ly = (elkLabel.y ?? 0) + (elkLabel.height ?? 0) / 2;
|
|
535
536
|
if (preserveNodePositions && sourceOriginal && sourceElk && targetOriginal && targetElk) {
|
|
536
537
|
const sourceOffset = {
|
|
537
538
|
x: sourceOriginal.x - sourceElk.x,
|