@principal-ai/subsystems-react 0.56.12 → 0.56.14

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 (54) hide show
  1. package/dist/index.d.ts +6 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +3 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/subsystem/C4Graph.d.ts.map +1 -1
  6. package/dist/subsystem/C4Graph.js +10 -4
  7. package/dist/subsystem/C4Graph.js.map +1 -1
  8. package/dist/subsystem/C4ProjectionView.d.ts +60 -0
  9. package/dist/subsystem/C4ProjectionView.d.ts.map +1 -0
  10. package/dist/subsystem/C4ProjectionView.js +336 -0
  11. package/dist/subsystem/C4ProjectionView.js.map +1 -0
  12. package/dist/subsystem/C4SketchView.d.ts +23 -0
  13. package/dist/subsystem/C4SketchView.d.ts.map +1 -0
  14. package/dist/subsystem/C4SketchView.js +119 -0
  15. package/dist/subsystem/C4SketchView.js.map +1 -0
  16. package/dist/subsystem/DescriptionOverlay.d.ts +6 -0
  17. package/dist/subsystem/DescriptionOverlay.d.ts.map +1 -0
  18. package/dist/subsystem/DescriptionOverlay.js +58 -0
  19. package/dist/subsystem/DescriptionOverlay.js.map +1 -0
  20. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  21. package/dist/subsystem/SubsystemComponentGraph.js +289 -271
  22. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  23. package/dist/subsystem/__fixtures__/c4Drilldown.d.ts +4 -27
  24. package/dist/subsystem/__fixtures__/c4Drilldown.d.ts.map +1 -1
  25. package/dist/subsystem/__fixtures__/c4Drilldown.js +4 -56
  26. package/dist/subsystem/__fixtures__/c4Drilldown.js.map +1 -1
  27. package/dist/subsystem/__fixtures__/c4Sketch.d.ts +9 -0
  28. package/dist/subsystem/__fixtures__/c4Sketch.d.ts.map +1 -0
  29. package/dist/subsystem/__fixtures__/c4Sketch.js +89 -0
  30. package/dist/subsystem/__fixtures__/c4Sketch.js.map +1 -0
  31. package/dist/subsystem/c4Sketch.d.ts +74 -0
  32. package/dist/subsystem/c4Sketch.d.ts.map +1 -0
  33. package/dist/subsystem/c4Sketch.js +9 -0
  34. package/dist/subsystem/c4Sketch.js.map +1 -0
  35. package/dist/subsystem/c4TrailFocus.d.ts +30 -0
  36. package/dist/subsystem/c4TrailFocus.d.ts.map +1 -0
  37. package/dist/subsystem/c4TrailFocus.js +57 -0
  38. package/dist/subsystem/c4TrailFocus.js.map +1 -0
  39. package/package.json +1 -1
  40. package/src/index.ts +14 -0
  41. package/src/stories/Canvases/C4/C4Draft.stories.tsx +238 -0
  42. package/src/stories/Canvases/C4/C4Sketch.stories.tsx +36 -0
  43. package/src/stories/Canvases/C4/C4ToComponentDrill.stories.tsx +18 -429
  44. package/src/subsystem/C4Graph.tsx +10 -4
  45. package/src/subsystem/C4ProjectionView.tsx +595 -0
  46. package/src/subsystem/C4SketchView.tsx +241 -0
  47. package/src/subsystem/DescriptionOverlay.test.tsx +76 -0
  48. package/src/subsystem/DescriptionOverlay.tsx +101 -0
  49. package/src/subsystem/SubsystemComponentGraph.sidebar.test.tsx +10 -3
  50. package/src/subsystem/SubsystemComponentGraph.tsx +44 -12
  51. package/src/subsystem/__fixtures__/c4Drilldown.ts +7 -58
  52. package/src/subsystem/__fixtures__/c4Sketch.ts +94 -0
  53. package/src/subsystem/c4Sketch.ts +78 -0
  54. package/src/subsystem/c4TrailFocus.ts +62 -0
