@principal-ai/subsystems-react 0.53.2 → 0.53.4
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 +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/subsystem/C4NodeCard.d.ts +45 -10
- package/dist/subsystem/C4NodeCard.d.ts.map +1 -1
- package/dist/subsystem/C4NodeCard.js +51 -20
- package/dist/subsystem/C4NodeCard.js.map +1 -1
- package/dist/subsystem/IssueList.d.ts +10 -1
- package/dist/subsystem/IssueList.d.ts.map +1 -1
- package/dist/subsystem/IssueList.js +13 -5
- package/dist/subsystem/IssueList.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts +8 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +26 -9
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/model.d.ts +13 -2
- 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 +14 -6
- package/dist/subsystem/nodes.js.map +1 -1
- package/dist/utils/elkLayout.d.ts.map +1 -1
- package/dist/utils/elkLayout.js +28 -6
- package/dist/utils/elkLayout.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/stories/Subsystem/C4Graph/C4NodeGallery.stories.tsx +1 -1
- package/src/stories/Subsystem/C4Graph/C4Transition.stories.tsx +74 -4
- package/src/subsystem/C4NodeCard.test.ts +47 -8
- package/src/subsystem/C4NodeCard.tsx +61 -20
- package/src/subsystem/IssueList.tsx +30 -5
- package/src/subsystem/SubsystemComponentGraph.tsx +36 -8
- package/src/subsystem/model.ts +13 -2
- package/src/subsystem/nodes.tsx +14 -6
- package/src/utils/elkLayout.ts +29 -6
|
@@ -74,6 +74,7 @@ import {
|
|
|
74
74
|
issueRung,
|
|
75
75
|
ISSUE_KIND_ICON,
|
|
76
76
|
ISSUE_RUNG_ORDER,
|
|
77
|
+
type SubsystemIssueListProps,
|
|
77
78
|
type SubsystemIssue,
|
|
78
79
|
type SubsystemIssueCategory,
|
|
79
80
|
} from './IssueList';
|
|
@@ -354,6 +355,13 @@ export interface SubsystemComponentGraphProps {
|
|
|
354
355
|
* plain issues view expands everything.
|
|
355
356
|
*/
|
|
356
357
|
focusIssueCategory?: SubsystemIssueCategory;
|
|
358
|
+
/**
|
|
359
|
+
* Per-layer verification status from the host's lane summary (the same
|
|
360
|
+
* derivation the maintainer's lane icons use). Forwarded to the issues
|
|
361
|
+
* list so a layer with nothing to verify renders muted ("not applicable")
|
|
362
|
+
* instead of a green check for a verification that never ran.
|
|
363
|
+
*/
|
|
364
|
+
laneStatus?: SubsystemIssueListProps['laneStatus'];
|
|
357
365
|
/**
|
|
358
366
|
* Click an issue. The graph first focuses the target on the canvas itself —
|
|
359
367
|
* a component target is selected and framed (any edge / trail focus
|
|
@@ -528,7 +536,7 @@ interface InnerProps extends SubsystemComponentGraphProps {
|
|
|
528
536
|
measured: { w: number; h: number } | null;
|
|
529
537
|
}
|
|
530
538
|
|
|
531
|
-
function Inner({ components, trails, graphifyRelations, orderByLine, initialTrailId, onReorderTrails, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, showSingletonFrames = true, moduleNesting, edgeView, title, hideSidebar, trailStepMode = 'focus', autoPlayTrails = false, trailAutoPlayIntervalMs = TRAIL_PLAY_PAUSE_MS, zoomOnTrailFocus = true, trailFocusDurationMs = 300, graphTitle, showTrailTitle = false, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, diagnostic, issues, showIssues, focusIssueCategory, onSelectIssue, onApplyIssueFix, onHoverIssue, renderFileViewer, renderTrailViewer, onFileSelect, componentVerification, onInspectSymbol, boundaryColors, hideDrawer = false, persistKey, liveEvents, agentsPanel }: InnerProps) {
|
|
539
|
+
function Inner({ components, trails, graphifyRelations, orderByLine, initialTrailId, onReorderTrails, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, showSingletonFrames = true, moduleNesting, edgeView, title, hideSidebar, trailStepMode = 'focus', autoPlayTrails = false, trailAutoPlayIntervalMs = TRAIL_PLAY_PAUSE_MS, zoomOnTrailFocus = true, trailFocusDurationMs = 300, graphTitle, showTrailTitle = false, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, diagnostic, issues, showIssues, focusIssueCategory, laneStatus, onSelectIssue, onApplyIssueFix, onHoverIssue, renderFileViewer, renderTrailViewer, onFileSelect, componentVerification, onInspectSymbol, boundaryColors, hideDrawer = false, persistKey, liveEvents, agentsPanel }: InnerProps) {
|
|
532
540
|
const { theme } = useTheme();
|
|
533
541
|
const { fitView, fitBounds, screenToFlowPosition } = useReactFlow();
|
|
534
542
|
const viewport = useViewport();
|
|
@@ -1140,15 +1148,18 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
1140
1148
|
[trails],
|
|
1141
1149
|
);
|
|
1142
1150
|
|
|
1143
|
-
// Per-node diagnostics badge: fold each component-targeted finding
|
|
1144
|
-
//
|
|
1145
|
-
//
|
|
1151
|
+
// Per-node diagnostics badge: fold each component-targeted finding into one
|
|
1152
|
+
// badge per node — worst severity wins, the chip shows the earliest failing
|
|
1153
|
+
// rung, and the count tallies the findings. A finding with no construct rung
|
|
1154
|
+
// still badges the node when it carries a dedicated kind icon (e.g. a C4
|
|
1155
|
+
// process gap): the chip wears the kind's icon instead of a rung's, so the
|
|
1156
|
+
// node says "diagnostics here" for every finding the sidebar lists.
|
|
1146
1157
|
const issueBadgeByAlias = useMemo(() => {
|
|
1147
1158
|
const map = new Map<string, SubsystemNodeIssue>();
|
|
1148
1159
|
if (!issues?.length) return map;
|
|
1149
1160
|
for (const issue of issues) {
|
|
1150
1161
|
const rung = issueRung(issue.kind);
|
|
1151
|
-
if (!rung) continue;
|
|
1162
|
+
if (!rung && !ISSUE_KIND_ICON[issue.kind]) continue;
|
|
1152
1163
|
const comp = issueComponent(issue);
|
|
1153
1164
|
if (!comp) continue;
|
|
1154
1165
|
const prev = map.get(comp.alias);
|
|
@@ -1158,12 +1169,27 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
1158
1169
|
? 'error'
|
|
1159
1170
|
: 'info',
|
|
1160
1171
|
rung:
|
|
1161
|
-
|
|
1162
|
-
? prev
|
|
1163
|
-
: rung
|
|
1172
|
+
rung == null
|
|
1173
|
+
? prev?.rung
|
|
1174
|
+
: prev?.rung != null &&
|
|
1175
|
+
ISSUE_RUNG_ORDER[prev.rung] <= ISSUE_RUNG_ORDER[rung]
|
|
1176
|
+
? prev.rung
|
|
1177
|
+
: rung,
|
|
1164
1178
|
count: (prev?.count ?? 0) + 1,
|
|
1165
1179
|
});
|
|
1166
1180
|
}
|
|
1181
|
+
// A rung finding wins the chip; only rung-less nodes wear a kind icon —
|
|
1182
|
+
// the first kind-iconed finding the sidebar lists for that node.
|
|
1183
|
+
for (const [alias, badge] of map) {
|
|
1184
|
+
if (badge.rung != null) continue;
|
|
1185
|
+
const kinded = issues.find(
|
|
1186
|
+
(i) =>
|
|
1187
|
+
!issueRung(i.kind) &&
|
|
1188
|
+
ISSUE_KIND_ICON[i.kind] &&
|
|
1189
|
+
issueComponent(i)?.alias === alias,
|
|
1190
|
+
);
|
|
1191
|
+
if (kinded) map.set(alias, { ...badge, kind: kinded.kind });
|
|
1192
|
+
}
|
|
1167
1193
|
return map;
|
|
1168
1194
|
}, [issues, issueComponent]);
|
|
1169
1195
|
|
|
@@ -2760,6 +2786,7 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
2760
2786
|
<SubsystemIssueList
|
|
2761
2787
|
issues={issues ?? []}
|
|
2762
2788
|
focusCategory={focusIssueCategory}
|
|
2789
|
+
laneStatus={laneStatus}
|
|
2763
2790
|
onSelectIssue={focusIssueTarget}
|
|
2764
2791
|
onDeselectIssue={unfocusIssueTarget}
|
|
2765
2792
|
onApplyFix={onApplyIssueFix}
|
|
@@ -2772,6 +2799,7 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
|
|
|
2772
2799
|
<SubsystemIssueList
|
|
2773
2800
|
issues={issues ?? []}
|
|
2774
2801
|
focusCategory={focusIssueCategory}
|
|
2802
|
+
laneStatus={laneStatus}
|
|
2775
2803
|
onSelectIssue={focusIssueTarget}
|
|
2776
2804
|
onDeselectIssue={unfocusIssueTarget}
|
|
2777
2805
|
onApplyFix={onApplyIssueFix}
|
package/src/subsystem/model.ts
CHANGED
|
@@ -1135,8 +1135,19 @@ export type SubsystemIssueRung =
|
|
|
1135
1135
|
export interface SubsystemNodeIssue {
|
|
1136
1136
|
/** Worst severity on the node — `error` (contradicts source) beats `info`. */
|
|
1137
1137
|
severity: 'error' | 'info';
|
|
1138
|
-
/**
|
|
1139
|
-
|
|
1138
|
+
/**
|
|
1139
|
+
* Earliest failing rung across the node's findings. Absent when none of the
|
|
1140
|
+
* node's findings sit on the construct ladder — a rung-less finding (e.g. a
|
|
1141
|
+
* process gap) carries its audit `kind` instead.
|
|
1142
|
+
*/
|
|
1143
|
+
rung?: SubsystemIssueRung;
|
|
1144
|
+
/**
|
|
1145
|
+
* The audit kind of the node's finding when that finding has no construct
|
|
1146
|
+
* rung: the chip wears the kind's icon (`ISSUE_KIND_ICON`) rather than a
|
|
1147
|
+
* rung's. Set only when `rung` is absent — a rung finding is more specific
|
|
1148
|
+
* about where the construct fails, so it wins the chip.
|
|
1149
|
+
*/
|
|
1150
|
+
kind?: string;
|
|
1140
1151
|
/** Total findings on this node. */
|
|
1141
1152
|
count: number;
|
|
1142
1153
|
}
|
package/src/subsystem/nodes.tsx
CHANGED
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
import { componentColor } from '../pierre/constructColors';
|
|
47
47
|
import { resolvePierreSyntaxThemeName } from '../pierre/pierreSyntaxTheme';
|
|
48
48
|
import { ISSUE_KIND_ICON, ISSUE_RUNG_ICON } from './IssueList';
|
|
49
|
+
import { Search } from 'lucide-react';
|
|
49
50
|
import type { LucideIcon } from 'lucide-react';
|
|
50
51
|
|
|
51
52
|
export const CONSTRUCT_LABEL: Record<string, string> = {
|
|
@@ -122,11 +123,13 @@ function useSubsystemCallbacks(): SubsystemGraphCallbacks {
|
|
|
122
123
|
|
|
123
124
|
/**
|
|
124
125
|
* The diagnostics overlay for a component node: the node's own border turns
|
|
125
|
-
* dotted (see the node's `borderStyle`) and
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
126
|
+
* dotted (see the node's `borderStyle`) and a severity-colored corner chip
|
|
127
|
+
* carries the count. The chip's icon is the earliest-failing construct rung
|
|
128
|
+
* (file → symbol → declaration → type → signature); a finding with no rung
|
|
129
|
+
* (e.g. a C4 process gap) wears its kind icon instead — see
|
|
130
|
+
* `SubsystemNodeIssue.kind`. The chip is purely additive and non-interactive;
|
|
131
|
+
* the border stays construct-colored, so severity never has to compete with
|
|
132
|
+
* the construct palette.
|
|
130
133
|
*/
|
|
131
134
|
function NodeIssueOverlay({ issue }: { issue: SubsystemNodeIssue }) {
|
|
132
135
|
const { theme } = useTheme();
|
|
@@ -134,10 +137,15 @@ function NodeIssueOverlay({ issue }: { issue: SubsystemNodeIssue }) {
|
|
|
134
137
|
const color = isError
|
|
135
138
|
? (theme.colors.error ?? '#e5534b')
|
|
136
139
|
: (theme.colors.warning ?? '#d4a017');
|
|
140
|
+
const Icon = issue.rung
|
|
141
|
+
? ISSUE_RUNG_ICON[issue.rung]
|
|
142
|
+
: issue.kind
|
|
143
|
+
? (ISSUE_KIND_ICON[issue.kind] ?? Search)
|
|
144
|
+
: Search;
|
|
137
145
|
return (
|
|
138
146
|
<IssueChip
|
|
139
147
|
color={color}
|
|
140
|
-
Icon={
|
|
148
|
+
Icon={Icon}
|
|
141
149
|
count={issue.count}
|
|
142
150
|
anchor={{ right: -9, bottom: -8 }}
|
|
143
151
|
/>
|
package/src/utils/elkLayout.ts
CHANGED
|
@@ -715,6 +715,28 @@ export function planCompoundGroups(
|
|
|
715
715
|
return { built, skipped: [...skipped] };
|
|
716
716
|
}
|
|
717
717
|
|
|
718
|
+
/**
|
|
719
|
+
* The four side ports every node in the graph carries, named
|
|
720
|
+
* `<id>_top|_right|_bottom|_left` — the ids `getPortSide` resolves an edge
|
|
721
|
+
* endpoint to.
|
|
722
|
+
*
|
|
723
|
+
* Shared by leaves and compound parents on purpose. A parent is still an ELK
|
|
724
|
+
* *node* that outside edges terminate on, and ELK rejects the whole graph if an
|
|
725
|
+
* edge names a port that was never declared: `JsonImportException: Referenced
|
|
726
|
+
* shape does not exist`. Declaring ports only on leaves silently works right up
|
|
727
|
+
* until the first edge crosses into a node that later becomes a group — which is
|
|
728
|
+
* why the drill-down only broke once a container was opened *and* something
|
|
729
|
+
* outside the boundary pointed at it.
|
|
730
|
+
*/
|
|
731
|
+
function cardinalPorts(id: string) {
|
|
732
|
+
return [
|
|
733
|
+
{ id: `${id}_top`, properties: { 'port.side': 'NORTH' } },
|
|
734
|
+
{ id: `${id}_right`, properties: { 'port.side': 'EAST' } },
|
|
735
|
+
{ id: `${id}_bottom`, properties: { 'port.side': 'SOUTH' } },
|
|
736
|
+
{ id: `${id}_left`, properties: { 'port.side': 'WEST' } },
|
|
737
|
+
];
|
|
738
|
+
}
|
|
739
|
+
|
|
718
740
|
/**
|
|
719
741
|
* Compute ELK layout for nodes and edges
|
|
720
742
|
*
|
|
@@ -774,12 +796,7 @@ export async function computeElkLayout(
|
|
|
774
796
|
x: node.position.x,
|
|
775
797
|
y,
|
|
776
798
|
// Add ports on each side for edge connections
|
|
777
|
-
ports:
|
|
778
|
-
{ id: `${node.id}_top`, properties: { 'port.side': 'NORTH' } },
|
|
779
|
-
{ id: `${node.id}_right`, properties: { 'port.side': 'EAST' } },
|
|
780
|
-
{ id: `${node.id}_bottom`, properties: { 'port.side': 'SOUTH' } },
|
|
781
|
-
{ id: `${node.id}_left`, properties: { 'port.side': 'WEST' } },
|
|
782
|
-
],
|
|
799
|
+
ports: cardinalPorts(node.id),
|
|
783
800
|
properties: {
|
|
784
801
|
'portConstraints': 'FIXED_SIDE',
|
|
785
802
|
...(layer !== undefined ? { 'layering.layer': String(layer) } : {}),
|
|
@@ -999,6 +1016,12 @@ export async function computeElkLayout(
|
|
|
999
1016
|
builtGroups.set(g.id, {
|
|
1000
1017
|
id: g.id,
|
|
1001
1018
|
children,
|
|
1019
|
+
// Same side ports a leaf gets. An edge from outside the group terminates
|
|
1020
|
+
// on the group, and ELK throws on a port reference it cannot resolve.
|
|
1021
|
+
// Deliberately NOT `portConstraints: FIXED_SIDE` the way leaves are: each
|
|
1022
|
+
// port already names its own `port.side`, which is what routes the edge,
|
|
1023
|
+
// and pinning a parent could fight the layout of the children it holds.
|
|
1024
|
+
ports: cardinalPorts(g.id),
|
|
1002
1025
|
layoutOptions:
|
|
1003
1026
|
Object.keys(sizeOptions).length > 0
|
|
1004
1027
|
? { ...compoundLayoutOptions, ...sizeOptions }
|