@principal-ai/subsystems-react 0.23.4 → 0.24.1

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.
Files changed (41) hide show
  1. package/dist/index.d.ts +8 -2
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +4 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/subsystem/ComponentDeclaration.d.ts +3 -1
  6. package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
  7. package/dist/subsystem/ComponentDeclaration.js +2 -2
  8. package/dist/subsystem/ComponentDeclaration.js.map +1 -1
  9. package/dist/subsystem/ConstructsCatalog.d.ts +30 -0
  10. package/dist/subsystem/ConstructsCatalog.d.ts.map +1 -0
  11. package/dist/subsystem/ConstructsCatalog.js +349 -0
  12. package/dist/subsystem/ConstructsCatalog.js.map +1 -0
  13. package/dist/subsystem/DiagnosticToggle.d.ts +46 -0
  14. package/dist/subsystem/DiagnosticToggle.d.ts.map +1 -0
  15. package/dist/subsystem/DiagnosticToggle.js +95 -0
  16. package/dist/subsystem/DiagnosticToggle.js.map +1 -0
  17. package/dist/subsystem/IssueList.d.ts +102 -0
  18. package/dist/subsystem/IssueList.d.ts.map +1 -0
  19. package/dist/subsystem/IssueList.js +282 -0
  20. package/dist/subsystem/IssueList.js.map +1 -0
  21. package/dist/subsystem/SubsystemComponentGraph.d.ts +40 -1
  22. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  23. package/dist/subsystem/SubsystemComponentGraph.js +103 -65
  24. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  25. package/dist/subsystem/model.d.ts +32 -2
  26. package/dist/subsystem/model.d.ts.map +1 -1
  27. package/dist/subsystem/model.js +47 -0
  28. package/dist/subsystem/model.js.map +1 -1
  29. package/package.json +1 -1
  30. package/src/index.ts +42 -0
  31. package/src/stories/Subsystem/ComponentGraph/Constructs.stories.tsx +287 -0
  32. package/src/stories/Subsystem/ComponentGraph/Diagnostics.stories.tsx +138 -0
  33. package/src/stories/Subsystem/ComponentGraph/EdgeViews.stories.tsx +159 -0
  34. package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +130 -0
  35. package/src/subsystem/ComponentDeclaration.tsx +4 -1
  36. package/src/subsystem/ConstructsCatalog.tsx +644 -0
  37. package/src/subsystem/DiagnosticToggle.tsx +160 -0
  38. package/src/subsystem/IssueList.tsx +488 -0
  39. package/src/subsystem/SubsystemComponentGraph.tsx +156 -42
  40. package/src/subsystem/model.test.ts +34 -0
  41. package/src/subsystem/model.ts +72 -2
@@ -7,13 +7,18 @@
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.
13
16
  */
14
17
  import type { ReactNode } from 'react';
15
- import { type SubsystemComponentEdge, type SubsystemComponent, type SubsystemRelation, type SubsystemWalkthrough } from './model';
18
+ import { type SubsystemComponentEdge, type SubsystemComponent, type SubsystemEdgeView, type SubsystemRelation, type SubsystemWalkthrough } from './model';
16
19
  import type { SubsystemOpenFileOptions } from './declarationRef';
20
+ import { type SubsystemDiagnostic } from './DiagnosticToggle';
21
+ import { type SubsystemIssue } from './IssueList';
17
22
  import type { ComponentVerificationState } from './ComponentDeclaration';
18
23
  /** Context passed to `renderWalkthroughViewer` when a flow/step is focused. */
