@principal-ai/subsystems-react 0.51.3 → 0.52.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/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/subsystem/C4Graph.d.ts +3 -7
- package/dist/subsystem/C4Graph.d.ts.map +1 -1
- package/dist/subsystem/C4Graph.js +202 -63
- package/dist/subsystem/C4Graph.js.map +1 -1
- package/dist/subsystem/C4NodeCard.d.ts +26 -26
- package/dist/subsystem/C4NodeCard.d.ts.map +1 -1
- package/dist/subsystem/C4NodeCard.js +56 -30
- package/dist/subsystem/C4NodeCard.js.map +1 -1
- package/dist/subsystem/IssueList.d.ts +1 -1
- package/dist/subsystem/IssueList.d.ts.map +1 -1
- package/dist/subsystem/IssueList.js +20 -1
- package/dist/subsystem/IssueList.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +74 -26
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/c4.d.ts +48 -49
- package/dist/subsystem/c4.d.ts.map +1 -1
- package/dist/subsystem/c4.js +50 -0
- package/dist/subsystem/c4.js.map +1 -1
- package/dist/subsystem/c4Evidence.d.ts +88 -84
- package/dist/subsystem/c4Evidence.d.ts.map +1 -1
- package/dist/subsystem/c4Evidence.js +113 -166
- package/dist/subsystem/c4Evidence.js.map +1 -1
- package/dist/subsystem/model.d.ts +6 -0
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.d.ts.map +1 -1
- package/dist/subsystem/nodes.js +9 -7
- package/dist/subsystem/nodes.js.map +1 -1
- package/dist/subsystem/techIcons/index.d.ts +5 -0
- package/dist/subsystem/techIcons/index.d.ts.map +1 -1
- package/dist/subsystem/techIcons/index.js +3 -0
- package/dist/subsystem/techIcons/index.js.map +1 -1
- package/dist/utils/edgeLabel.d.ts +14 -0
- package/dist/utils/edgeLabel.d.ts.map +1 -1
- package/dist/utils/edgeLabel.js +24 -0
- package/dist/utils/edgeLabel.js.map +1 -1
- package/dist/utils/elkLayout.d.ts +65 -0
- package/dist/utils/elkLayout.d.ts.map +1 -1
- package/dist/utils/elkLayout.js +145 -11
- package/dist/utils/elkLayout.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +8 -6
- package/src/stories/Subsystem/C4Graph/C4Component.stories.tsx +198 -0
- package/src/stories/Subsystem/C4Graph/C4Container.stories.tsx +42 -6
- package/src/stories/Subsystem/C4Graph/C4NodeGallery.stories.tsx +9 -7
- package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +686 -2
- package/src/stories/Subsystem/ComponentGraph/ProposedTrails.stories.tsx +21 -1
- package/src/subsystem/C4Graph.tsx +296 -99
- package/src/subsystem/C4NodeCard.test.ts +32 -22
- package/src/subsystem/C4NodeCard.tsx +67 -39
- package/src/subsystem/IssueList.tsx +23 -0
- package/src/subsystem/SubsystemComponentGraph.tsx +79 -23
- package/src/subsystem/c4.protocolColor.test.ts +26 -0
- package/src/subsystem/c4.ts +76 -52
- package/src/subsystem/c4Evidence.test.ts +177 -237
- package/src/subsystem/c4Evidence.ts +160 -235
- package/src/subsystem/model.ts +6 -0
- package/src/subsystem/nodes.group.test.tsx +15 -0
- package/src/subsystem/nodes.tsx +12 -9
- package/src/subsystem/techIcons/index.tsx +8 -0
- package/src/utils/edgeLabel.test.ts +21 -2
- package/src/utils/edgeLabel.ts +28 -0
- package/src/utils/elkLayout.test.ts +50 -0
- package/src/utils/elkLayout.ts +191 -11
- package/src/stories/Subsystem/ComponentGraph/IssueOverlay.stories.tsx +0 -602
|
@@ -20,7 +20,7 @@ import { ReactFlow, ReactFlowProvider, useReactFlow, useViewport, applyNodeChang
|
|
|
20
20
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
21
21
|
import { FileText, X } from 'lucide-react';
|
|
22
22
|
import { IndustryMarkdownSlide } from 'themed-markdown';
|
|
23
|
-
import { buildSubsystemGraph, deriveGraphEdges, isConstructsOnlyModel, moduleGroupNodeId, isTrailMechanism, edgeColor, MECHANISM_DESCRIPTIONS, subsystemGraphLayoutKey, trailStepGraphEdgeId, } from './model';
|
|
23
|
+
import { buildSubsystemGraph, deriveGraphEdges, isConstructsOnlyModel, moduleGroupNodeId, processGroupNodeId, isTrailMechanism, edgeColor, MECHANISM_DESCRIPTIONS, subsystemGraphLayoutKey, trailStepGraphEdgeId, } from './model';
|
|
24
24
|
import { ConstructsCatalog } from './ConstructsCatalog';
|
|
25
25
|
import { SubsystemAgentsPanel, } from './AgentsPanel';
|
|
26
26
|
import { MaintainLivePanel, } from '../components/maintain-events/MaintainLivePanel';
|
|
@@ -654,17 +654,25 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
654
654
|
c.name === target.label ||
|
|
655
655
|
c.symbol === target.label) ?? null);
|
|
656
656
|
}, [components]);
|
|
657
|
-
// A `module` issue target names a boundary frame, so focusing
|
|
658
|
-
// that region on the canvas. Null when the target names no
|
|
659
|
-
// dropped from the layout, or a region key nothing
|
|
660
|
-
|
|
657
|
+
// A `module` / `process` issue target names a boundary frame, so focusing
|
|
658
|
+
// the card frames that region on the canvas. Null when the target names no
|
|
659
|
+
// frame (a singleton dropped from the layout, or a region key nothing
|
|
660
|
+
// declares).
|
|
661
|
+
const issueFrameNodeId = useCallback((issue) => {
|
|
661
662
|
const target = issue.target;
|
|
662
|
-
if (target?.kind
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
return
|
|
667
|
-
|
|
663
|
+
if (target?.kind === 'module') {
|
|
664
|
+
const key = target.id ?? target.label;
|
|
665
|
+
if (!components.some((c) => c.module?.trim() === key))
|
|
666
|
+
return null;
|
|
667
|
+
return moduleGroupNodeId(key);
|
|
668
|
+
}
|
|
669
|
+
if (target?.kind === 'process') {
|
|
670
|
+
const key = target.id ?? target.label;
|
|
671
|
+
if (!components.some((c) => c.process === key))
|
|
672
|
+
return null;
|
|
673
|
+
return processGroupNodeId(key);
|
|
674
|
+
}
|
|
675
|
+
return null;
|
|
668
676
|
}, [components]);
|
|
669
677
|
// A `step` issue target names ONE step of a trail — the trail id in
|
|
670
678
|
// `id`, the 0-based step position in `stepIndex`. Validated against the loaded
|
|
@@ -709,7 +717,9 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
709
717
|
return map;
|
|
710
718
|
}, [issues, issueComponent]);
|
|
711
719
|
// Per-frame diagnostics badge: the same fold, but for findings about a
|
|
712
|
-
// BOUNDARY rather than a construct. Keyed by the region's React Flow node
|
|
720
|
+
// BOUNDARY rather than a construct. Keyed by the region's React Flow node
|
|
721
|
+
// id — a `module` target names a module frame, a `process` target a process
|
|
722
|
+
// frame (dynamic-topology findings, e.g. C4 process verification).
|
|
713
723
|
// Only kinds with a dedicated frame icon qualify (`ISSUE_KIND_ICON`) — a
|
|
714
724
|
// finding with no icon of its own has nothing to badge the frame with, and
|
|
715
725
|
// borrowing its layer's icon would imply the frame is at fault for it.
|
|
@@ -719,9 +729,13 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
719
729
|
return map;
|
|
720
730
|
for (const issue of issues) {
|
|
721
731
|
const target = issue.target;
|
|
722
|
-
if (target
|
|
732
|
+
if (!target || !ISSUE_KIND_ICON[issue.kind])
|
|
733
|
+
continue;
|
|
734
|
+
if (target.kind !== 'module' && target.kind !== 'process')
|
|
723
735
|
continue;
|
|
724
|
-
const id =
|
|
736
|
+
const id = target.kind === 'module'
|
|
737
|
+
? moduleGroupNodeId(target.id ?? target.label)
|
|
738
|
+
: processGroupNodeId(target.id ?? target.label);
|
|
725
739
|
const prev = map.get(id);
|
|
726
740
|
map.set(id, {
|
|
727
741
|
severity: prev?.severity === 'error' || issue.severity === 'error' ? 'error' : 'info',
|
|
@@ -762,6 +776,22 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
762
776
|
}
|
|
763
777
|
return map;
|
|
764
778
|
}, [issues, trails, issueStep]);
|
|
779
|
+
// Edges backed by a step that ITSELF declares `proposed` — a planned seam
|
|
780
|
+
// between otherwise-real components. Distinct from a proposed ENDPOINT
|
|
781
|
+
// (the node is the placeholder there, and draws its own dashed border):
|
|
782
|
+
// here the seam is the claim that is not real yet, so the edge label draws
|
|
783
|
+
// dashed. Proposed steps are never verified, so this never competes with
|
|
784
|
+
// the step-issue badge.
|
|
785
|
+
const proposedEdgeIds = useMemo(() => {
|
|
786
|
+
const set = new Set();
|
|
787
|
+
for (const trail of trails ?? []) {
|
|
788
|
+
for (const step of trail.steps) {
|
|
789
|
+
if (step.proposed)
|
|
790
|
+
set.add(trailStepGraphEdgeId(step));
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return set;
|
|
794
|
+
}, [trails]);
|
|
765
795
|
// Expanded verification layers in the diagnostics list. When any layer with
|
|
766
796
|
// findings is expanded, the canvas dims everything that layer does not
|
|
767
797
|
// implicate (see `issueFocus`). The list publishes this through
|
|
@@ -787,6 +817,7 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
787
817
|
// target:
|
|
788
818
|
// component → that node
|
|
789
819
|
// module → every component in that module
|
|
820
|
+
// process → every component with that `process` key
|
|
790
821
|
// trail → the flow's nodes + its step edges
|
|
791
822
|
// repo → every component of that repo
|
|
792
823
|
// graph → whole-graph finding; implicates nothing specific
|
|
@@ -822,6 +853,13 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
822
853
|
}
|
|
823
854
|
}
|
|
824
855
|
}
|
|
856
|
+
else if (t.kind === 'process') {
|
|
857
|
+
const key = t.id ?? t.label;
|
|
858
|
+
for (const c of components) {
|
|
859
|
+
if (c.process === key)
|
|
860
|
+
nodeIds.add(c.alias);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
825
863
|
else if (t.kind === 'trail') {
|
|
826
864
|
const wt = (trails ?? []).find((w) => w.id === t.id || w.title === t.label);
|
|
827
865
|
if (wt) {
|
|
@@ -1017,7 +1055,8 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
1017
1055
|
// Host override wins over the library's derived frame color.
|
|
1018
1056
|
const color = region?.key != null ? boundaryColors?.[region.key] : undefined;
|
|
1019
1057
|
// Boundary findings badge the FRAME, not a member — a region's shape is
|
|
1020
|
-
// the fault, so no leaf construct carries it.
|
|
1058
|
+
// the fault, so no leaf construct carries it. Process verification
|
|
1059
|
+
// findings ride the same path: their target kind is `process`.
|
|
1021
1060
|
const regionIssue = regionIssueByNodeId.get(n.id);
|
|
1022
1061
|
return {
|
|
1023
1062
|
...n,
|
|
@@ -1070,6 +1109,11 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
1070
1109
|
}, [xyflowNodesBase, openFile, selected, focusNodeIds, openedNodeIds, brightNodeIds, previewNodeIds, issueFocusNodeIds, issueBadgeByAlias, regionIssueByNodeId, boundaryColors]);
|
|
1071
1110
|
const baseNodesKey = useMemo(() => nodes.map((n) => n.id).sort().join(','), [nodes]);
|
|
1072
1111
|
const baseEdgesKey = useMemo(() => convertedEdges.map((e) => e.id).sort().join(','), [convertedEdges]);
|
|
1112
|
+
// Stamp `proposedStep` onto edge data so the SVG edge itself (not just its
|
|
1113
|
+
// label) draws dashed when a backing trail step declares itself proposed.
|
|
1114
|
+
const proposedStampedEdges = useMemo(() => baseEdges.map((e) => proposedEdgeIds.has(e.id)
|
|
1115
|
+
? { ...e, data: { ...e.data, proposedStep: true } }
|
|
1116
|
+
: e), [baseEdges, proposedEdgeIds]);
|
|
1073
1117
|
const dispEdges = useMemo(() => {
|
|
1074
1118
|
const paint = (e, dimmed) => {
|
|
1075
1119
|
const markerEnd = e.markerEnd;
|
|
@@ -1094,7 +1138,7 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
1094
1138
|
return isTrailMechanism(mechanism);
|
|
1095
1139
|
};
|
|
1096
1140
|
if (openedEdgeIds || focusEdgeIds || previewEdgeIds || issueFocusEdgeIds) {
|
|
1097
|
-
return
|
|
1141
|
+
return proposedStampedEdges.map((e) => {
|
|
1098
1142
|
const vis = flowElementVisibility({
|
|
1099
1143
|
inOpened: openedEdgeIds?.has(e.id) === true,
|
|
1100
1144
|
inSelected: focusEdgeIds?.has(e.id) === true,
|
|
@@ -1108,13 +1152,13 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
1108
1152
|
});
|
|
1109
1153
|
}
|
|
1110
1154
|
if (selectedEdgeId) {
|
|
1111
|
-
return
|
|
1155
|
+
return proposedStampedEdges.map((e) => ({
|
|
1112
1156
|
...paint(e, e.id !== selectedEdgeId),
|
|
1113
1157
|
hidden: !edgeInView(e),
|
|
1114
1158
|
}));
|
|
1115
1159
|
}
|
|
1116
|
-
return
|
|
1117
|
-
}, [
|
|
1160
|
+
return proposedStampedEdges.map((e) => ({ ...e, hidden: !edgeInView(e) }));
|
|
1161
|
+
}, [proposedStampedEdges, selectedEdgeId, openedEdgeIds, focusEdgeIds, previewEdgeIds, issueFocusEdgeIds, resolvedEdgeView]);
|
|
1118
1162
|
const onNodesChange = useCallback((changes) => {
|
|
1119
1163
|
// Capture dimension changes (React Flow's measurement callback).
|
|
1120
1164
|
// Group parents are ELK-sized — ignore their measurements.
|
|
@@ -1805,7 +1849,7 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
1805
1849
|
else {
|
|
1806
1850
|
// Nothing selectable — but a module target names a boundary frame, so
|
|
1807
1851
|
// frame that region.
|
|
1808
|
-
const moduleNodeId =
|
|
1852
|
+
const moduleNodeId = issueFrameNodeId(issue);
|
|
1809
1853
|
if (moduleNodeId) {
|
|
1810
1854
|
setSelected(null);
|
|
1811
1855
|
setSelectedEdgeId(null);
|
|
@@ -1822,7 +1866,7 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
1822
1866
|
}, [
|
|
1823
1867
|
issueComponent,
|
|
1824
1868
|
issueStep,
|
|
1825
|
-
|
|
1869
|
+
issueFrameNodeId,
|
|
1826
1870
|
focusTrailStep,
|
|
1827
1871
|
onSelect,
|
|
1828
1872
|
onSelectIssue,
|
|
@@ -1845,7 +1889,7 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
1845
1889
|
}
|
|
1846
1890
|
else {
|
|
1847
1891
|
const step = issueStep(issue);
|
|
1848
|
-
const moduleNodeId = step ? null :
|
|
1892
|
+
const moduleNodeId = step ? null : issueFrameNodeId(issue);
|
|
1849
1893
|
if (step) {
|
|
1850
1894
|
// Only unwind a step focus this card still owns. A dim-mode graph
|
|
1851
1895
|
// keeps its step focus in hover state instead (owned by the pointer,
|
|
@@ -1872,7 +1916,7 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
1872
1916
|
requestAnimationFrame(() => {
|
|
1873
1917
|
fitView({ padding: 0.1, duration: 300, minZoom: 0.05, maxZoom: 2 });
|
|
1874
1918
|
});
|
|
1875
|
-
}, [issueComponent, issueStep,
|
|
1919
|
+
}, [issueComponent, issueStep, issueFrameNodeId, fitView]);
|
|
1876
1920
|
// Construct tokens inside a trail snippet. A step's line is an edge
|
|
1877
1921
|
// between its `from`/`to` components, so a token naming either of those
|
|
1878
1922
|
// constructs should navigate to that construct's declaration. Index the
|
|
@@ -1931,6 +1975,7 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
1931
1975
|
.map((e) => {
|
|
1932
1976
|
const d = e.data;
|
|
1933
1977
|
const stepIssue = stepIssueBadgeByEdgeId.get(e.id);
|
|
1978
|
+
const proposed = proposedEdgeIds.has(e.id);
|
|
1934
1979
|
return {
|
|
1935
1980
|
id: e.id,
|
|
1936
1981
|
mechanism: d?.mechanism ?? 'uses',
|
|
@@ -1941,10 +1986,11 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
1941
1986
|
pathLength: d?.pathLength ?? 0,
|
|
1942
1987
|
stepNos: selectedFlowStepNos?.get(e.id),
|
|
1943
1988
|
stepIssue,
|
|
1989
|
+
proposed,
|
|
1944
1990
|
};
|
|
1945
1991
|
});
|
|
1946
1992
|
return labels;
|
|
1947
|
-
}, [dispEdges, selectedFlowStepNos, stepIssueBadgeByEdgeId]);
|
|
1993
|
+
}, [dispEdges, selectedFlowStepNos, stepIssueBadgeByEdgeId, proposedEdgeIds]);
|
|
1948
1994
|
// Unique source files across components → sidebar file trees.
|
|
1949
1995
|
const treeFilePaths = treeFiles;
|
|
1950
1996
|
// The hovered node's file (null when not hovering / file-less component).
|
|
@@ -2130,7 +2176,9 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
2130
2176
|
position: 'absolute',
|
|
2131
2177
|
inset: 0,
|
|
2132
2178
|
borderRadius: 4,
|
|
2133
|
-
border:
|
|
2179
|
+
border: lbl.proposed
|
|
2180
|
+
? `1.5px dashed ${color}`
|
|
2181
|
+
: `0.5px solid ${color}`,
|
|
2134
2182
|
background: theme.colors.backgroundSecondary ?? theme.colors.background,
|
|
2135
2183
|
pointerEvents: 'none',
|
|
2136
2184
|
} })) : (_jsx("svg", { "aria-hidden": true, width: EDGE_LABEL_WIDTH, height: EDGE_LABEL_HEIGHT + EDGE_LABEL_CLOUD_EXTRA_TOP, viewBox: `0 0 ${EDGE_LABEL_WIDTH} ${EDGE_LABEL_HEIGHT + EDGE_LABEL_CLOUD_EXTRA_TOP}`, style: {
|
|
@@ -2138,7 +2186,7 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
2138
2186
|
left: 0,
|
|
2139
2187
|
top: -EDGE_LABEL_CLOUD_EXTRA_TOP,
|
|
2140
2188
|
pointerEvents: 'none',
|
|
2141
|
-
}, children: _jsx("path", { d: EDGE_LABEL_CLOUD_PATH, fill: theme.colors.backgroundSecondary ?? theme.colors.background, stroke: hexWithAlpha(color, 0.75), strokeWidth: 1.2, strokeLinejoin: "round" }) })), _jsx("span", { style: {
|
|
2189
|
+
}, children: _jsx("path", { d: EDGE_LABEL_CLOUD_PATH, fill: theme.colors.backgroundSecondary ?? theme.colors.background, stroke: hexWithAlpha(color, 0.75), strokeWidth: 1.2, strokeLinejoin: "round", strokeDasharray: lbl.proposed ? '4 3' : undefined }) })), _jsx("span", { style: {
|
|
2142
2190
|
position: 'relative',
|
|
2143
2191
|
display: 'block',
|
|
2144
2192
|
maxWidth: '100%',
|