@@ -34,6 +34,7 @@ import { GRAPH_CANVAS_CLASS, GRAPH_NAV_PROPS, GraphChrome, GraphLayerStyle, LAYO
34
34
  import { ComponentDeclaration } from './ComponentDeclaration';
35
35
  import { FileDrawer, FILE_DRAWER_HEIGHT_MS } from './FileDrawer';
36
36
  import { GraphWithSidebar } from './GraphWithSidebar';
37
+ import { DescriptionOverlay } from './DescriptionOverlay';
37
38
  import { TrailsPanel, TRAIL_PLAY_PAUSE_MS } from './TrailsPanel';
38
39
  import { EDGE_LABEL_WIDTH, EDGE_LABEL_HEIGHT, EDGE_LABEL_FONT_SIZE, EDGE_LABEL_CLOUD_PATH, EDGE_LABEL_CLOUD_EXTRA_TOP, } from '../utils/edgeLabel';
39
40
  /** Identifiers a token could match to name this component as a construct. */
@@ -130,6 +131,7 @@ const TrailDrawerContent = memo(function TrailDrawerContent({ render, trail, ste
130
131
  function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirection: layoutDirectionProp, defaultLayoutDirection = 'horizontal', onLayoutDirectionChange, hideLayoutToggle = false, refocusOnLayoutFlip = true, initialTrailId, onTrailSelect, onTrailStepHover, onReorderTrails, stepEmphasis, onRemoveStepEmphasis, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, showSingletonFrames = true, moduleNesting, edgeView, hideSidebar, trailStepMode = 'focus', autoPlayTrails = false, trailAutoPlayIntervalMs = TRAIL_PLAY_PAUSE_MS, zoomOnTrailFocus = true, trailFocusDurationMs = 300, graphTitle, showTrailTitle = false, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, issues, showIssues, filesActive, sidebarScope, onSidebarTrailChange, focusIssueCategory, laneStatus, onSelectIssue, onApplyIssueFix, onHoverIssue, renderFileViewer, renderTrailViewer, onFileSelect, componentVerification, onInspectSymbol, boundaryColors, codeProcessKeys, hideDrawer = false, persistKey, liveEvents, agentsPanel }) {
131
132
  const { theme } = useTheme();
132
133
  const { fitView, fitBounds } = useReactFlow();
134
+ const [descriptionOpen, setDescriptionOpen] = useState(false);
133
135
  const viewport = useViewport();
134
136
  // Restored once per mount from `localStorage` (see `readViewState`). Each
135
137
  // graph is its own tab/mount, so `persistKey` is stable for a mount.
@@ -2057,7 +2059,23 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
2057
2059
  : undefined
2058
2060
  : undefined;
2059
2061
  // Header block: extras, then the description.
2060
- const sidebarHeader = (_jsxs(_Fragment, { children: [sidebarExtra, description && (_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, enableKeyboardScrolling: false, autoFocusOnVisible: false }) })), sidebarAfterDescription] }));
2062
+ const sidebarHeader = (_jsxs(_Fragment, { children: [sidebarExtra, description && (_jsxs("div", { style: { fontSize: theme.fontSizes[0], lineHeight: 1.5 }, children: [_jsx("div", { style: {
2063
+ display: '-webkit-box',
2064
+ WebkitBoxOrient: 'vertical',
2065
+ WebkitLineClamp: 6,
2066
+ maxHeight: '9em',
2067
+ overflow: 'hidden',
2068
+ }, children: _jsx(IndustryMarkdownSlide, { content: description, slideIdPrefix: "subsystem-desc", slideIndex: 0, isVisible: true, theme: theme, disableScroll: true, disableBasePadding: true, enableKeyboardScrolling: false, autoFocusOnVisible: false }) }), _jsx("button", { type: "button", "aria-label": "Read full description", "aria-haspopup": "dialog", "aria-expanded": descriptionOpen, onClick: () => setDescriptionOpen(true), style: {
2069
+ marginTop: 6,
2070
+ padding: 0,
2071
+ border: 'none',
2072
+ background: 'transparent',
2073
+ color: theme.colors.textSecondary,
2074
+ fontFamily: theme.fonts.body,
2075
+ fontSize: theme.fontSizes[0],
2076
+ textDecoration: 'underline',
2077
+ cursor: 'pointer',
2078
+ }, children: "More" })] })), sidebarAfterDescription] }));
2061
2079
  // Lower panel: diagnostics, the file tree, or the trail flow list.
2062
2080
  const sidebarPanel = issuesActive ? (diagnosticsTab === 'agents' && agentsPanel && !sidebarTrail ? (_jsx(SubsystemAgentsPanel, { ...agentsPanel })) : (_jsx(SubsystemIssueList, { issues: sidebarScope.issues, focusCategory: focusIssueCategory, laneStatus: sidebarTrail ? undefined : laneStatus, onSelectIssue: focusIssueTarget, onDeselectIssue: unfocusIssueTarget, onApplyFix: onApplyIssueFix, onHoverIssue: onHoverIssue, onExpandedCategoriesChange: handleExpandedCategoriesChange }))) : filesActive ? treePaths.entries.length === 0 ? (_jsx("div", { style: { padding: 16, color: theme.colors.textSecondary }, children: "No files for this trail." })) : (_jsx(SubsystemFileTree, { headerless: true, files: treeFilePaths, selectedFile: openFile ? treePaths.toDisplay.get(openFile) : null, onSelectFile: (path) => {
2063
2081
  const file = treePaths.toFile.get(path);
@@ -2102,279 +2120,279 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
2102
2120
  fontSize: theme.fontSizes[0],
2103
2121
  color: theme.colors.textMuted ?? theme.colors.textSecondary,
2104
2122
  }, children: fileOverlay.file }), _jsx(FileOverlayCloseButton, { onClose: closeFileOverlay })] }), _jsx("div", { style: { flex: 1, minHeight: 0, overflow: 'auto' }, children: _jsx(FileDrawerContent, { render: renderDrawerContent, file: fileOverlay.file, startLine: fileOverlay.startLine, endLine: fileOverlay.endLine, fullFile: true }) })] }));