19
24
  export interface WalkthroughViewerContext {
@@ -45,6 +50,17 @@ export interface SubsystemComponentGraphProps {
45
50
  maxNodeWidth?: number;
46
51
  /** Show edge labels (mechanism names) on the graph. @default true */
47
52
  showEdgeLabels?: boolean;
53
+ /**
54
+ * Which edge vocabulary the canvas draws. The relation and walkthrough
55
+ * vocabularies are disjoint, so a graph carrying both shows one or the
56
+ * other — never both. Edges outside the view are hidden (labels go too).
57
+ * - `relations`: topology relation edges (`imports`, `extends`, …)
58
+ * - `walkthroughs`: walkthrough hop edges (`calls`, `feeds`, …), including
59
+ * step numbers when a flow is focused/hovered
60
+ * Defaults to `walkthroughs` when the model has walkthroughs but no
61
+ * relations (e.g. flow-only models), otherwise `relations`.
62
+ */
63
+ edgeView?: SubsystemEdgeView;
48
64
  /** Subsystem title displayed in the sidebar. */
49
65
  title?: string;
50
66
  /**
@@ -92,6 +108,29 @@ export interface SubsystemComponentGraphProps {
92
108
  sidebarExtra?: ReactNode;
93
109
  /** Rendered in the sidebar under the description (e.g. selection inspector). */
94
110
  sidebarAfterDescription?: ReactNode;
111
+ /**
112
+ * Diagnostics status chip in the sidebar title row (beside the description
113
+ * toggle). Shows the last verification pass's state as an icon color + count;
114
+ * `onOpen` opens the full report. Omit to hide the chip.
115
+ */
116
+ diagnostic?: SubsystemDiagnostic;
117
+ /**
118
+ * Verification issues for this graph (audit findings). When diagnostics are
119
+ * active (see `showIssues`) the sidebar's bottom panel becomes the issue
120
+ * list instead of the file tree / walkthroughs.
121
+ */
122
+ issues?: SubsystemIssue[];
123
+ /**
124
+ * Force the sidebar into diagnostics mode. Defaults to `true` whenever
125
+ * `issues` is non-empty (and nothing else has been forced).
126
+ */
127
+ showIssues?: boolean;
128
+ /** Click an issue — focus its target on the graph / open detail. */
129
+ onSelectIssue?: (issue: SubsystemIssue) => void;
130
+ /** Apply an issue's deterministic fix. */
131
+ onApplyIssueFix?: (issue: SubsystemIssue) => void;
132
+ /** Hover an issue — transiently highlight its target (null on leave). */
133
+ onHoverIssue?: (issue: SubsystemIssue | null) => void;
95
134
  /**
96
135
  * Host-injected reader/renderer for the bottom file drawer, keyed by
97
136
  * repo-root-relative path. Opening happens on declaration/file-tree clicks.
@@ -1 +1 @@
1
- {"version":3,"file":"SubsystemComponentGraph.d.ts","sourceRoot":"","sources":["../../src/subsystem/SubsystemComponentGraph.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAiC,SAAS,EAAE,MAAM,OAAO,CAAC;AAqBtE,OAAO,EAOL,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EAEvB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAC1B,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAKjE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAYzE,+EAA+E;AAC/E,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,oBAAoB,CAAC;IAClC,0EAA0E;IAC1E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,2FAA2F;IAC3F,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,wBAAwB,KAAK,IAAI,CAAC;CACrE;AAMD,MAAM,WAAW,4BAA4B;IAC3C,UAAU,EAAE,kBAAkB,EAAE,CAAC;IACjC,wGAAwG;IACxG,SAAS,EAAE,iBAAiB,EAAE,CAAC;IAC/B;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACtC,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,6EAA6E;IAC7E,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACtD;kFAC8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IACtC;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,wDAAwD;IACxD,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,SAAS,CAAC;IAC1B,6DAA6D;IAC7D,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,gFAAgF;IAChF,uBAAuB,CAAC,EAAE,SAAS,CAAC;IACpC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,wBAAwB,KAAK,SAAS,CAAC;IAChF;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,CAAC,GAAG,EAAE,wBAAwB,KAAK,SAAS,CAAC;IACvE;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,kBAAkB,KAAK,SAAS,CAAC;IAC9D;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,0EAA0E;IAC1E,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,2DAA2D;IAC3D,qBAAqB,CAAC,EAAE,0BAA0B,GAAG,IAAI,CAAC;CAC3D;AAmqED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,4BAA4B,+BAsC1E"}
1
+ {"version":3,"file":"SubsystemComponentGraph.d.ts","sourceRoot":"","sources":["../../src/subsystem/SubsystemComponentGraph.tsx"],"names":[],"mappings":"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"}
@@ -8,6 +8,9 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
8
8
  * inside one labeled boundary frame; nodes without one sit outside every
9
9
  * boundary. Clicking a component invokes `onSelect`.
10
10
  *
11
+ * When the model has components but no topology or walkthrough edges, the
12
+ * canvas is a constructs catalog (list + signature) instead of a graph.
13
+ *
11
14
  * This is a focused fork of the package's `GraphRenderer` pipeline (same ELK
12
15
  * edge routing, delayed fitView, Background/Controls/MiniMap, node/edge type
13
16
  * injection, onNodeClick) adapted to the subsystem model — read-only.
@@ -17,8 +20,11 @@ import { ReactFlow, ReactFlowProvider, Background, BackgroundVariant, Controls,
17
20
  import { useTheme } from '@principal-ade/industry-theme';
18
21
  import { ChevronDown, ChevronUp, Pause, Play, X } from 'lucide-react';
19
22
  import { IndustryMarkdownSlide } from 'themed-markdown';
20
- import { buildSubsystemGraph, deriveGraphEdges, MECHANISM_COLOR, MECHANISM_DESCRIPTIONS, subsystemGraphLayoutKey, walkthroughStepGraphEdgeId, } from './model';
23
+ import { buildSubsystemGraph, deriveGraphEdges, isConstructsOnlyModel, isRelationMechanism, isWalkthroughMechanism, MECHANISM_COLOR, MECHANISM_DESCRIPTIONS, subsystemGraphLayoutKey, walkthroughStepGraphEdgeId, } from './model';
24
+ import { ConstructsCatalog } from './ConstructsCatalog';
21
25
  import { SubsystemComponentNode, SubsystemGroupNode, SubsystemEdge, SUBSYSTEM_CALLBACKS, hexWithAlpha, EDGE_DIM_ALPHA, fileMatchForNode, flowElementVisibility } from './nodes';
26
+ import { SubsystemDiagnosticToggle } from './DiagnosticToggle';
27
+ import { SubsystemIssueList } from './IssueList';
22
28
  import { SubsystemFileTree } from './SubsystemFileTree';
23
29
  import { GraphLayoutCover } from './GraphLayoutCover';
24
30
  import { ComponentDeclaration } from './ComponentDeclaration';
@@ -69,11 +75,17 @@ function FileOverlayCloseButton({ onClose }) {
69
75
  const WalkthroughDrawerContent = memo(function WalkthroughDrawerContent({ render, walkthrough, stepIndex, onOpenFile, }) {
70
76
  return _jsx(_Fragment, { children: render({ walkthrough, stepIndex, onOpenFile }) });
71
77
  });
72
- function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, 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 }) {
78
+ 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 }) {
73
79
  const { theme } = useTheme();
74
80
  const { fitView } = useReactFlow();
75
81
  const viewport = useViewport();
76
82
  const graphEdges = useMemo(() => deriveGraphEdges({ relations, walkthroughs }), [relations, walkthroughs]);
83
+ // One edge vocabulary at a time. When the caller doesn't pick, follow the
84
+ // model: a flow-only graph defaults to walkthrough edges rather than empty.
85
+ const resolvedEdgeView = edgeView ??
86
+ (relations.length === 0 && (walkthroughs?.length ?? 0) > 0
87
+ ? 'walkthroughs'
88
+ : 'relations');
77
89
  const [built, setBuilt] = useState({
78
90
  nodes: [],
79
91
  edges: [],
@@ -597,6 +609,13 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
597
609
  markerEnd: nextMarker,
598
610
  };
599
611
  };
612
+ // Edges outside the selected view are hidden entirely (labels included).
613
+ const edgeInView = (e) => {
614
+ const mechanism = e.data?.mechanism ?? 'imports';
615
+ return resolvedEdgeView === 'relations'
616
+ ? isRelationMechanism(mechanism)
617
+ : isWalkthroughMechanism(mechanism);
618
+ };
600
619
  if (openedEdgeIds || focusEdgeIds || previewEdgeIds) {
601
620
  return baseEdges.map((e) => {
602
621
  const vis = flowElementVisibility({
@@ -606,14 +625,18 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
606
625
  anySelected: focusEdgeIds != null,
607
626
  });
608
627
  const dimmed = previewEdgeIds ? vis.hidden || !previewEdgeIds.has(e.id) : vis.dimmed;
609
- const hidden = vis.hidden;
628
+ const hidden = vis.hidden || !edgeInView(e);
610
629
  return { ...paint(e, dimmed), hidden };
611
630
  });
612
631
  }
613
- if (!selectedEdgeId)
614
- return baseEdges;
615
- return baseEdges.map((e) => paint(e, e.id !== selectedEdgeId));
616
- }, [baseEdges, selectedEdgeId, openedEdgeIds, focusEdgeIds, previewEdgeIds]);
632
+ if (selectedEdgeId) {
633
+ return baseEdges.map((e) => ({
634
+ ...paint(e, e.id !== selectedEdgeId),
635
+ hidden: !edgeInView(e),
636
+ }));
637
+ }
638
+ return baseEdges.map((e) => ({ ...e, hidden: !edgeInView(e) }));
639
+ }, [baseEdges, selectedEdgeId, openedEdgeIds, focusEdgeIds, previewEdgeIds, resolvedEdgeView]);
617
640
  const onNodesChange = useCallback((changes) => {
618
641
  // Capture dimension changes (React Flow's measurement callback).
619
642
  // Group parents are ELK-sized — ignore their measurements.
@@ -673,6 +696,10 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
673
696
  // own owner-avatar header. Clicking a header collapses that repo's tree.
674
697
  const repoGroups = useMemo(() => buildRepoGroups(components), [components]);
675
698
  const hasWalkthroughs = useMemo(() => (walkthroughs?.length ?? 0) > 0, [walkthroughs]);
699
+ // Diagnostics mode: the bottom panel shows the issue list instead of the
700
+ // file tree / walkthroughs. Explicit `showIssues` wins; otherwise any issues
701
+ // present turn it on.
702
+ const issuesActive = showIssues ?? (issues?.length ?? 0) > 0;
676
703
  const [collapsedRepos, setCollapsedRepos] = useState(new Set());
677
704
  const toggleRepoCollapsed = useCallback((key) => {
678
705
  setCollapsedRepos((prev) => {
@@ -1052,9 +1079,7 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
1052
1079
  if (e.hidden)
1053
1080
  return false;
1054
1081
  const d = e?.data;
1055
- if (d?.dimmed)
1056
- return false;
1057
- return true;
1082
+ return !d?.dimmed;
1058
1083
  })
1059
1084
  .map((e) => {
1060
1085
  const d = e.data;
@@ -1097,7 +1122,7 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
1097
1122
  const walkthroughViewerRef = useRef(renderWalkthroughViewer);
1098
1123
  walkthroughViewerRef.current = renderWalkthroughViewer;
1099
1124
  const renderWalkthroughDrawerContent = useCallback((ctx) => walkthroughViewerRef.current?.(ctx) ?? null, []);
1100
- return (_jsxs("div", { style: { width: '100%', height: '100%', display: 'flex', flexDirection: 'row', userSelect: sidebarDrag ? 'none' : undefined }, children: [!hideSidebar && (title || description || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasWalkthroughs) && (_jsxs("div", { style: {
1125
+ return (_jsxs("div", { style: { width: '100%', height: '100%', display: 'flex', flexDirection: 'row', userSelect: sidebarDrag ? 'none' : undefined }, children: [!hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasWalkthroughs) && (_jsxs("div", { style: {
1101
1126
  width: sidebarWidth,
1102
1127
  minWidth: sidebarWidth,
1103
1128
  borderRight: `1px solid ${theme.colors.border}`,
@@ -1113,7 +1138,7 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
1113
1138
  display: 'flex',
1114
1139
  flexDirection: 'column',
1115
1140
  gap: 12,
1116
- }, children: [sidebarExtra, (title || description) && (_jsxs("div", { style: { display: 'flex', alignItems: 'flex-start', gap: 8 }, children: [title && (_jsx("h2", { style: {
1141
+ }, children: [sidebarExtra, (title || description || diagnostic) && (_jsxs("div", { style: { display: 'flex', alignItems: 'flex-start', gap: 8 }, children: [title && (_jsx("h2", { style: {
1117
1142
  margin: 0,
1118
1143
  flex: 1,
1119
1144
  minWidth: 0,
@@ -1121,21 +1146,29 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
1121
1146
  fontWeight: 600,
1122
1147
  color: theme.colors.text,
1123
1148
  fontFamily: theme.fonts.monospace,
1124
- }, children: title })), description && (_jsx("button", { type: "button", "aria-expanded": descriptionVisible, "aria-label": descriptionVisible ? 'Hide description' : 'Show description', title: descriptionVisible ? 'Hide description' : 'Show description', onMouseEnter: () => setDescToggleHover(true), onMouseLeave: () => setDescToggleHover(false), onClick: () => setDescriptionVisible((v) => !v), style: {
1125
- display: 'inline-flex',
1149
+ }, children: title })), _jsxs("div", { style: {
1150
+ display: 'flex',
1126
1151
  alignItems: 'center',
1127
- justifyContent: 'center',
1152
+ gap: 4,
1128
1153
  flexShrink: 0,
1129
- width: 22,
1130
- height: 22,
1131
- padding: 0,
1132
- border: 'none',
1133
- borderRadius: 4,
1134
- background: descToggleHover ? theme.colors.border : 'transparent',
1135
- color: descToggleHover ? theme.colors.text : (theme.colors.textMuted ?? theme.colors.textSecondary),
1136
- cursor: 'pointer',
1137
- transition: 'background 120ms ease, color 120ms ease',
1138
- }, children: descriptionVisible ? _jsx(ChevronUp, { size: 14 }) : _jsx(ChevronDown, { size: 14 }) }))] })), description && descriptionVisible && (_jsx("div", { style: { fontSize: theme.fontSizes[0], lineHeight: 1.5 }, children: _jsx(IndustryMarkdownSlide, { content: description, slideIdPrefix: "subsystem-desc", slideIndex: 0, isVisible: true, theme: theme, disableScroll: true, disableBasePadding: true, fontSizeScale: 0.9, enableKeyboardScrolling: false, autoFocusOnVisible: false }) })), sidebarAfterDescription] }), (treeFilePaths.length > 0 || hasWalkthroughs) && (_jsxs("div", { style: {
1154
+ // Title's flex:1 already pushes this cluster right; without a
1155
+ // title, keep the actions flush right instead of flush-left.
1156
+ marginLeft: title ? undefined : 'auto',
1157
+ }, children: [diagnostic && _jsx(SubsystemDiagnosticToggle, { ...diagnostic }), description && (_jsx("button", { type: "button", "aria-expanded": descriptionVisible, "aria-label": descriptionVisible ? 'Hide description' : 'Show description', title: descriptionVisible ? 'Hide description' : 'Show description', onMouseEnter: () => setDescToggleHover(true), onMouseLeave: () => setDescToggleHover(false), onClick: () => setDescriptionVisible((v) => !v), style: {
1158
+ display: 'inline-flex',
1159
+ alignItems: 'center',
1160
+ justifyContent: 'center',
1161
+ flexShrink: 0,
1162
+ width: 22,
1163
+ height: 22,
1164
+ padding: 0,
1165
+ border: 'none',
1166
+ borderRadius: 4,
1167
+ background: descToggleHover ? theme.colors.border : 'transparent',
1168
+ color: descToggleHover ? theme.colors.text : (theme.colors.textMuted ?? theme.colors.textSecondary),
1169
+ cursor: 'pointer',
1170
+ transition: 'background 120ms ease, color 120ms ease',
1171
+ }, children: descriptionVisible ? _jsx(ChevronUp, { size: 14 }) : _jsx(ChevronDown, { size: 14 }) }))] })] })), description && descriptionVisible && (_jsx("div", { style: { fontSize: theme.fontSizes[0], lineHeight: 1.5 }, children: _jsx(IndustryMarkdownSlide, { content: description, slideIdPrefix: "subsystem-desc", slideIndex: 0, isVisible: true, theme: theme, disableScroll: true, disableBasePadding: true, fontSizeScale: 0.9, enableKeyboardScrolling: false, autoFocusOnVisible: false }) })), sidebarAfterDescription] }), (treeFilePaths.length > 0 || hasWalkthroughs || issuesActive) && (_jsx("div", { style: {
1139
1172
  ...(showDesc ? { height: '50%' } : { flex: 1, minHeight: 0 }),
1140
1173
  minHeight: 160,
1141
1174
  flexShrink: 0,
@@ -1143,46 +1176,46 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
1143
1176
  display: 'flex',
1144
1177
  flexDirection: 'column',
1145
1178
  overflow: 'hidden',
1146
- }, children: [hasWalkthroughs && (_jsx("div", { role: "tablist", "aria-label": "Sidebar view", style: {
1147
- display: 'flex',
1148
- width: '100%',
1149
- flexShrink: 0,
1150
- borderBottom: `1px solid ${theme.colors.border}`,
1151
- background: theme.colors.backgroundSecondary ?? theme.colors.background,
1152
- }, children: ['walkthroughs', 'files'].map((view) => (_jsx("button", { type: "button", role: "tab", "aria-selected": sidebarView === view, onClick: () => setSidebarView(view), style: {
1179
+ }, children: issuesActive ? (_jsx(SubsystemIssueList, { issues: issues ?? [], onSelectIssue: onSelectIssue, onApplyFix: onApplyIssueFix, onHoverIssue: onHoverIssue })) : (_jsxs(_Fragment, { children: [hasWalkthroughs && (_jsx("div", { role: "tablist", "aria-label": "Sidebar view", style: {
1180
+ display: 'flex',
1181
+ width: '100%',
1182
+ flexShrink: 0,
1183
+ borderBottom: `1px solid ${theme.colors.border}`,
1184
+ background: theme.colors.backgroundSecondary ?? theme.colors.background,
1185
+ }, children: ['walkthroughs', 'files'].map((view) => (_jsx("button", { type: "button", role: "tab", "aria-selected": sidebarView === view, onClick: () => setSidebarView(view), style: {
1186
+ flex: 1,
1187
+ minWidth: 0,
1188
+ padding: '8px 8px',
1189
+ border: 'none',
1190
+ borderRadius: 0,
1191
+ background: sidebarView === view ? theme.colors.background : 'transparent',
1192
+ color: sidebarView === view
1193
+ ? theme.colors.text
1194
+ : theme.colors.textSecondary,
1195
+ fontSize: theme.fontSizes[1],
1196
+ fontFamily: theme.fonts.monospace,
1197
+ textTransform: 'capitalize',
1198
+ cursor: 'pointer',
1199
+ }, children: view === 'walkthroughs' ? 'Walkthroughs' : 'Files' }, view))) })), sidebarView === 'walkthroughs' && walkthroughs && walkthroughs.length > 0 ? (_jsx("div", { style: {
1153
1200
  flex: 1,
1154
- minWidth: 0,
1155
- padding: '8px 8px',
1156
- border: 'none',
1157
- borderRadius: 0,
1158
- background: sidebarView === view ? theme.colors.background : 'transparent',
1159
- color: sidebarView === view
1160
- ? theme.colors.text
1161
- : theme.colors.textSecondary,
1162
- fontSize: theme.fontSizes[1],
1163
- fontFamily: theme.fonts.monospace,
1164
- textTransform: 'capitalize',
1165
- cursor: 'pointer',
1166
- }, children: view === 'walkthroughs' ? 'Walkthroughs' : 'Files' }, view))) })), sidebarView === 'walkthroughs' && walkthroughs && walkthroughs.length > 0 ? (_jsx("div", { style: {
1167
- flex: 1,
1168
- minHeight: 0,
1169
- overflowY: 'auto',
1170
- padding: '0 0 12px',
1171
- }, children: walkthroughs.map((tl) => (_jsx(WalkthroughFlow, { walkthrough: tl, collapsed: !expandedWalkthroughs.has(tl.id), active: focusedWalkthroughId === tl.id
1172
- ? { stepIndex: focusedStepIndex }
1173
- : hoveredWalkthroughStep?.walkthroughId === tl.id
1174
- ? { stepIndex: hoveredWalkthroughStep.stepIndex }
1175
- : null, onToggleCollapsed: toggleWalkthroughCollapsed, onFocusFlow: focusWalkthroughEdges, onClearFocus: clearWalkthroughFocus, onFocusStep: focusWalkthroughStep, onHoverStep: (tl, i) => setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: i }), onHoverFlow: (tl) => setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: null }), onLeaveStep: () => setHoveredWalkthroughStep(null) }, tl.id))) })) : treeFilePaths.length > 0 ? (_jsx(_Fragment, { children: repoGroups.groups.map((group, i) => {
1176
- const groupKey = group.repoKey ?? '__no-repo__';
1177
- const collapsed = collapsedRepos.has(groupKey);
1178
- return (_jsxs("div", { style: {
1179
- flex: collapsed ? '0 0 auto' : 1,
1180
- minHeight: collapsed ? 0 : undefined,
1181
- display: 'flex',
1182
- flexDirection: 'column',
1183
- borderTop: i > 0 ? `1px solid ${theme.colors.border}` : undefined,
1184
- }, children: [repoGroups.multiRepo && (_jsx(RepoGroupHeader, { group: group, collapsed: collapsed, onToggle: () => toggleRepoCollapsed(groupKey) })), !collapsed && (_jsx(SubsystemFileTree, { files: group.entries.map((e) => e.displayPath), selectedFile: selected?.file ?? openFile, hoveredFile: hoveredFile, onSelectFile: onTreeSelectFile, headerless: repoGroups.multiRepo }))] }, groupKey));
1185
- }) })) : null] }))] })), !hideSidebar && (title || description || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasWalkthroughs) && (_jsx("div", { onMouseDown: onSidebarResizeStart, "aria-label": "Resize sidebar", title: "Drag to resize", style: {
1201
+ minHeight: 0,
1202
+ overflowY: 'auto',
1203
+ padding: '0 0 12px',
1204
+ }, children: walkthroughs.map((tl) => (_jsx(WalkthroughFlow, { walkthrough: tl, collapsed: !expandedWalkthroughs.has(tl.id), active: focusedWalkthroughId === tl.id
1205
+ ? { stepIndex: focusedStepIndex }
1206
+ : hoveredWalkthroughStep?.walkthroughId === tl.id
1207
+ ? { stepIndex: hoveredWalkthroughStep.stepIndex }
1208
+ : null, onToggleCollapsed: toggleWalkthroughCollapsed, onFocusFlow: focusWalkthroughEdges, onClearFocus: clearWalkthroughFocus, onFocusStep: focusWalkthroughStep, onHoverStep: (tl, i) => setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: i }), onHoverFlow: (tl) => setHoveredWalkthroughStep({ walkthroughId: tl.id, stepIndex: null }), onLeaveStep: () => setHoveredWalkthroughStep(null) }, tl.id))) })) : treeFilePaths.length > 0 ? (_jsx(_Fragment, { children: repoGroups.groups.map((group, i) => {
1209
+ const groupKey = group.repoKey ?? '__no-repo__';
1210
+ const collapsed = collapsedRepos.has(groupKey);
1211
+ return (_jsxs("div", { style: {
1212
+ flex: collapsed ? '0 0 auto' : 1,
1213
+ minHeight: collapsed ? 0 : undefined,
1214
+ display: 'flex',
1215
+ flexDirection: 'column',
1216
+ borderTop: i > 0 ? `1px solid ${theme.colors.border}` : undefined,
1217
+ }, children: [repoGroups.multiRepo && (_jsx(RepoGroupHeader, { group: group, collapsed: collapsed, onToggle: () => toggleRepoCollapsed(groupKey) })), !collapsed && (_jsx(SubsystemFileTree, { files: group.entries.map((e) => e.displayPath), selectedFile: selected?.file ?? openFile, hoveredFile: hoveredFile, onSelectFile: onTreeSelectFile, headerless: repoGroups.multiRepo }))] }, groupKey));
1218
+ }) })) : null] })) }))] })), !hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasWalkthroughs) && (_jsx("div", { onMouseDown: onSidebarResizeStart, "aria-label": "Resize sidebar", title: "Drag to resize", style: {
1186
1219
  width: 3,
1187
1220
  flexShrink: 0,
1188
1221
  cursor: 'col-resize',
@@ -1640,6 +1673,11 @@ export function SubsystemComponentGraph(props) {
1640
1673
  ro.observe(el);
1641
1674
  return () => ro.disconnect();
1642
1675
  }, []);
1676
+ const constructsOnly = isConstructsOnlyModel({
1677
+ components: props.components,
1678
+ relations: props.relations,
1679
+ walkthroughs: props.walkthroughs,
1680
+ });
1643
1681
  return (_jsx("div", { ref: wrapRef, style: {
1644
1682
  position: 'relative',
1645
1683
  width: '100%',
@@ -1647,6 +1685,6 @@ export function SubsystemComponentGraph(props) {
1647
1685
  minHeight: 0,
1648
1686
  display: 'flex',
1649
1687
  flexDirection: 'column',
1650
- }, children: _jsx("div", { style: { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }, children: _jsx(ReactFlowProvider, { children: _jsx(Inner, { ...props, measured: size }) }) }) }));
1688
+ }, children: _jsx("div", { style: { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }, children: constructsOnly ? (_jsx(ConstructsCatalog, { components: props.components, onSelect: props.onSelect, title: props.title, hideSidebar: props.hideSidebar, description: props.description, diagnostic: props.diagnostic, sidebarExtra: props.sidebarExtra, sidebarAfterDescription: props.sidebarAfterDescription, renderFileViewer: props.renderFileViewer, renderFileView: props.renderFileView, onFileSelect: props.onFileSelect, onVerifyComponent: props.onVerifyComponent, componentVerification: props.componentVerification })) : (_jsx(ReactFlowProvider, { children: _jsx(Inner, { ...props, measured: size }) })) }) }));
1651
1689
  }
1652
1690
  //# sourceMappingURL=SubsystemComponentGraph.js.map