@principal-ai/subsystems-react 0.38.0 → 0.40.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/components/maintain-events/MaintainEventLog.d.ts +35 -0
- package/dist/components/maintain-events/MaintainEventLog.d.ts.map +1 -0
- package/dist/components/maintain-events/MaintainEventLog.js +66 -0
- package/dist/components/maintain-events/MaintainEventLog.js.map +1 -0
- package/dist/components/maintain-events/MaintainLivePanel.d.ts +15 -0
- package/dist/components/maintain-events/MaintainLivePanel.d.ts.map +1 -0
- package/dist/components/maintain-events/MaintainLivePanel.js +49 -0
- package/dist/components/maintain-events/MaintainLivePanel.js.map +1 -0
- package/dist/components/maintain-events/index.d.ts +8 -0
- package/dist/components/maintain-events/index.d.ts.map +1 -0
- package/dist/components/maintain-events/index.js +6 -0
- package/dist/components/maintain-events/index.js.map +1 -0
- 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 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.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 +15 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +464 -14
- 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 +38 -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 +124 -67
- 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/package.json +3 -3
- package/src/components/maintain-events/MaintainEventLog.tsx +152 -0
- package/src/components/maintain-events/MaintainLivePanel.tsx +99 -0
- package/src/components/maintain-events/index.ts +10 -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 +9 -0
- package/src/stories/MaintainEvents/MaintainEventLog.stories.tsx +66 -0
- package/src/stories/MaintainEvents/MaintainLivePanel.stories.tsx +77 -0
- 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/NodeAnatomy.stories.tsx +40 -73
- package/src/stories/Subsystem/ComponentGraph/StoreValueType.stories.tsx +261 -0
- package/src/stories/Subsystem/StoreValueTypeDeclaration.stories.tsx +182 -0
- package/src/stories/data/maintain-live-events.json +554 -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 +541 -13
- package/src/subsystem/formatDeclaration.test.ts +64 -0
- package/src/subsystem/formatDeclaration.ts +20 -5
- package/src/subsystem/model.ts +46 -0
- package/src/subsystem/nodes.group.test.tsx +52 -0
- package/src/subsystem/nodes.tsx +102 -2
- package/src/subsystem/symbolRefs.ts +2 -0
|
@@ -2,6 +2,70 @@ import { describe, expect, test } from 'bun:test';
|
|
|
2
2
|
import { generateDeclarationString, resolveComponentDeclaration } from './formatDeclaration';
|
|
3
3
|
import type { SubsystemComponent } from './model';
|
|
4
4
|
|
|
5
|
+
describe('generateDeclarationString — store', () => {
|
|
6
|
+
const base: Omit<SubsystemComponent, 'declaration'> = {
|
|
7
|
+
id: 'st',
|
|
8
|
+
name: 'feeds',
|
|
9
|
+
construct: 'store',
|
|
10
|
+
symbol: 'feeds',
|
|
11
|
+
file: 'src/live.ts',
|
|
12
|
+
purl: 'pkg:github/acme/app',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
test('renders a declared valueType as the store signature', () => {
|
|
16
|
+
const component: SubsystemComponent = {
|
|
17
|
+
...base,
|
|
18
|
+
declaration: {
|
|
19
|
+
kind: 'store',
|
|
20
|
+
storage: 'memory',
|
|
21
|
+
properties: [],
|
|
22
|
+
valueType: 'Map<string, OpencodeLiveFeedState>',
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
expect(generateDeclarationString(component)).toBe(
|
|
26
|
+
'// store: feeds — Map<string, OpencodeLiveFeedState>\n// backing: memory',
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('named members render as a body under the valueType', () => {
|
|
31
|
+
const component: SubsystemComponent = {
|
|
32
|
+
...base,
|
|
33
|
+
declaration: {
|
|
34
|
+
kind: 'store',
|
|
35
|
+
storage: 'memory',
|
|
36
|
+
valueType: 'Map<string, { width: number }>',
|
|
37
|
+
properties: [{ name: 'current', type: 'Map<string, { width: number }>' }],
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
expect(generateDeclarationString(component)).toBe(
|
|
41
|
+
'// store: feeds — Map<string, { width: number }>\n// backing: memory\n current: Map<string, { width: number }>;',
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('members without a valueType still render as declare const lines', () => {
|
|
46
|
+
const component: SubsystemComponent = {
|
|
47
|
+
...base,
|
|
48
|
+
declaration: {
|
|
49
|
+
kind: 'store',
|
|
50
|
+
properties: [{ name: 'ROOT', type: 'string' }],
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
expect(generateDeclarationString(component)).toBe(
|
|
54
|
+
'// store: feeds\ndeclare const ROOT: string;',
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('a store with neither valueType nor members says so explicitly', () => {
|
|
59
|
+
const component: SubsystemComponent = {
|
|
60
|
+
...base,
|
|
61
|
+
declaration: { kind: 'store', storage: 'memory', properties: [] },
|
|
62
|
+
};
|
|
63
|
+
expect(generateDeclarationString(component)).toBe(
|
|
64
|
+
'// store: feeds — no declared type (no valueType or state members)\n// backing: memory',
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
5
69
|
describe('generateDeclarationString — custom_entity', () => {
|
|
6
70
|
test('renders entity identity + entityKind, no attributes when none authored', () => {
|
|
7
71
|
const component: SubsystemComponent = {
|
|
@@ -223,19 +223,34 @@ function generateType(
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
/**
|
|
226
|
-
* A store renders as its retained state —
|
|
227
|
-
* the
|
|
228
|
-
* block; the access mechanism lives in separate accessor nodes.
|
|
226
|
+
* A store renders as its retained state — the declaration of what it holds,
|
|
227
|
+
* the way every other construct renders its signature. The node's name labels
|
|
228
|
+
* the block; the access mechanism lives in separate accessor nodes.
|
|
229
|
+
*
|
|
230
|
+
* Order: the backing comment, then the store's own typed declaration
|
|
231
|
+
* (`valueType` when declared, otherwise the named members as a body), then any
|
|
232
|
+
* remaining members. A store that declares neither says so explicitly rather
|
|
233
|
+
* than rendering as an empty block.
|
|
229
234
|
*/
|
|
230
235
|
function generateStore(name: string, declaration?: GraphifyComponentDetail): string {
|
|
231
236
|
const store = declaration?.kind === 'store' ? declaration : undefined;
|
|
232
237
|
const backing = store?.storage ? `\n// backing: ${store.storage}` : '';
|
|
233
|
-
const
|
|
238
|
+
const members = store?.properties ?? [];
|
|
239
|
+
const props = members
|
|
234
240
|
.map((p) => `declare const ${p.name}${p.type ? `: ${p.type}` : ''};`)
|
|
235
241
|
.join('\n');
|
|
236
242
|
|
|
243
|
+
if (store?.valueType) {
|
|
244
|
+
const named = members.length
|
|
245
|
+
? `\n${members
|
|
246
|
+
.map((p) => ` ${p.name}${p.type ? `: ${p.type}` : ''};`)
|
|
247
|
+
.join('\n')}`
|
|
248
|
+
: '';
|
|
249
|
+
return `// store: ${name} — ${store.valueType}${backing}${named}`;
|
|
250
|
+
}
|
|
251
|
+
|
|
237
252
|
if (!props) {
|
|
238
|
-
return `// store: ${name} — no
|
|
253
|
+
return `// store: ${name} — no declared type (no valueType or state members)${backing}`;
|
|
239
254
|
}
|
|
240
255
|
return `// store: ${name}${backing}\n${props}`;
|
|
241
256
|
}
|
package/src/subsystem/model.ts
CHANGED
|
@@ -1038,6 +1038,50 @@ export interface SubsystemGraphNodeData extends Record<string, unknown> {
|
|
|
1038
1038
|
fileMatch?: boolean;
|
|
1039
1039
|
/** True while this node is on an opened-but-unselected flow. */
|
|
1040
1040
|
dimmed?: boolean;
|
|
1041
|
+
/** Diagnostics badge — see `SubsystemNodeIssue`. Absent when no findings. */
|
|
1042
|
+
issue?: SubsystemNodeIssue;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
* The per-construct verification ladder, in order. A finding's rung is the
|
|
1047
|
+
* earliest step it fails: file → symbol → declaration → type → signature.
|
|
1048
|
+
*/
|
|
1049
|
+
export type SubsystemIssueRung =
|
|
1050
|
+
| 'file'
|
|
1051
|
+
| 'symbol'
|
|
1052
|
+
| 'declaration'
|
|
1053
|
+
| 'type'
|
|
1054
|
+
| 'signature';
|
|
1055
|
+
|
|
1056
|
+
/**
|
|
1057
|
+
* Per-node diagnostics badge — the proposed issue overlay (severity ring +
|
|
1058
|
+
* earliest-rung corner chip). The graph derives one per component from the
|
|
1059
|
+
* audit issues whose component target resolves to that node. Absent when the
|
|
1060
|
+
* node has no rung-mapped findings.
|
|
1061
|
+
*/
|
|
1062
|
+
export interface SubsystemNodeIssue {
|
|
1063
|
+
/** Worst severity on the node — `error` (contradicts source) beats `info`. */
|
|
1064
|
+
severity: 'error' | 'info';
|
|
1065
|
+
/** Earliest failing rung across the node's findings. */
|
|
1066
|
+
rung: SubsystemIssueRung;
|
|
1067
|
+
/** Total findings on this node. */
|
|
1068
|
+
count: number;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* Per-frame diagnostics badge — a finding about the BOUNDARY itself (its
|
|
1073
|
+
* containment / nesting), not about any member construct. Separate from
|
|
1074
|
+
* `SubsystemNodeIssue` because these have no construct rung to report: the
|
|
1075
|
+
* fault is the region's shape, which is also why it badges the frame rather
|
|
1076
|
+
* than a leaf. `kind` selects the icon (see `ISSUE_KIND_ICON`).
|
|
1077
|
+
*/
|
|
1078
|
+
export interface SubsystemRegionIssue {
|
|
1079
|
+
/** Worst severity across the region's findings — `error` beats `info`. */
|
|
1080
|
+
severity: 'error' | 'info';
|
|
1081
|
+
/** The kind that supplies the badge icon. */
|
|
1082
|
+
kind: string;
|
|
1083
|
+
/** Total findings on this region. */
|
|
1084
|
+
count: number;
|
|
1041
1085
|
}
|
|
1042
1086
|
|
|
1043
1087
|
export interface SubsystemGroupNodeData extends Record<string, unknown> {
|
|
@@ -1046,6 +1090,8 @@ export interface SubsystemGroupNodeData extends Record<string, unknown> {
|
|
|
1046
1090
|
dimmed?: boolean;
|
|
1047
1091
|
/** Host-supplied frame color override; falls back to the derived color. */
|
|
1048
1092
|
color?: string;
|
|
1093
|
+
/** Boundary diagnostics badge — see `SubsystemRegionIssue`. */
|
|
1094
|
+
issue?: SubsystemRegionIssue;
|
|
1049
1095
|
}
|
|
1050
1096
|
|
|
1051
1097
|
export type SubsystemGraphNode =
|
|
@@ -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
|
@@ -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
|
}
|
|
@@ -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
|
}
|