2105
- return (_jsx(GraphWithSidebar, { hidden: !sidebarVisible, bodyHidden: !sidebarBodyVisible, headerMaxHeight: sidebarBodyVisible ? '30%' : '100%', header: sidebarHeader, tabs: sidebarTabs, activeTab: diagnosticsTab, onTabChange: (id) => setDiagnosticsTab(id), sidebarBody: sidebarBody, tabListLabel: issuesActive ? 'Diagnostics view' : 'Sidebar view', defaultWidth: persisted.sidebarWidth ?? 450, minWidth: 240, maxWidth: sidebarMaxWidth, persistKey: persistKey, onResizeEnd: () => requestAnimationFrame(() => fitView()), children: _jsxs("div", { style: { flex: 1, position: 'relative', display: 'flex', flexDirection: committedDirection === 'vertical' ? 'row' : 'column', minHeight: 0, minWidth: 0 }, children: [_jsxs("div", { className: layoutFlipping ? LAYOUT_FLIP_CLASS : undefined, style: {
2106
- position: 'relative',
2107
- flex: collapseCanvas ? 0 : 1,
2108
- minHeight: 0,
2109
- minWidth: 0,
2110
- height: collapseCanvas ? 0 : undefined,
2111
- overflow: 'hidden',
2112
- display: 'flex',
2113
- flexDirection: 'column',
2114
- }, children: [showEdgeLabels !== false && (_jsx("div", { style: {
2115
- position: 'absolute',
2116
- inset: 0,
2117
- overflow: 'hidden',
2118
- pointerEvents: 'none',
2119
- zIndex: 5,
2120
- opacity: layoutFlipping ? 0 : 1,
2121
- transition: `opacity ${LAYOUT_FLIP_EDGE_MS}ms ease`,
2122
- }, children: edgeLabels.map((lbl) => {
2123
- const mechanism = lbl.mechanism;
2124
- const isGraphify = lbl.provenance === 'graphify';
2125
- const color = edgeColor({ mechanism, provenance: lbl.provenance });
2126
- // Label chrome stays the crisp rounded box for every edge — graphify
2127
- // provenance is carried by hue (GRAPHIFY_RELATION_COLOR), the dashed
2128
- // stroke, and the tooltip, NOT by the "ambiguous" cloud silhouette
2129
- // (that idiom is reserved for soft authored mechanisms).
2130
- const verifiable = true;
2131
- const labelTitle = isGraphify
2132
- ? 'Derived from graphify (static symbol graph)'
2133
- : MECHANISM_DESCRIPTIONS.find(([m]) => m === mechanism)?.[2] === false
2134
- ? 'Not directly verifiable with graphify'
2135
- : undefined;
2136
- const screenX = lbl.midX * viewport.zoom + viewport.x;
2137
- const screenY = lbl.midY * viewport.zoom + viewport.y;
2138
- const text = lbl.stepNos?.length
2139
- ? `${lbl.stepNos.map((n) => `${n}:`).join(' ')} ${lbl.mechanism}`
2140
- : lbl.mechanism;
2141
- return (_jsxs("div", { "data-edge-label": lbl.id, title: labelTitle, onClick: (e) => {
2142
- e.stopPropagation();
2143
- selectEdge(lbl.id);
2144
- }, style: {
2145
- position: 'absolute',
2146
- left: screenX,
2147
- top: screenY,
2148
- // Center on the flow-space midpoint, and scale with the graph
2149
- // (flow-space size): labels shrink as you zoom out instead of
2150
- // staying fixed-screen-size and dominating the smaller graph.
2151
- transform: `translate(-50%, -50%) scale(${viewport.zoom})`,
2152
- transformOrigin: 'center center',
2153
- display: 'flex',
2154
- alignItems: 'center',
2155
- justifyContent: 'center',
2156
- width: EDGE_LABEL_WIDTH,
2157
- height: EDGE_LABEL_HEIGHT,
2158
- boxSizing: 'border-box',
2159
- padding: '7px 8px',
2160
- cursor: 'pointer',
2161
- pointerEvents: 'auto',
2162
- opacity: lbl.dimmed ? 0.15 : 1,
2163
- }, children: [verifiable ? (_jsx("span", { "aria-hidden": true, style: {
2164
- position: 'absolute',
2165
- inset: 0,
2166
- borderRadius: 4,
2167
- border: `0.5px solid ${color}`,
2168
- background: theme.colors.backgroundSecondary ?? theme.colors.background,
2169
- pointerEvents: 'none',
2170
- } })) : (_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: {
2171
- position: 'absolute',
2172
- left: 0,
2173
- top: -EDGE_LABEL_CLOUD_EXTRA_TOP,
2174
- pointerEvents: 'none',
2175
- }, 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: {
2176
- position: 'relative',
2177
- display: 'block',
2178
- maxWidth: '100%',
2179
- overflow: 'hidden',
2180
- textOverflow: 'ellipsis',
2181
- whiteSpace: 'nowrap',
2182
- fontSize: EDGE_LABEL_FONT_SIZE,
2183
- lineHeight: 1,
2184
- fontFamily: theme.fonts.monospace,
2185
- fontWeight: 500,
2186
- color,
2187
- }, children: text }), lbl.stepIssue && (() => {
2188
- const StepIssueIcon = ISSUE_KIND_ICON[lbl.stepIssue.kind];
2189
- // Match IssueList: error severity = red, info severity = amber
2190
- const issueColor = lbl.stepIssue.severity === 'error'
2191
- ? (theme.colors.error ?? '#e5534b')
2192
- : (theme.colors.warning ?? '#d4a017');
2193
- const badgeBg = theme.colors.backgroundSecondary ?? theme.colors.background;
2194
- return (_jsxs("span", { "aria-hidden": true, title: lbl.stepIssue.kind === 'step_span_stale'
2195
- ? 'Call site changed since verification'
2196
- : lbl.stepIssue.kind === 'step_site_outside_from'
2197
- ? 'Calls site is outside its source component'
2198
- : 'Call site not yet verified', style: {
2123
+ return (_jsxs(GraphWithSidebar, { hidden: !sidebarVisible, bodyHidden: !sidebarBodyVisible, header: sidebarHeader, tabs: sidebarTabs, activeTab: diagnosticsTab, onTabChange: (id) => setDiagnosticsTab(id), sidebarBody: sidebarBody, tabListLabel: issuesActive ? 'Diagnostics view' : 'Sidebar view', defaultWidth: persisted.sidebarWidth ?? 450, minWidth: 240, maxWidth: sidebarMaxWidth, persistKey: persistKey, onResizeEnd: () => requestAnimationFrame(() => fitView()), children: [!hideSidebar && description && descriptionOpen && (_jsx(DescriptionOverlay, { description: description, onClose: () => setDescriptionOpen(false) })), _jsxs("div", { style: { flex: 1, position: 'relative', display: 'flex', flexDirection: committedDirection === 'vertical' ? 'row' : 'column', minHeight: 0, minWidth: 0 }, children: [_jsxs("div", { className: layoutFlipping ? LAYOUT_FLIP_CLASS : undefined, style: {
2124
+ position: 'relative',
2125
+ flex: collapseCanvas ? 0 : 1,
2126
+ minHeight: 0,
2127
+ minWidth: 0,
2128
+ height: collapseCanvas ? 0 : undefined,
2129
+ overflow: 'hidden',
2130
+ display: 'flex',
2131
+ flexDirection: 'column',
2132
+ }, children: [showEdgeLabels !== false && (_jsx("div", { style: {
2133
+ position: 'absolute',
2134
+ inset: 0,
2135
+ overflow: 'hidden',
2136
+ pointerEvents: 'none',
2137
+ zIndex: 5,
2138
+ opacity: layoutFlipping ? 0 : 1,
2139
+ transition: `opacity ${LAYOUT_FLIP_EDGE_MS}ms ease`,
2140
+ }, children: edgeLabels.map((lbl) => {
2141
+ const mechanism = lbl.mechanism;
2142
+ const isGraphify = lbl.provenance === 'graphify';
2143
+ const color = edgeColor({ mechanism, provenance: lbl.provenance });
2144
+ // Label chrome stays the crisp rounded box for every edge — graphify
2145
+ // provenance is carried by hue (GRAPHIFY_RELATION_COLOR), the dashed
2146
+ // stroke, and the tooltip, NOT by the "ambiguous" cloud silhouette
2147
+ // (that idiom is reserved for soft authored mechanisms).
2148
+ const verifiable = true;
2149
+ const labelTitle = isGraphify
2150
+ ? 'Derived from graphify (static symbol graph)'
2151
+ : MECHANISM_DESCRIPTIONS.find(([m]) => m === mechanism)?.[2] === false
2152
+ ? 'Not directly verifiable with graphify'
2153
+ : undefined;
2154
+ const screenX = lbl.midX * viewport.zoom + viewport.x;
2155
+ const screenY = lbl.midY * viewport.zoom + viewport.y;
2156
+ const text = lbl.stepNos?.length
2157
+ ? `${lbl.stepNos.map((n) => `${n}:`).join(' ')} ${lbl.mechanism}`
2158
+ : lbl.mechanism;
2159
+ return (_jsxs("div", { "data-edge-label": lbl.id, title: labelTitle, onClick: (e) => {
2160
+ e.stopPropagation();
2161
+ selectEdge(lbl.id);
2162
+ }, style: {
2163
+ position: 'absolute',
2164
+ left: screenX,
2165
+ top: screenY,
2166
+ // Center on the flow-space midpoint, and scale with the graph
2167
+ // (flow-space size): labels shrink as you zoom out instead of
2168
+ // staying fixed-screen-size and dominating the smaller graph.
2169
+ transform: `translate(-50%, -50%) scale(${viewport.zoom})`,
2170
+ transformOrigin: 'center center',
2171
+ display: 'flex',
2172
+ alignItems: 'center',
2173
+ justifyContent: 'center',
2174
+ width: EDGE_LABEL_WIDTH,
2175
+ height: EDGE_LABEL_HEIGHT,
2176
+ boxSizing: 'border-box',
2177
+ padding: '7px 8px',
2178
+ cursor: 'pointer',
2179
+ pointerEvents: 'auto',
2180
+ opacity: lbl.dimmed ? 0.15 : 1,
2181
+ }, children: [verifiable ? (_jsx("span", { "aria-hidden": true, style: {
2199
2182
  position: 'absolute',
2200
- right: -7,
2201
- bottom: -6,
2202
- zIndex: 2,
2203
- display: 'inline-flex',
2204
- alignItems: 'center',
2205
- justifyContent: 'center',
2206
- gap: 3,
2207
- minWidth: 18,
2208
- padding: '2px 4px',
2183
+ inset: 0,
2209
2184
  borderRadius: 4,
2210
- border: `1.5px solid ${issueColor}`,
2211
- background: badgeBg,
2185
+ border: `0.5px solid ${color}`,
2186
+ background: theme.colors.backgroundSecondary ?? theme.colors.background,
2187
+ pointerEvents: 'none',
2188
+ } })) : (_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: {
2189
+ position: 'absolute',
2190
+ left: 0,
2191
+ top: -EDGE_LABEL_CLOUD_EXTRA_TOP,
2212
2192
  pointerEvents: 'none',
2213
- }, children: [StepIssueIcon && (_jsx(StepIssueIcon, { size: 12, color: issueColor })), lbl.stepIssue.count > 1 && (_jsx("span", { style: {
2214
- color: issueColor,
2215
- fontFamily: theme.fonts.monospace,
2216
- fontSize: 10,
2217
- fontWeight: 700,
2218
- lineHeight: 1,
2219
- }, children: lbl.stepIssue.count }))] }));
2220
- })()] }, lbl.id));
2221
- }) })), _jsx(GraphLayerStyle, {}), _jsx(ReactFlow, { nodes: dispNodes, edges: dispEdges, nodeTypes: nodeTypes, edgeTypes: edgeTypes, className: GRAPH_CANVAS_CLASS,
2222
- // The key above remounts ReactFlow when the node/edge SET changes (a
2223
- // trail-focus layout, a filtered model). A remount otherwise resets the
2224
- // viewport to identity — a visible jump to scale 1 before the post-flip
2225
- // refit. Seed the new instance with the viewport the old one had.
2226
- defaultViewport: viewport, ...GRAPH_NAV_PROPS, onNodeClick: onNodeClick, onNodeDoubleClick: onNodeDoubleClick, onEdgeClick: onEdgeClick, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, proOptions: { hideAttribution: true }, onPaneClick: onPaneClick, style: {
2227
- width: '100%',
2228
- height: '100%',
2229
- flex: 1,
2230
- minHeight: 0,
2231
- background: theme.colors.background,
2232
- }, children: _jsx(GraphChrome, {}) }, `${baseNodesKey}-${baseEdgesKey}`), !hideLayoutToggle && (_jsxs("button", { type: "button", onClick: () => changeLayoutDirection(requestedDirection === 'horizontal' ? 'vertical' : 'horizontal'), disabled: layoutFlipping, title: requestedDirection === 'horizontal'
2233
- ? 'Switch to a vertical layout (top-to-bottom)'
2234
- : 'Switch to a horizontal layout (left-to-right)', "aria-label": requestedDirection === 'horizontal'
2235
- ? 'Switch to vertical layout'
2236
- : 'Switch to horizontal layout', style: {
2237
- position: 'absolute',
2238
- top: 10,
2239
- right: 10,
2240
- zIndex: 6,
2241
- display: 'inline-flex',
2242
- alignItems: 'center',
2243
- gap: 5,
2244
- padding: '4px 9px',
2245
- border: `1px solid ${theme.colors.border}`,
2246
- borderRadius: 6,
2247
- background: theme.colors.backgroundSecondary ?? theme.colors.background,
2248
- color: theme.colors.textSecondary,
2249
- fontFamily: theme.fonts.monospace,
2250
- fontSize: theme.fontSizes[0],
2251
- cursor: layoutFlipping ? 'default' : 'pointer',
2252
- opacity: layoutFlipping ? 0.55 : 1,
2253
- }, children: [requestedDirection === 'horizontal' ? (_jsx(ArrowDown, { size: 13, "aria-hidden": "true" })) : (_jsx(ArrowRight, { size: 13, "aria-hidden": "true" })), requestedDirection === 'horizontal' ? 'Vertical' : 'Horizontal'] })), unroutedIds.length > 0 && (_jsxs("div", { role: "status", title: `Edges with no routed path (not drawn):\n${unroutedIds.join('\n')}`, style: {
2254
- position: 'absolute',
2255
- top: 10,
2256
- left: 10,
2257
- zIndex: 6,
2258
- display: 'inline-flex',
2259
- alignItems: 'center',
2260
- gap: 6,
2261
- padding: '4px 10px',
2262
- fontSize: theme.fontSizes[0],
2263
- fontFamily: theme.fonts.body,
2264
- color: theme.colors.text,
2265
- background: theme.colors.backgroundSecondary ?? theme.colors.background,
2266
- border: `1px solid ${theme.colors.warning ?? '#d4a017'}`,
2267
- borderRadius: 6,
2268
- boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
2269
- }, children: [_jsx(Unlink, { size: 13, color: theme.colors.warning ?? '#d4a017', "aria-hidden": true }), unroutedIds.length, " ", unroutedIds.length === 1 ? 'edge' : 'edges', " not drawn"] })), (graphTitle || overlayTrailTitle || overlayTrailStep) && (_jsxs("div", { style: {
2270
- position: 'absolute',
2271
- top: 10,
2272
- left: '50%',
2273
- transform: 'translateX(-50%)',
2274
- zIndex: 6,
2275
- display: 'flex',
2276
- flexDirection: 'column',
2277
- alignItems: 'center',
2278
- gap: 6,
2279
- maxWidth: '70%',
2280
- pointerEvents: 'none',
2281
- }, children: [graphTitle && (_jsx("div", { style: {
2282
- maxWidth: '100%',
2283
- overflow: 'hidden',
2284
- textOverflow: 'ellipsis',
2285
- whiteSpace: 'nowrap',
2286
- padding: '6px 18px',
2287
- fontSize: theme.fontSizes[3],
2288
- fontWeight: 600,
2289
- fontFamily: theme.fonts.monospace,
2290
- color: theme.colors.text,
2291
- background: theme.colors.backgroundSecondary ?? theme.colors.background,
2292
- border: `1px solid ${theme.colors.border}`,
2293
- borderRadius: 6,
2294
- boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
2295
- }, children: graphTitle })), overlayTrailTitle && (_jsxs("div", { style: {
2296
- maxWidth: '100%',
2297
- minWidth: overlayTrailStep ? 160 : undefined,
2298
- display: 'flex',
2299
- flexDirection: 'column',
2300
- background: theme.colors.backgroundSecondary ?? theme.colors.background,
2301
- border: `1px solid ${theme.colors.border}`,
2302
- borderRadius: 6,
2303
- boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
2304
- overflow: 'hidden',
2305
- opacity: 0.95,
2306
- }, "aria-label": overlayTrailStep
2307
- ? `${overlayTrailTitle}, step ${overlayTrailStep.index} of ${overlayTrailStep.total}`
2308
- : overlayTrailTitle, children: [_jsx("div", { style: {
2309
- padding: '5px 14px',
2310
- fontSize: theme.fontSizes[2] ?? theme.fontSizes[1],
2311
- fontWeight: 600,
2312
- fontFamily: theme.fonts.monospace,
2313
- color: theme.colors.text,
2314
- overflow: 'hidden',
2315
- textOverflow: 'ellipsis',
2316
- whiteSpace: 'nowrap',
2317
- textAlign: 'center',
2318
- }, children: overlayTrailTitle }), overlayTrailStep && overlayTrailStep.total > 0 && (_jsx("div", { style: {
2319
- display: 'flex',
2320
- gap: 3,
2321
- padding: '0 6px 5px',
2322
- }, "aria-hidden": "true", children: Array.from({ length: overlayTrailStep.total }, (_, i) => {
2323
- const n = i + 1;
2324
- const active = n === overlayTrailStep.index;
2325
- const done = n < overlayTrailStep.index;
2326
- return (_jsx("span", { style: {
2327
- flex: 1,
2328
- height: 2,
2329
- borderRadius: 1,
2330
- background: active
2331
- ? (theme.colors.accent ?? theme.colors.primary ?? theme.colors.text)
2332
- : done
2333
- ? (theme.colors.textSecondary ?? theme.colors.text)
2334
- : (theme.colors.border ?? 'rgba(127,127,127,0.45)'),
2335
- opacity: active ? 1 : done ? 0.75 : 0.4,
2336
- transition: 'opacity 120ms ease, background 120ms ease',
2337
- } }, n));
2338
- }) }))] })), overlayTrailStep?.annotation && (_jsx("div", { style: {
2339
- maxWidth: '100%',
2340
- padding: '6px 14px',
2341
- background: theme.colors.backgroundSecondary ?? theme.colors.background,
2342
- border: `1px solid ${theme.colors.border}`,
2343
- borderRadius: 6,
2344
- boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
2345
- textAlign: 'center',
2346
- fontSize: theme.fontSizes[0],
2347
- fontFamily: theme.fonts.body,
2348
- color: theme.colors.textMuted ?? theme.colors.textSecondary,
2349
- lineHeight: 1.35,
2350
- }, children: overlayTrailStep.annotation }))] })), selected && (_jsx("div", { style: {
2351
- position: 'absolute',
2352
- top: 10,
2353
- right: 10,
2354
- zIndex: 8,
2355
- width: 'min(560px, 70%)',
2356
- maxHeight: '46%',
2357
- overflowY: 'auto',
2358
- background: theme.colors.background,
2359
- border: `1px solid ${theme.colors.border}`,
2360
- borderRadius: 8,
2361
- boxShadow: '0 8px 24px rgba(0,0,0,0.35)',
2362
- }, children: _jsx(ComponentDeclaration, { component: selected, onOpenFile: onOpenDeclarationFile, onRelatedSelect: resolveRelatedComponent, onInspectSymbol: onInspectSymbol
2363
- ? (symbol, ref) => onInspectSymbol({
2364
- purl: selected.purl,
2365
- file: selected.file,
2366
- symbol,
2367
- ref,
2368
- })
2369
- : undefined, verification: componentVerification, fileOpen: drawerTarget?.kind === 'file' &&
2370
- !!selected.file &&
2371
- selected.file === drawerTarget.file, declarationOpen: drawerTarget?.kind === 'file' &&
2372
- drawerTarget.startLine != null &&
2373
- !!selected.file &&
2374
- selected.file === drawerTarget.file &&
2375
- selected.declarationSpan?.startLine === drawerTarget.startLine }) })), committedDirection === 'vertical' && fileOverlayView] }), _jsx(FileDrawer, { title: drawerTitle, onClose: closeDrawer, placement: committedDirection === 'vertical' ? 'left' : 'bottom', onResizeEnd: () => requestAnimationFrame(() => fitView()), fillHeight: drawerFillHeight, suppressEscape: fileOverlayOpen, hidden: hideDrawer, progress: drawerProgress, children: drawerTarget?.kind === 'trail' &&
2376
- focusedTrail &&
2377
- renderTrailViewer ? (_jsx(TrailDrawerContent, { render: renderTrailDrawerContent, trail: focusedTrail, stepIndex: drawerTarget.stepIndex, onOpenFile: onOpenFileFromTrail, resolveSymbol: resolveTrailSymbol, onSymbolClick: openConstructDeclaration, onVisibleStepChange: setVisibleStepIndex })) : drawerTarget?.kind === 'file' ? (_jsx(FileDrawerContent, { render: renderDrawerContent, file: drawerTarget.file, startLine: drawerTarget.startLine, endLine: drawerTarget.endLine })) : null }), committedDirection !== 'vertical' && fileOverlayView, _jsx(GraphLayoutCover, { revealed: layoutReady || layoutFlipping }), canvasOverlay, liveEvents ? _jsx(MaintainLivePanel, { ...liveEvents }) : null] }) }));
2193
+ }, 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: {
2194
+ position: 'relative',
2195
+ display: 'block',
2196
+ maxWidth: '100%',
2197
+ overflow: 'hidden',
2198
+ textOverflow: 'ellipsis',
2199
+ whiteSpace: 'nowrap',
2200
+ fontSize: EDGE_LABEL_FONT_SIZE,
2201
+ lineHeight: 1,
2202
+ fontFamily: theme.fonts.monospace,
2203
+ fontWeight: 500,
2204
+ color,
2205
+ }, children: text }), lbl.stepIssue && (() => {
2206
+ const StepIssueIcon = ISSUE_KIND_ICON[lbl.stepIssue.kind];
2207
+ // Match IssueList: error severity = red, info severity = amber
2208
+ const issueColor = lbl.stepIssue.severity === 'error'
2209
+ ? (theme.colors.error ?? '#e5534b')
2210
+ : (theme.colors.warning ?? '#d4a017');
2211
+ const badgeBg = theme.colors.backgroundSecondary ?? theme.colors.background;
2212
+ return (_jsxs("span", { "aria-hidden": true, title: lbl.stepIssue.kind === 'step_span_stale'
2213
+ ? 'Call site changed since verification'
2214
+ : lbl.stepIssue.kind === 'step_site_outside_from'
2215
+ ? 'Calls site is outside its source component'
2216
+ : 'Call site not yet verified', style: {
2217
+ position: 'absolute',
2218
+ right: -7,
2219
+ bottom: -6,
2220
+ zIndex: 2,
2221
+ display: 'inline-flex',
2222
+ alignItems: 'center',
2223
+ justifyContent: 'center',
2224
+ gap: 3,
2225
+ minWidth: 18,
2226
+ padding: '2px 4px',
2227
+ borderRadius: 4,
2228
+ border: `1.5px solid ${issueColor}`,
2229
+ background: badgeBg,
2230
+ pointerEvents: 'none',
2231
+ }, children: [StepIssueIcon && (_jsx(StepIssueIcon, { size: 12, color: issueColor })), lbl.stepIssue.count > 1 && (_jsx("span", { style: {
2232
+ color: issueColor,
2233
+ fontFamily: theme.fonts.monospace,
2234
+ fontSize: 10,
2235
+ fontWeight: 700,
2236
+ lineHeight: 1,
2237
+ }, children: lbl.stepIssue.count }))] }));
2238
+ })()] }, lbl.id));
2239
+ }) })), _jsx(GraphLayerStyle, {}), _jsx(ReactFlow, { nodes: dispNodes, edges: dispEdges, nodeTypes: nodeTypes, edgeTypes: edgeTypes, className: GRAPH_CANVAS_CLASS,
2240
+ // The key above remounts ReactFlow when the node/edge SET changes (a
2241
+ // trail-focus layout, a filtered model). A remount otherwise resets the
2242
+ // viewport to identity — a visible jump to scale 1 before the post-flip
2243
+ // refit. Seed the new instance with the viewport the old one had.
2244
+ defaultViewport: viewport, ...GRAPH_NAV_PROPS, onNodeClick: onNodeClick, onNodeDoubleClick: onNodeDoubleClick, onEdgeClick: onEdgeClick, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, proOptions: { hideAttribution: true }, onPaneClick: onPaneClick, style: {
2245
+ width: '100%',
2246
+ height: '100%',
2247
+ flex: 1,
2248
+ minHeight: 0,
2249
+ background: theme.colors.background,
2250
+ }, children: _jsx(GraphChrome, {}) }, `${baseNodesKey}-${baseEdgesKey}`), !hideLayoutToggle && (_jsxs("button", { type: "button", onClick: () => changeLayoutDirection(requestedDirection === 'horizontal' ? 'vertical' : 'horizontal'), disabled: layoutFlipping, title: requestedDirection === 'horizontal'
2251
+ ? 'Switch to a vertical layout (top-to-bottom)'
2252
+ : 'Switch to a horizontal layout (left-to-right)', "aria-label": requestedDirection === 'horizontal'
2253
+ ? 'Switch to vertical layout'
2254
+ : 'Switch to horizontal layout', style: {
2255
+ position: 'absolute',
2256
+ top: 10,
2257
+ right: 10,
2258
+ zIndex: 6,
2259
+ display: 'inline-flex',
2260
+ alignItems: 'center',
2261
+ gap: 5,
2262
+ padding: '4px 9px',
2263
+ border: `1px solid ${theme.colors.border}`,
2264
+ borderRadius: 6,
2265
+ background: theme.colors.backgroundSecondary ?? theme.colors.background,
2266
+ color: theme.colors.textSecondary,
2267
+ fontFamily: theme.fonts.monospace,
2268
+ fontSize: theme.fontSizes[0],
2269
+ cursor: layoutFlipping ? 'default' : 'pointer',
2270
+ opacity: layoutFlipping ? 0.55 : 1,
2271
+ }, children: [requestedDirection === 'horizontal' ? (_jsx(ArrowDown, { size: 13, "aria-hidden": "true" })) : (_jsx(ArrowRight, { size: 13, "aria-hidden": "true" })), requestedDirection === 'horizontal' ? 'Vertical' : 'Horizontal'] })), unroutedIds.length > 0 && (_jsxs("div", { role: "status", title: `Edges with no routed path (not drawn):\n${unroutedIds.join('\n')}`, style: {
2272
+ position: 'absolute',
2273
+ top: 10,
2274
+ left: 10,
2275
+ zIndex: 6,
2276
+ display: 'inline-flex',
2277
+ alignItems: 'center',
2278
+ gap: 6,
2279
+ padding: '4px 10px',
2280
+ fontSize: theme.fontSizes[0],
2281
+ fontFamily: theme.fonts.body,
2282
+ color: theme.colors.text,
2283
+ background: theme.colors.backgroundSecondary ?? theme.colors.background,
2284
+ border: `1px solid ${theme.colors.warning ?? '#d4a017'}`,
2285
+ borderRadius: 6,
2286
+ boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
2287
+ }, children: [_jsx(Unlink, { size: 13, color: theme.colors.warning ?? '#d4a017', "aria-hidden": true }), unroutedIds.length, " ", unroutedIds.length === 1 ? 'edge' : 'edges', " not drawn"] })), (graphTitle || overlayTrailTitle || overlayTrailStep) && (_jsxs("div", { style: {
2288
+ position: 'absolute',
2289
+ top: 10,
2290
+ left: '50%',
2291
+ transform: 'translateX(-50%)',
2292
+ zIndex: 6,
2293
+ display: 'flex',
2294
+ flexDirection: 'column',
2295
+ alignItems: 'center',
2296
+ gap: 6,
2297
+ maxWidth: '70%',
2298
+ pointerEvents: 'none',
2299
+ }, children: [graphTitle && (_jsx("div", { style: {
2300
+ maxWidth: '100%',
2301
+ overflow: 'hidden',
2302
+ textOverflow: 'ellipsis',
2303
+ whiteSpace: 'nowrap',
2304
+ padding: '6px 18px',
2305
+ fontSize: theme.fontSizes[3],
2306
+ fontWeight: 600,
2307
+ fontFamily: theme.fonts.monospace,
2308
+ color: theme.colors.text,
2309
+ background: theme.colors.backgroundSecondary ?? theme.colors.background,
2310
+ border: `1px solid ${theme.colors.border}`,
2311
+ borderRadius: 6,
2312
+ boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
2313
+ }, children: graphTitle })), overlayTrailTitle && (_jsxs("div", { style: {
2314
+ maxWidth: '100%',
2315
+ minWidth: overlayTrailStep ? 160 : undefined,
2316
+ display: 'flex',
2317
+ flexDirection: 'column',
2318
+ background: theme.colors.backgroundSecondary ?? theme.colors.background,
2319
+ border: `1px solid ${theme.colors.border}`,
2320
+ borderRadius: 6,
2321
+ boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
2322
+ overflow: 'hidden',
2323
+ opacity: 0.95,
2324
+ }, "aria-label": overlayTrailStep
2325
+ ? `${overlayTrailTitle}, step ${overlayTrailStep.index} of ${overlayTrailStep.total}`
2326
+ : overlayTrailTitle, children: [_jsx("div", { style: {
2327
+ padding: '5px 14px',
2328
+ fontSize: theme.fontSizes[2] ?? theme.fontSizes[1],
2329
+ fontWeight: 600,
2330
+ fontFamily: theme.fonts.monospace,
2331
+ color: theme.colors.text,
2332
+ overflow: 'hidden',
2333
+ textOverflow: 'ellipsis',
2334
+ whiteSpace: 'nowrap',
2335
+ textAlign: 'center',
2336
+ }, children: overlayTrailTitle }), overlayTrailStep && overlayTrailStep.total > 0 && (_jsx("div", { style: {
2337
+ display: 'flex',
2338
+ gap: 3,
2339
+ padding: '0 6px 5px',
2340
+ }, "aria-hidden": "true", children: Array.from({ length: overlayTrailStep.total }, (_, i) => {
2341
+ const n = i + 1;
2342
+ const active = n === overlayTrailStep.index;
2343
+ const done = n < overlayTrailStep.index;
2344
+ return (_jsx("span", { style: {
2345
+ flex: 1,
2346
+ height: 2,
2347
+ borderRadius: 1,
2348
+ background: active
2349
+ ? (theme.colors.accent ?? theme.colors.primary ?? theme.colors.text)
2350
+ : done
2351
+ ? (theme.colors.textSecondary ?? theme.colors.text)
2352
+ : (theme.colors.border ?? 'rgba(127,127,127,0.45)'),
2353
+ opacity: active ? 1 : done ? 0.75 : 0.4,
2354
+ transition: 'opacity 120ms ease, background 120ms ease',
2355
+ } }, n));
2356
+ }) }))] })), overlayTrailStep?.annotation && (_jsx("div", { style: {
2357
+ maxWidth: '100%',
2358
+ padding: '6px 14px',
2359
+ background: theme.colors.backgroundSecondary ?? theme.colors.background,
2360
+ border: `1px solid ${theme.colors.border}`,
2361
+ borderRadius: 6,
2362
+ boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
2363
+ textAlign: 'center',
2364
+ fontSize: theme.fontSizes[0],
2365
+ fontFamily: theme.fonts.body,
2366
+ color: theme.colors.textMuted ?? theme.colors.textSecondary,
2367
+ lineHeight: 1.35,
2368
+ }, children: overlayTrailStep.annotation }))] })), selected && (_jsx("div", { style: {
2369
+ position: 'absolute',
2370
+ top: 10,
2371
+ right: 10,
2372
+ zIndex: 8,
2373
+ width: 'min(560px, 70%)',
2374
+ maxHeight: '46%',
2375
+ overflowY: 'auto',
2376
+ background: theme.colors.background,
2377
+ border: `1px solid ${theme.colors.border}`,
2378
+ borderRadius: 8,
2379
+ boxShadow: '0 8px 24px rgba(0,0,0,0.35)',
2380
+ }, children: _jsx(ComponentDeclaration, { component: selected, onOpenFile: onOpenDeclarationFile, onRelatedSelect: resolveRelatedComponent, onInspectSymbol: onInspectSymbol
2381
+ ? (symbol, ref) => onInspectSymbol({
2382
+ purl: selected.purl,
2383
+ file: selected.file,
2384
+ symbol,
2385
+ ref,
2386
+ })
2387
+ : undefined, verification: componentVerification, fileOpen: drawerTarget?.kind === 'file' &&
2388
+ !!selected.file &&
2389
+ selected.file === drawerTarget.file, declarationOpen: drawerTarget?.kind === 'file' &&
2390
+ drawerTarget.startLine != null &&
2391
+ !!selected.file &&
2392
+ selected.file === drawerTarget.file &&
2393
+ selected.declarationSpan?.startLine === drawerTarget.startLine }) })), committedDirection === 'vertical' && fileOverlayView] }), _jsx(FileDrawer, { title: drawerTitle, onClose: closeDrawer, placement: committedDirection === 'vertical' ? 'left' : 'bottom', onResizeEnd: () => requestAnimationFrame(() => fitView()), fillHeight: drawerFillHeight, suppressEscape: fileOverlayOpen, hidden: hideDrawer, progress: drawerProgress, children: drawerTarget?.kind === 'trail' &&
2394
+ focusedTrail &&
2395
+ renderTrailViewer ? (_jsx(TrailDrawerContent, { render: renderTrailDrawerContent, trail: focusedTrail, stepIndex: drawerTarget.stepIndex, onOpenFile: onOpenFileFromTrail, resolveSymbol: resolveTrailSymbol, onSymbolClick: openConstructDeclaration, onVisibleStepChange: setVisibleStepIndex })) : drawerTarget?.kind === 'file' ? (_jsx(FileDrawerContent, { render: renderDrawerContent, file: drawerTarget.file, startLine: drawerTarget.startLine, endLine: drawerTarget.endLine })) : null }), committedDirection !== 'vertical' && fileOverlayView, _jsx(GraphLayoutCover, { revealed: layoutReady || layoutFlipping }), canvasOverlay, liveEvents ? _jsx(MaintainLivePanel, { ...liveEvents }) : null] })] }));
2378
2396
  }
2379
2397
  export function SubsystemComponentGraph(props) {
2380
2398
  const { theme } = useTheme();