@principal-ai/subsystems-react 0.24.0 → 0.24.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/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/subsystem/ComponentDeclaration.d.ts +3 -1
- package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
- package/dist/subsystem/ComponentDeclaration.js +2 -2
- package/dist/subsystem/ComponentDeclaration.js.map +1 -1
- package/dist/subsystem/ConstructsCatalog.d.ts +30 -0
- package/dist/subsystem/ConstructsCatalog.d.ts.map +1 -0
- package/dist/subsystem/ConstructsCatalog.js +329 -0
- package/dist/subsystem/ConstructsCatalog.js.map +1 -0
- package/dist/subsystem/DiagnosticToggle.d.ts +46 -0
- package/dist/subsystem/DiagnosticToggle.d.ts.map +1 -0
- package/dist/subsystem/DiagnosticToggle.js +95 -0
- package/dist/subsystem/DiagnosticToggle.js.map +1 -0
- package/dist/subsystem/IssueList.d.ts +103 -0
- package/dist/subsystem/IssueList.d.ts.map +1 -0
- package/dist/subsystem/IssueList.js +310 -0
- package/dist/subsystem/IssueList.js.map +1 -0
- package/dist/subsystem/SubsystemComponentGraph.d.ts +28 -0
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +81 -58
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/SubsystemFileTree.js +2 -2
- package/dist/subsystem/SubsystemFileTree.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 +12 -0
- package/dist/subsystem/model.js.map +1 -1
- package/dist/subsystem/nodes.js +5 -5
- package/dist/subsystem/nodes.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +37 -0
- package/src/stories/Subsystem/ComponentGraph/Constructs.stories.tsx +287 -0
- package/src/stories/Subsystem/ComponentGraph/Diagnostics.stories.tsx +138 -0
- package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +130 -0
- package/src/subsystem/ComponentDeclaration.tsx +4 -1
- package/src/subsystem/ConstructsCatalog.tsx +604 -0
- package/src/subsystem/DiagnosticToggle.tsx +160 -0
- package/src/subsystem/IssueList.tsx +519 -0
- package/src/subsystem/SubsystemComponentGraph.tsx +119 -38
- package/src/subsystem/SubsystemFileTree.tsx +2 -2
- package/src/subsystem/model.test.ts +34 -0
- package/src/subsystem/model.ts +18 -2
- package/src/subsystem/nodes.tsx +5 -5
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* inside one labeled boundary frame; nodes without one sit outside every
|
|
8
8
|
* boundary. Clicking a component invokes `onSelect`.
|
|
9
9
|
*
|
|
10
|
+
* When the model has components but no topology or walkthrough edges, the
|
|
11
|
+
* canvas is a constructs catalog (list + signature) instead of a graph.
|
|
12
|
+
*
|
|
10
13
|
* This is a focused fork of the package's `GraphRenderer` pipeline (same ELK
|
|
11
14
|
* edge routing, delayed fitView, Background/Controls/MiniMap, node/edge type
|
|
12
15
|
* injection, onNodeClick) adapted to the subsystem model — read-only.
|
|
@@ -37,6 +40,7 @@ import { IndustryMarkdownSlide } from 'themed-markdown';
|
|
|
37
40
|
import {
|
|
38
41
|
buildSubsystemGraph,
|
|
39
42
|
deriveGraphEdges,
|
|
43
|
+
isConstructsOnlyModel,
|
|
40
44
|
isRelationMechanism,
|
|
41
45
|
isWalkthroughMechanism,
|
|
42
46
|
MECHANISM_COLOR,
|
|
@@ -50,8 +54,11 @@ import {
|
|
|
50
54
|
type SubsystemRelation,
|
|
51
55
|
type SubsystemWalkthrough,
|
|
52
56
|
} from './model';
|
|
57
|
+
import { ConstructsCatalog } from './ConstructsCatalog';
|
|
53
58
|
import type { SubsystemOpenFileOptions } from './declarationRef';
|
|
54
59
|
import { SubsystemComponentNode, SubsystemGroupNode, SubsystemEdge, SUBSYSTEM_CALLBACKS, hexWithAlpha, EDGE_DIM_ALPHA, fileMatchForNode, flowElementVisibility } from './nodes';
|
|
60
|
+
import { SubsystemDiagnosticToggle, type SubsystemDiagnostic } from './DiagnosticToggle';
|
|
61
|
+
import { SubsystemIssueList, type SubsystemIssue } from './IssueList';
|
|
55
62
|
import { SubsystemFileTree } from './SubsystemFileTree';
|
|
56
63
|
import { GraphLayoutCover } from './GraphLayoutCover';
|
|
57
64
|
import { ComponentDeclaration } from './ComponentDeclaration';
|
|
@@ -160,6 +167,29 @@ export interface SubsystemComponentGraphProps {
|
|
|
160
167
|
sidebarExtra?: ReactNode;
|
|
161
168
|
/** Rendered in the sidebar under the description (e.g. selection inspector). */
|
|
162
169
|
sidebarAfterDescription?: ReactNode;
|
|
170
|
+
/**
|
|
171
|
+
* Diagnostics status chip in the sidebar title row (beside the description
|
|
172
|
+
* toggle). Shows the last verification pass's state as an icon color + count;
|
|
173
|
+
* `onOpen` opens the full report. Omit to hide the chip.
|
|
174
|
+
*/
|
|
175
|
+
diagnostic?: SubsystemDiagnostic;
|
|
176
|
+
/**
|
|
177
|
+
* Verification issues for this graph (audit findings). When diagnostics are
|
|
178
|
+
* active (see `showIssues`) the sidebar's bottom panel becomes the issue
|
|
179
|
+
* list instead of the file tree / walkthroughs.
|
|
180
|
+
*/
|
|
181
|
+
issues?: SubsystemIssue[];
|
|
182
|
+
/**
|
|
183
|
+
* Force the sidebar into diagnostics mode. Defaults to `true` whenever
|
|
184
|
+
* `issues` is non-empty (and nothing else has been forced).
|
|
185
|
+
*/
|
|
186
|
+
showIssues?: boolean;
|
|
187
|
+
/** Click an issue — focus its target on the graph / open detail. */
|
|
188
|
+
onSelectIssue?: (issue: SubsystemIssue) => void;
|
|
189
|
+
/** Apply an issue's deterministic fix. */
|
|
190
|
+
onApplyIssueFix?: (issue: SubsystemIssue) => void;
|
|
191
|
+
/** Hover an issue — transiently highlight its target (null on leave). */
|
|
192
|
+
onHoverIssue?: (issue: SubsystemIssue | null) => void;
|
|
163
193
|
/**
|
|
164
194
|
* Host-injected reader/renderer for the bottom file drawer, keyed by
|
|
165
195
|
* repo-root-relative path. Opening happens on declaration/file-tree clicks.
|
|
@@ -274,7 +304,7 @@ interface InnerProps extends SubsystemComponentGraphProps {
|
|
|
274
304
|
measured: { w: number; h: number } | null;
|
|
275
305
|
}
|
|
276
306
|
|
|
277
|
-
function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, edgeView, title, hideSidebar, walkthroughStepMode = 'focus', autoPlayWalkthroughs = false, walkthroughAutoPlayIntervalMs = WALKTHROUGH_PLAY_PAUSE_MS, zoomOnWalkthroughFocus = true, graphTitle, showWalkthroughTitle = false, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, renderFileView, renderFileViewer, renderWalkthroughViewer, onFileSelect, onVerifyComponent, componentVerification }: InnerProps) {
|
|
307
|
+
function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, edgeView, title, hideSidebar, walkthroughStepMode = 'focus', autoPlayWalkthroughs = false, walkthroughAutoPlayIntervalMs = WALKTHROUGH_PLAY_PAUSE_MS, zoomOnWalkthroughFocus = true, graphTitle, showWalkthroughTitle = false, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, diagnostic, issues, showIssues, onSelectIssue, onApplyIssueFix, onHoverIssue, renderFileView, renderFileViewer, renderWalkthroughViewer, onFileSelect, onVerifyComponent, componentVerification }: InnerProps) {
|
|
278
308
|
const { theme } = useTheme();
|
|
279
309
|
const { fitView } = useReactFlow();
|
|
280
310
|
const viewport = useViewport();
|
|
@@ -935,6 +965,10 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
935
965
|
// own owner-avatar header. Clicking a header collapses that repo's tree.
|
|
936
966
|
const repoGroups = useMemo(() => buildRepoGroups(components), [components]);
|
|
937
967
|
const hasWalkthroughs = useMemo(() => (walkthroughs?.length ?? 0) > 0, [walkthroughs]);
|
|
968
|
+
// Diagnostics mode: the bottom panel shows the issue list instead of the
|
|
969
|
+
// file tree / walkthroughs. Explicit `showIssues` wins; otherwise any issues
|
|
970
|
+
// present turn it on.
|
|
971
|
+
const issuesActive = showIssues ?? (issues?.length ?? 0) > 0;
|
|
938
972
|
const [collapsedRepos, setCollapsedRepos] = useState<Set<string>>(new Set());
|
|
939
973
|
const toggleRepoCollapsed = useCallback((key: string) => {
|
|
940
974
|
setCollapsedRepos((prev) => {
|
|
@@ -1426,7 +1460,7 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
1426
1460
|
{/* Sidebar: scrollable title/description on top, files or flows pinned below.
|
|
1427
1461
|
The description hides by default so files/flows get the room; the
|
|
1428
1462
|
title-row toggle reveals it, and the lower panel yields back to 50%. */}
|
|
1429
|
-
{!hideSidebar && (title || description || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasWalkthroughs) && (
|
|
1463
|
+
{!hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasWalkthroughs) && (
|
|
1430
1464
|
<div
|
|
1431
1465
|
style={{
|
|
1432
1466
|
width: sidebarWidth,
|
|
@@ -1450,7 +1484,7 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
1450
1484
|
}}
|
|
1451
1485
|
>
|
|
1452
1486
|
{sidebarExtra}
|
|
1453
|
-
{(title || description) && (
|
|
1487
|
+
{(title || description || diagnostic) && (
|
|
1454
1488
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 8 }}>
|
|
1455
1489
|
{title && (
|
|
1456
1490
|
<h2
|
|
@@ -1467,34 +1501,47 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
1467
1501
|
{title}
|
|
1468
1502
|
</h2>
|
|
1469
1503
|
)}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1504
|
+
<div
|
|
1505
|
+
style={{
|
|
1506
|
+
display: 'flex',
|
|
1507
|
+
alignItems: 'center',
|
|
1508
|
+
gap: 4,
|
|
1509
|
+
flexShrink: 0,
|
|
1510
|
+
// Title's flex:1 already pushes this cluster right; without a
|
|
1511
|
+
// title, keep the actions flush right instead of flush-left.
|
|
1512
|
+
marginLeft: title ? undefined : 'auto',
|
|
1513
|
+
}}
|
|
1514
|
+
>
|
|
1515
|
+
{diagnostic && <SubsystemDiagnosticToggle {...diagnostic} />}
|
|
1516
|
+
{description && (
|
|
1517
|
+
<button
|
|
1518
|
+
type="button"
|
|
1519
|
+
aria-expanded={descriptionVisible}
|
|
1520
|
+
aria-label={descriptionVisible ? 'Hide description' : 'Show description'}
|
|
1521
|
+
title={descriptionVisible ? 'Hide description' : 'Show description'}
|
|
1522
|
+
onMouseEnter={() => setDescToggleHover(true)}
|
|
1523
|
+
onMouseLeave={() => setDescToggleHover(false)}
|
|
1524
|
+
onClick={() => setDescriptionVisible((v) => !v)}
|
|
1525
|
+
style={{
|
|
1526
|
+
display: 'inline-flex',
|
|
1527
|
+
alignItems: 'center',
|
|
1528
|
+
justifyContent: 'center',
|
|
1529
|
+
flexShrink: 0,
|
|
1530
|
+
width: 22,
|
|
1531
|
+
height: 22,
|
|
1532
|
+
padding: 0,
|
|
1533
|
+
border: 'none',
|
|
1534
|
+
borderRadius: 4,
|
|
1535
|
+
background: descToggleHover ? theme.colors.border : 'transparent',
|
|
1536
|
+
color: descToggleHover ? theme.colors.text : (theme.colors.textMuted ?? theme.colors.textSecondary),
|
|
1537
|
+
cursor: 'pointer',
|
|
1538
|
+
transition: 'background 120ms ease, color 120ms ease',
|
|
1539
|
+
}}
|
|
1540
|
+
>
|
|
1541
|
+
{descriptionVisible ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
|
|
1542
|
+
</button>
|
|
1543
|
+
)}
|
|
1544
|
+
</div>
|
|
1498
1545
|
</div>
|
|
1499
1546
|
)}
|
|
1500
1547
|
{description && descriptionVisible && (
|
|
@@ -1507,7 +1554,6 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
1507
1554
|
theme={theme}
|
|
1508
1555
|
disableScroll={true}
|
|
1509
1556
|
disableBasePadding
|
|
1510
|
-
fontSizeScale={0.9}
|
|
1511
1557
|
enableKeyboardScrolling={false}
|
|
1512
1558
|
autoFocusOnVisible={false}
|
|
1513
1559
|
/>
|
|
@@ -1515,7 +1561,7 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
1515
1561
|
)}
|
|
1516
1562
|
{sidebarAfterDescription}
|
|
1517
1563
|
</div>
|
|
1518
|
-
{(treeFilePaths.length > 0 || hasWalkthroughs) && (
|
|
1564
|
+
{(treeFilePaths.length > 0 || hasWalkthroughs || issuesActive) && (
|
|
1519
1565
|
<div
|
|
1520
1566
|
style={{
|
|
1521
1567
|
...(showDesc ? { height: '50%' as const } : { flex: 1, minHeight: 0 }),
|
|
@@ -1527,6 +1573,15 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
1527
1573
|
overflow: 'hidden',
|
|
1528
1574
|
}}
|
|
1529
1575
|
>
|
|
1576
|
+
{issuesActive ? (
|
|
1577
|
+
<SubsystemIssueList
|
|
1578
|
+
issues={issues ?? []}
|
|
1579
|
+
onSelectIssue={onSelectIssue}
|
|
1580
|
+
onApplyFix={onApplyIssueFix}
|
|
1581
|
+
onHoverIssue={onHoverIssue}
|
|
1582
|
+
/>
|
|
1583
|
+
) : (
|
|
1584
|
+
<>
|
|
1530
1585
|
{hasWalkthroughs && (
|
|
1531
1586
|
<div
|
|
1532
1587
|
role="tablist"
|
|
@@ -1640,13 +1695,15 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
1640
1695
|
})}
|
|
1641
1696
|
</>
|
|
1642
1697
|
) : null}
|
|
1698
|
+
</>
|
|
1699
|
+
)}
|
|
1643
1700
|
</div>
|
|
1644
1701
|
)}
|
|
1645
1702
|
</div>
|
|
1646
1703
|
)}
|
|
1647
1704
|
|
|
1648
1705
|
{/* Drag handle between sidebar and canvas — resize the left panel. */}
|
|
1649
|
-
{!hideSidebar && (title || description || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasWalkthroughs) && (
|
|
1706
|
+
{!hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasWalkthroughs) && (
|
|
1650
1707
|
<div
|
|
1651
1708
|
onMouseDown={onSidebarResizeStart}
|
|
1652
1709
|
aria-label="Resize sidebar"
|
|
@@ -2352,7 +2409,7 @@ function WalkthroughFlow({
|
|
|
2352
2409
|
style={{
|
|
2353
2410
|
flexShrink: 0,
|
|
2354
2411
|
width: 14,
|
|
2355
|
-
fontSize: theme.fontSizes[0]
|
|
2412
|
+
fontSize: theme.fontSizes[0],
|
|
2356
2413
|
fontFamily: theme.fonts.monospace,
|
|
2357
2414
|
color: stepActive ? theme.colors.text : muted,
|
|
2358
2415
|
}}
|
|
@@ -2435,6 +2492,12 @@ export function SubsystemComponentGraph(props: SubsystemComponentGraphProps) {
|
|
|
2435
2492
|
return () => ro.disconnect();
|
|
2436
2493
|
}, []);
|
|
2437
2494
|
|
|
2495
|
+
const constructsOnly = isConstructsOnlyModel({
|
|
2496
|
+
components: props.components,
|
|
2497
|
+
relations: props.relations,
|
|
2498
|
+
walkthroughs: props.walkthroughs,
|
|
2499
|
+
});
|
|
2500
|
+
|
|
2438
2501
|
return (
|
|
2439
2502
|
<div
|
|
2440
2503
|
ref={wrapRef}
|
|
@@ -2448,9 +2511,27 @@ export function SubsystemComponentGraph(props: SubsystemComponentGraphProps) {
|
|
|
2448
2511
|
}}
|
|
2449
2512
|
>
|
|
2450
2513
|
<div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
|
|
2451
|
-
|
|
2452
|
-
<
|
|
2453
|
-
|
|
2514
|
+
{constructsOnly ? (
|
|
2515
|
+
<ConstructsCatalog
|
|
2516
|
+
components={props.components}
|
|
2517
|
+
onSelect={props.onSelect}
|
|
2518
|
+
title={props.title}
|
|
2519
|
+
hideSidebar={props.hideSidebar}
|
|
2520
|
+
description={props.description}
|
|
2521
|
+
diagnostic={props.diagnostic}
|
|
2522
|
+
sidebarExtra={props.sidebarExtra}
|
|
2523
|
+
sidebarAfterDescription={props.sidebarAfterDescription}
|
|
2524
|
+
renderFileViewer={props.renderFileViewer}
|
|
2525
|
+
renderFileView={props.renderFileView}
|
|
2526
|
+
onFileSelect={props.onFileSelect}
|
|
2527
|
+
onVerifyComponent={props.onVerifyComponent}
|
|
2528
|
+
componentVerification={props.componentVerification}
|
|
2529
|
+
/>
|
|
2530
|
+
) : (
|
|
2531
|
+
<ReactFlowProvider>
|
|
2532
|
+
<Inner {...props} measured={size} />
|
|
2533
|
+
</ReactFlowProvider>
|
|
2534
|
+
)}
|
|
2454
2535
|
</div>
|
|
2455
2536
|
</div>
|
|
2456
2537
|
);
|
|
@@ -153,7 +153,7 @@ export function SubsystemFileTree({ files, selectedFile, hoveredFile, onSelectFi
|
|
|
153
153
|
>
|
|
154
154
|
<span
|
|
155
155
|
style={{
|
|
156
|
-
fontSize: theme.fontSizes[0]
|
|
156
|
+
fontSize: theme.fontSizes[0],
|
|
157
157
|
fontFamily: theme.fonts.monospace,
|
|
158
158
|
textTransform: 'uppercase',
|
|
159
159
|
color: muted,
|
|
@@ -162,7 +162,7 @@ export function SubsystemFileTree({ files, selectedFile, hoveredFile, onSelectFi
|
|
|
162
162
|
>
|
|
163
163
|
Files
|
|
164
164
|
</span>
|
|
165
|
-
<span style={{ fontSize: theme.fontSizes[0]
|
|
165
|
+
<span style={{ fontSize: theme.fontSizes[0], fontFamily: theme.fonts.monospace, color: muted }}>
|
|
166
166
|
{paths.length}
|
|
167
167
|
</span>
|
|
168
168
|
</div>
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
buildSubsystemGraph,
|
|
14
14
|
componentPackageKey,
|
|
15
15
|
deriveNameFromSymbol,
|
|
16
|
+
isConstructsOnlyModel,
|
|
16
17
|
constructBadgeLabel,
|
|
17
18
|
constructBadgeColor,
|
|
18
19
|
FRAMEWORK_BADGE_COLOR,
|
|
@@ -642,3 +643,36 @@ describe('subsystem graph model', () => {
|
|
|
642
643
|
expect(subsystemGraphLayoutKey(base)).toBe(subsystemGraphLayoutKey(withRef));
|
|
643
644
|
});
|
|
644
645
|
});
|
|
646
|
+
|
|
647
|
+
describe('isConstructsOnlyModel', () => {
|
|
648
|
+
test('true when components exist and there are no edges', () => {
|
|
649
|
+
expect(isConstructsOnlyModel({ components: comps, relations: [], walkthroughs: [] })).toBe(true);
|
|
650
|
+
expect(isConstructsOnlyModel({ components: comps })).toBe(true);
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
test('false when empty, or when relations or walkthrough hops exist', () => {
|
|
654
|
+
expect(isConstructsOnlyModel({ components: [], relations: [] })).toBe(false);
|
|
655
|
+
expect(isConstructsOnlyModel({ components: comps, relations })).toBe(false);
|
|
656
|
+
expect(
|
|
657
|
+
isConstructsOnlyModel({
|
|
658
|
+
components: comps,
|
|
659
|
+
relations: [],
|
|
660
|
+
walkthroughs: [
|
|
661
|
+
{
|
|
662
|
+
id: 'wt',
|
|
663
|
+
title: 'flow',
|
|
664
|
+
steps: [
|
|
665
|
+
{
|
|
666
|
+
from: 'transcript',
|
|
667
|
+
to: 'reader',
|
|
668
|
+
mechanism: 'calls',
|
|
669
|
+
file: 'transcript.ts',
|
|
670
|
+
line: 1,
|
|
671
|
+
},
|
|
672
|
+
],
|
|
673
|
+
},
|
|
674
|
+
],
|
|
675
|
+
}),
|
|
676
|
+
).toBe(false);
|
|
677
|
+
});
|
|
678
|
+
});
|
package/src/subsystem/model.ts
CHANGED
|
@@ -331,8 +331,8 @@ export function walkthroughStepGraphEdgeId(
|
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
export function deriveGraphEdges(doc: {
|
|
334
|
-
relations?: SubsystemRelation[];
|
|
335
|
-
walkthroughs?: SubsystemWalkthrough[];
|
|
334
|
+
relations?: readonly SubsystemRelation[];
|
|
335
|
+
walkthroughs?: readonly SubsystemWalkthrough[];
|
|
336
336
|
}): SubsystemComponentEdge[] {
|
|
337
337
|
const byId = new Map<string, SubsystemComponentEdge>();
|
|
338
338
|
for (const r of doc.relations ?? []) {
|
|
@@ -363,6 +363,22 @@ export function deriveGraphEdges(doc: {
|
|
|
363
363
|
return [...byId.values()];
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
+
/**
|
|
367
|
+
* True when the model has components but no topology or walkthrough edges.
|
|
368
|
+
* Those snapshots are a catalog of declarations, not a graph.
|
|
369
|
+
*/
|
|
370
|
+
export function isConstructsOnlyModel(doc: {
|
|
371
|
+
components: readonly { id: string }[];
|
|
372
|
+
relations?: readonly SubsystemRelation[];
|
|
373
|
+
walkthroughs?: readonly SubsystemWalkthrough[];
|
|
374
|
+
}): boolean {
|
|
375
|
+
if (doc.components.length === 0) return false;
|
|
376
|
+
return deriveGraphEdges({
|
|
377
|
+
relations: doc.relations,
|
|
378
|
+
walkthroughs: doc.walkthroughs,
|
|
379
|
+
}).length === 0;
|
|
380
|
+
}
|
|
381
|
+
|
|
366
382
|
/**
|
|
367
383
|
* Derive a consistent display `name` from a code `symbol` + construct.
|
|
368
384
|
*
|
package/src/subsystem/nodes.tsx
CHANGED
|
@@ -174,7 +174,7 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
174
174
|
left: BADGE_EDGE_INSET,
|
|
175
175
|
zIndex: 1,
|
|
176
176
|
fontFamily: theme.fonts.monospace,
|
|
177
|
-
fontSize: theme.fontSizes[
|
|
177
|
+
fontSize: theme.fontSizes[1],
|
|
178
178
|
letterSpacing: 0.5,
|
|
179
179
|
textTransform: 'uppercase',
|
|
180
180
|
lineHeight: '17px',
|
|
@@ -208,7 +208,7 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
208
208
|
<span
|
|
209
209
|
style={{
|
|
210
210
|
fontFamily: theme.fonts.monospace,
|
|
211
|
-
fontSize: theme.fontSizes[
|
|
211
|
+
fontSize: theme.fontSizes[1],
|
|
212
212
|
letterSpacing: 0.5,
|
|
213
213
|
textTransform: 'uppercase',
|
|
214
214
|
lineHeight: '17px',
|
|
@@ -226,7 +226,7 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
226
226
|
<span
|
|
227
227
|
style={{
|
|
228
228
|
fontFamily: theme.fonts.monospace,
|
|
229
|
-
fontSize: theme.fontSizes[
|
|
229
|
+
fontSize: theme.fontSizes[1],
|
|
230
230
|
letterSpacing: 0.5,
|
|
231
231
|
textTransform: 'uppercase',
|
|
232
232
|
lineHeight: '17px',
|
|
@@ -255,7 +255,7 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
255
255
|
transform: 'translateX(-50%)',
|
|
256
256
|
zIndex: 1,
|
|
257
257
|
fontFamily: theme.fonts.monospace,
|
|
258
|
-
fontSize: theme.fontSizes[
|
|
258
|
+
fontSize: theme.fontSizes[1],
|
|
259
259
|
letterSpacing: 0.5,
|
|
260
260
|
lineHeight: '17px',
|
|
261
261
|
whiteSpace: 'nowrap',
|
|
@@ -311,7 +311,7 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
|
|
|
311
311
|
.replace(/\(\)$/, '') && (
|
|
312
312
|
<div
|
|
313
313
|
style={{
|
|
314
|
-
fontSize: theme.fontSizes[0]
|
|
314
|
+
fontSize: theme.fontSizes[0],
|
|
315
315
|
fontFamily: theme.fonts.monospace,
|
|
316
316
|
color: color,
|
|
317
317
|
marginTop: 1,
|