@principal-ai/subsystems-react 0.53.6 → 0.54.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.
Files changed (104) hide show
  1. package/dist/index.d.ts +8 -2
  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/pierre/PierreTrailCodeView.d.ts.map +1 -1
  6. package/dist/pierre/PierreTrailCodeView.js +7 -5
  7. package/dist/pierre/PierreTrailCodeView.js.map +1 -1
  8. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -1
  9. package/dist/stories/Subsystem/ComponentGraph/fixtures.js +1 -2
  10. package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -1
  11. package/dist/subsystem/C4Graph.d.ts +144 -9
  12. package/dist/subsystem/C4Graph.d.ts.map +1 -1
  13. package/dist/subsystem/C4Graph.js +924 -148
  14. package/dist/subsystem/C4Graph.js.map +1 -1
  15. package/dist/subsystem/C4NodeCard.d.ts +22 -1
  16. package/dist/subsystem/C4NodeCard.d.ts.map +1 -1
  17. package/dist/subsystem/C4NodeCard.js +10 -2
  18. package/dist/subsystem/C4NodeCard.js.map +1 -1
  19. package/dist/subsystem/ComponentDeclaration.d.ts +5 -4
  20. package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
  21. package/dist/subsystem/ComponentDeclaration.js +12 -4
  22. package/dist/subsystem/ComponentDeclaration.js.map +1 -1
  23. package/dist/subsystem/ConstructsCatalog.d.ts.map +1 -1
  24. package/dist/subsystem/ConstructsCatalog.js +10 -7
  25. package/dist/subsystem/ConstructsCatalog.js.map +1 -1
  26. package/dist/subsystem/GraphSidebar.d.ts +71 -0
  27. package/dist/subsystem/GraphSidebar.d.ts.map +1 -0
  28. package/dist/subsystem/GraphSidebar.js +145 -0
  29. package/dist/subsystem/GraphSidebar.js.map +1 -0
  30. package/dist/subsystem/GraphWithSidebar.d.ts +24 -0
  31. package/dist/subsystem/GraphWithSidebar.d.ts.map +1 -0
  32. package/dist/subsystem/GraphWithSidebar.js +25 -0
  33. package/dist/subsystem/GraphWithSidebar.js.map +1 -0
  34. package/dist/subsystem/SubsystemComponentGraph.d.ts +33 -0
  35. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  36. package/dist/subsystem/SubsystemComponentGraph.js +364 -411
  37. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  38. package/dist/subsystem/TrailStepsNav.d.ts +48 -0
  39. package/dist/subsystem/TrailStepsNav.d.ts.map +1 -0
  40. package/dist/subsystem/TrailStepsNav.js +189 -0
  41. package/dist/subsystem/TrailStepsNav.js.map +1 -0
  42. package/dist/subsystem/TrailsPanel.d.ts.map +1 -1
  43. package/dist/subsystem/TrailsPanel.js +3 -4
  44. package/dist/subsystem/TrailsPanel.js.map +1 -1
  45. package/dist/subsystem/__fixtures__/c4Drilldown.d.ts +151 -0
  46. package/dist/subsystem/__fixtures__/c4Drilldown.d.ts.map +1 -0
  47. package/dist/subsystem/__fixtures__/c4Drilldown.js +783 -0
  48. package/dist/subsystem/__fixtures__/c4Drilldown.js.map +1 -0
  49. package/dist/subsystem/c4.d.ts +39 -8
  50. package/dist/subsystem/c4.d.ts.map +1 -1
  51. package/dist/subsystem/c4.js.map +1 -1
  52. package/dist/subsystem/declarationRef.d.ts +26 -18
  53. package/dist/subsystem/declarationRef.d.ts.map +1 -1
  54. package/dist/subsystem/declarationRef.js +9 -4
  55. package/dist/subsystem/declarationRef.js.map +1 -1
  56. package/dist/subsystem/model.d.ts +19 -1
  57. package/dist/subsystem/model.d.ts.map +1 -1
  58. package/dist/subsystem/model.js +12 -2
  59. package/dist/subsystem/model.js.map +1 -1
  60. package/dist/subsystem/nodes.d.ts.map +1 -1
  61. package/dist/subsystem/nodes.js +24 -4
  62. package/dist/subsystem/nodes.js.map +1 -1
  63. package/dist/subsystem/trailBrief.d.ts.map +1 -1
  64. package/dist/subsystem/trailBrief.js +6 -1
  65. package/dist/subsystem/trailBrief.js.map +1 -1
  66. package/package.json +3 -3
  67. package/src/index.ts +9 -2
  68. package/src/pierre/PierreTrailCodeView.tsx +9 -13
  69. package/src/stories/Subsystem/C4Graph/C4ToComponentDrill.stories.tsx +496 -0
  70. package/src/stories/Subsystem/ComponentGraph/AnimatedProgression.stories.tsx +7 -7
  71. package/src/stories/Subsystem/ComponentGraph/Constructs.stories.tsx +19 -25
  72. package/src/stories/Subsystem/ComponentGraph/DetailPanel.stories.tsx +8 -10
  73. package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +19 -19
  74. package/src/stories/Subsystem/ComponentGraph/ProposedTrails.stories.tsx +6 -6
  75. package/src/stories/Subsystem/ComponentGraph/TrailAutoplay.stories.tsx +7 -7
  76. package/src/stories/Subsystem/ComponentGraph/Trails.stories.tsx +27 -30
  77. package/src/stories/Subsystem/ComponentGraph/fixtures.ts +1 -2
  78. package/src/stories/Subsystem/ConstructsCatalog.stories.tsx +10 -13
  79. package/src/stories/Subsystem/EgoGraph/EgoGraph.stories.tsx +3 -4
  80. package/src/stories/Subsystem/Sidebar/GraphSidebar.stories.tsx +206 -0
  81. package/src/subsystem/C4Graph.groups.test.ts +96 -2
  82. package/src/subsystem/C4Graph.tsx +1377 -165
  83. package/src/subsystem/C4NodeCard.test.ts +1 -2
  84. package/src/subsystem/C4NodeCard.tsx +24 -2
  85. package/src/subsystem/ComponentDeclaration.tsx +18 -8
  86. package/src/subsystem/ConstructsCatalog.tsx +23 -8
  87. package/src/subsystem/GraphSidebar.test.tsx +152 -0
  88. package/src/subsystem/GraphSidebar.tsx +300 -0
  89. package/src/subsystem/GraphWithSidebar.test.tsx +78 -0
  90. package/src/subsystem/GraphWithSidebar.tsx +60 -0
  91. package/src/subsystem/SubsystemComponentGraph.tsx +284 -370
  92. package/src/subsystem/TrailStepsNav.test.tsx +179 -0
  93. package/src/subsystem/TrailStepsNav.tsx +372 -0
  94. package/src/subsystem/TrailsPanel.tsx +3 -4
  95. package/src/subsystem/__fixtures__/c4Drilldown.test.ts +338 -0
  96. package/src/subsystem/__fixtures__/c4Drilldown.ts +847 -0
  97. package/src/subsystem/c4.ts +41 -8
  98. package/src/subsystem/declarationRef.ts +28 -19
  99. package/src/subsystem/model.test.ts +32 -12
  100. package/src/subsystem/model.ts +30 -1
  101. package/src/subsystem/nodes.group.test.tsx +82 -0
  102. package/src/subsystem/nodes.tsx +30 -2
  103. package/src/subsystem/trailBrief.test.ts +2 -2
  104. package/src/subsystem/trailBrief.ts +6 -1
@@ -32,6 +32,7 @@ import { GraphLayoutCover } from './GraphLayoutCover';
32
32
  import { GRAPH_CANVAS_CLASS, GRAPH_NAV_PROPS, GraphChrome, GraphLayerStyle } from './graphChrome';
33
33
  import { ComponentDeclaration } from './ComponentDeclaration';
34
34
  import { FileDrawer, FILE_DRAWER_HEIGHT_MS } from './FileDrawer';
35
+ import { GraphWithSidebar } from './GraphWithSidebar';
35
36
  import { buildRepoGroups, repoAvatarUrl } from './paths';
36
37
  import { TrailsPanel, TRAIL_PLAY_PAUSE_MS } from './TrailsPanel';
37
38
  import { EDGE_LABEL_WIDTH, EDGE_LABEL_HEIGHT, EDGE_LABEL_FONT_SIZE, EDGE_LABEL_CLOUD_PATH, EDGE_LABEL_CLOUD_EXTRA_TOP, } from '../utils/edgeLabel';
@@ -88,9 +89,13 @@ const edgeTypes = {
88
89
  // Inner re-renders on every viewport pan/zoom and hover; recreating host
89
90
  // elements then would churn their readFile closures and flash the file
90
91
  // viewer's loading state on each render.
91
- const FileDrawerContent = memo(function FileDrawerContent({ render, file, startLine, fullFile, }) {
92
+ const FileDrawerContent = memo(function FileDrawerContent({ render, file, startLine, endLine, fullFile, }) {
92
93
  return (_jsx(_Fragment, { children: render(file, startLine != null
93
- ? { startLine, ...(fullFile ? { fullFile: true } : {}) }
94
+ ? {
95
+ startLine,
96
+ ...(endLine != null ? { endLine } : {}),
97
+ ...(fullFile ? { fullFile: true } : {}),
98
+ }
94
99
  : undefined) }));
95
100
  });
96
101
  function FileOverlayCloseButton({ onClose }) {
@@ -123,7 +128,7 @@ const TrailDrawerContent = memo(function TrailDrawerContent({ render, trail, ste
123
128
  onVisibleStepChange,
124
129
  }) }));
125
130
  });
126
- function Inner({ components, trails, graphifyRelations, orderByLine, initialTrailId, onReorderTrails, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, showSingletonFrames = true, moduleNesting, edgeView, title, hideSidebar, trailStepMode = 'focus', autoPlayTrails = false, trailAutoPlayIntervalMs = TRAIL_PLAY_PAUSE_MS, zoomOnTrailFocus = true, trailFocusDurationMs = 300, graphTitle, showTrailTitle = false, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, diagnostic, issues, showIssues, focusIssueCategory, laneStatus, onSelectIssue, onApplyIssueFix, onHoverIssue, renderFileViewer, renderTrailViewer, onFileSelect, componentVerification, onInspectSymbol, boundaryColors, hideDrawer = false, persistKey, liveEvents, agentsPanel }) {
131
+ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrailId, onTrailSelect, onTrailStepHover, onReorderTrails, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, showSingletonFrames = true, moduleNesting, edgeView, title, hideSidebar, trailStepMode = 'focus', autoPlayTrails = false, trailAutoPlayIntervalMs = TRAIL_PLAY_PAUSE_MS, zoomOnTrailFocus = true, trailFocusDurationMs = 300, graphTitle, showTrailTitle = false, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, diagnostic, issues, showIssues, focusIssueCategory, laneStatus, onSelectIssue, onApplyIssueFix, onHoverIssue, renderFileViewer, renderTrailViewer, onFileSelect, componentVerification, onInspectSymbol, boundaryColors, codeProcessKeys, hideDrawer = false, persistKey, liveEvents, agentsPanel }) {
127
132
  const { theme } = useTheme();
128
133
  const { fitView, fitBounds, screenToFlowPosition } = useReactFlow();
129
134
  const viewport = useViewport();
@@ -261,49 +266,11 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
261
266
  const [descToggleHover, setDescToggleHover] = useState(false);
262
267
  // `true` only when a description exists AND the user opened it.
263
268
  const showDesc = !!description && descriptionVisible;
264
- // Sidebar width (px). Draggable via the resize handle between the sidebar
265
- // and the graph canvas; clamps to sensible bounds while dragging. Restored
266
- // from the persisted view state when a `persistKey` is set.
267
- const [sidebarWidth, setSidebarWidth] = useState(persisted.sidebarWidth ?? 450);
268
- const [sidebarDrag, setSidebarDrag] = useState(false);
269
- const sidebarDragStartX = useRef(0);
270
- const sidebarDragStartWidth = useRef(450);
271
- // Mirror of `sidebarWidth` for the drag-end persist (the mouseup listener
272
- // closes over the drag-start render).
273
- const sidebarWidthRef = useRef(sidebarWidth);
274
- sidebarWidthRef.current = sidebarWidth;
275
- const sidebarMinWidth = 240;
276
- const sidebarMaxWidth = useMemo(() => Math.max(Math.min((_measured?.w ?? 680) * 0.5, 600), sidebarMinWidth), [_measured?.w]);
277
- // Resize drag: capture the drag state so document-level move/up listeners
278
- // stay attached for the duration of the gesture, then release on mouseup.
279
- const onSidebarResizeStart = useCallback((e) => {
280
- e.preventDefault();
281
- sidebarDragStartX.current = e.clientX;
282
- sidebarDragStartWidth.current = sidebarWidth;
283
- setSidebarDrag(true);
284
- }, [sidebarWidth]);
285
- useEffect(() => {
286
- if (!sidebarDrag)
287
- return;
288
- const onMove = (e) => {
289
- const delta = e.clientX - sidebarDragStartX.current;
290
- const next = Math.min(Math.max(sidebarDragStartWidth.current + delta, sidebarMinWidth), sidebarMaxWidth);
291
- setSidebarWidth(next);
292
- };
293
- const onUp = () => {
294
- setSidebarDrag(false);
295
- // Persist only on drag end — not on every mousemove.
296
- writeViewState(persistKey, { sidebarWidth: sidebarWidthRef.current });
297
- // Re-fit the canvas so the graph re-centers in the new available space.
298
- requestAnimationFrame(() => fitView());
299
- };
300
- document.addEventListener('mousemove', onMove);
301
- document.addEventListener('mouseup', onUp);
302
- return () => {
303
- document.removeEventListener('mousemove', onMove);
304
- document.removeEventListener('mouseup', onUp);
305
- };
306
- }, [sidebarDrag, sidebarMaxWidth, fitView, persistKey]);
269
+ // Sidebar width lives in the shared GraphWithSidebar chrome — draggable and
270
+ // persisted under `principal.subsystems.sidebarWidth.<persistKey>`. The graph
271
+ // only bounds how wide the panel may get so the canvas keeps room to breathe;
272
+ // a width saved under the old view-state key still seeds the panel once.
273
+ const sidebarMaxWidth = useMemo(() => Math.max(Math.min((_measured?.w ?? 680) * 0.5, 600), 240), [_measured?.w]);
307
274
  // Persist the trail working set per model. Each write merges into the
308
275
  // stored blob, so the three fields never clobber one another.
309
276
  useEffect(() => {
@@ -500,6 +467,7 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
500
467
  moduleNesting,
501
468
  graphifyRelations: graphifyRelationsRef.current,
502
469
  orderByLine,
470
+ codeProcessKeys,
503
471
  })
504
472
  .then(({ nodes, edges: e, absoluteRects }) => {
505
473
  if (!alive)
@@ -550,7 +518,7 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
550
518
  const measuredWidths = new Map(leafNodes.map((n) => [n.id, dims.get(n.id).width]));
551
519
  const measuredHeights = new Map(leafNodes.map((n) => [n.id, dims.get(n.id).height]));
552
520
  const gen = ++pass2GenRef.current;
553
- void buildSubsystemGraph({ components, trails }, { maxNodeWidth, showEdgeLabels, measuredWidths, measuredHeights, showSingletonFrames, moduleNesting, graphifyRelations, orderByLine })
521
+ void buildSubsystemGraph({ components, trails }, { maxNodeWidth, showEdgeLabels, measuredWidths, measuredHeights, showSingletonFrames, moduleNesting, graphifyRelations, orderByLine, codeProcessKeys })
554
522
  .then(({ nodes, edges: e, absoluteRects }) => {
555
523
  if (gen !== pass2GenRef.current)
556
524
  return;
@@ -1281,18 +1249,19 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
1281
1249
  const onOpenDeclarationFile = useCallback((file, opts) => {
1282
1250
  setFileOverlay(null);
1283
1251
  const startLine = opts?.startLine;
1252
+ const endLine = opts?.endLine;
1284
1253
  if (openFileRef.current?.file === file &&
1285
1254
  openFileRef.current.startLine === startLine) {
1286
1255
  setDrawerTarget(null);
1287
1256
  return;
1288
1257
  }
1289
- setDrawerTarget({ kind: 'file', file, startLine });
1258
+ setDrawerTarget({ kind: 'file', file, startLine, endLine });
1290
1259
  onFileSelect?.(file);
1291
1260
  }, [onFileSelect]);
1292
1261
  // Double-click a component opens its declaration in the file drawer — the
1293
- // `file` + anchored `declarationRef.startLine` when verify resolved one,
1294
- // else the file top. Uses the same open path as the declaration panel's
1295
- // file link, so the drawer and file tree stay in sync.
1262
+ // `file` + anchored `declarationSpan` range when verify resolved one, else
1263
+ // the file top. Uses the same open path as the declaration panel's file
1264
+ // link, so the drawer and file tree stay in sync.
1296
1265
  // Boundary region currently framed by a double-click, so a second
1297
1266
  // double-click on the same boundary zooms back out to the whole graph.
1298
1267
  const [focusedBoundaryKey, setFocusedBoundaryKey] = useState(null);
@@ -1330,11 +1299,14 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
1330
1299
  const comp = node.data?.component;
1331
1300
  if (!comp?.file)
1332
1301
  return;
1333
- const startLine = comp.declarationRef?.startLine;
1334
- onOpenDeclarationFile(comp.file, startLine != null ? { startLine } : undefined);
1302
+ const startLine = comp.declarationSpan?.startLine;
1303
+ const endLine = comp.declarationSpan?.endLine;
1304
+ onOpenDeclarationFile(comp.file, startLine != null
1305
+ ? { startLine, ...(endLine != null ? { endLine } : {}) }
1306
+ : undefined);
1335
1307
  }, [onOpenDeclarationFile, fitView, focusedBoundaryKey]);
1336
1308
  const onOpenFileFromTrail = useCallback((file, opts) => {
1337
- setFileOverlay({ file, startLine: opts?.startLine });
1309
+ setFileOverlay({ file, startLine: opts?.startLine, endLine: opts?.endLine });
1338
1310
  onFileSelect?.(file);
1339
1311
  }, [onFileSelect]);
1340
1312
  const closeFileOverlay = useCallback(() => {
@@ -1506,19 +1478,22 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
1506
1478
  setFocusedStepIndex(null);
1507
1479
  setFocusedTrailId(null);
1508
1480
  setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1481
+ onTrailSelect?.(null);
1509
1482
  return;
1510
1483
  }
1511
1484
  setFocusedStepIndex(null);
1512
1485
  setFocusedTrailId(tl.id);
1486
+ onTrailSelect?.({ trailId: tl.id });
1513
1487
  fitFocusBounds(new Set(tl.steps.map((s) => trailStepGraphEdgeId(s))));
1514
1488
  setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1515
- }, [fitFocusBounds, trailStepMode]);
1489
+ }, [fitFocusBounds, trailStepMode, onTrailSelect]);
1516
1490
  const clearTrailFocus = useCallback(() => {
1517
1491
  setFocusedTrailId(null);
1518
1492
  setFocusedStepIndex(null);
1519
1493
  setHoveredTrailStep(null);
1520
1494
  setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1521
- }, []);
1495
+ onTrailSelect?.(null);
1496
+ }, [onTrailSelect]);
1522
1497
  // Host deep-link: opening the graph from a trail row selects that
1523
1498
  // flow — expand its steps, focus its edges, and switch the sidebar to
1524
1499
  // Trails. Guarded by a ref so a later in-tab selection isn't yanked
@@ -1567,6 +1542,7 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
1567
1542
  setFocusedTrailId(null);
1568
1543
  setHoveredTrailStep({ trailId: tl.id, stepIndex });
1569
1544
  setDrawerTarget((prev) => (prev?.kind === 'trail' ? null : prev));
1545
+ onTrailSelect?.(null);
1570
1546
  return;
1571
1547
  }
1572
1548
  // Toggle: clicking the selected step unselects it (back to whole flow).
@@ -1586,6 +1562,7 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
1586
1562
  setFocusedStepIndex(stepIndex);
1587
1563
  setFocusedTrailId(tl.id);
1588
1564
  setHoveredTrailStep(null);
1565
+ onTrailSelect?.({ trailId: tl.id, stepIndices: [stepIndex] });
1589
1566
  // Open the drawer before fitting. If it was closed, wait for its height
1590
1567
  // transition so fitView uses the reduced canvas — not full height.
1591
1568
  const drawerWasOpen = drawerOpenRef.current;
@@ -1600,7 +1577,8 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
1600
1577
  setDrawerTarget({
1601
1578
  kind: 'file',
1602
1579
  file: step.file,
1603
- startLine: step.line,
1580
+ startLine: step.span?.startLine,
1581
+ endLine: step.span?.endLine,
1604
1582
  });
1605
1583
  }
1606
1584
  const edgeIds = new Set([trailStepGraphEdgeId(step)]);
@@ -1623,6 +1601,7 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
1623
1601
  focusedTrailId,
1624
1602
  renderTrailViewer,
1625
1603
  trailStepMode,
1604
+ onTrailSelect,
1626
1605
  ]);
1627
1606
  useEffect(() => {
1628
1607
  return () => {
@@ -1706,7 +1685,7 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
1706
1685
  setDrawerTarget({ kind: 'trail', trailId: tl.id, stepIndex: stepIdx });
1707
1686
  }
1708
1687
  else {
1709
- setDrawerTarget({ kind: 'file', file: step.file, startLine: step.line });
1688
+ setDrawerTarget({ kind: 'file', file: step.file, startLine: step.span?.startLine, endLine: step.span?.endLine });
1710
1689
  }
1711
1690
  }
1712
1691
  }
@@ -1975,8 +1954,11 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
1975
1954
  const comp = components.find((c) => c.alias === alias);
1976
1955
  if (!comp?.file)
1977
1956
  return;
1978
- const startLine = comp.declarationRef?.startLine;
1979
- onOpenDeclarationFile(comp.file, startLine != null ? { startLine } : undefined);
1957
+ const startLine = comp.declarationSpan?.startLine;
1958
+ const endLine = comp.declarationSpan?.endLine;
1959
+ onOpenDeclarationFile(comp.file, startLine != null
1960
+ ? { startLine, ...(endLine != null ? { endLine } : {}) }
1961
+ : undefined);
1980
1962
  }, [components, onOpenDeclarationFile]);
1981
1963
  // Edge label data for the overlay (rendered OUTSIDE ReactFlow so the pane
1982
1964
  // doesn't intercept pointer events). Uses ELK-computed label midpoints from
@@ -2025,367 +2007,338 @@ function Inner({ components, trails, graphifyRelations, orderByLine, initialTrai
2025
2007
  const trailViewerRef = useRef(renderTrailViewer);
2026
2008
  trailViewerRef.current = renderTrailViewer;
2027
2009
  const renderTrailDrawerContent = useCallback((ctx) => trailViewerRef.current?.(ctx) ?? null, []);
2028
- 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 || hasTrails) && (_jsxs("div", { style: {
2029
- width: sidebarWidth,
2030
- minWidth: sidebarWidth,
2031
- borderRight: `1px solid ${theme.colors.border}`,
2032
- background: theme.colors.backgroundSecondary ?? theme.colors.background,
2010
+ // Sidebar chrome — title/description header, Files/Trails (or Issues/Agents)
2011
+ // tab strip, and the lower panels. Everything renders through the shared
2012
+ // GraphWithSidebar; the graph keeps owning every bit of *state* (focused
2013
+ // trail, active tab, description visibility) because the canvas dims from
2014
+ // the same facts the sidebar renders.
2015
+ const sidebarVisible = !hideSidebar &&
2016
+ (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription ||
2017
+ treeFilePaths.length > 0 || hasTrails);
2018
+ const sidebarBodyVisible = treeFilePaths.length > 0 || hasTrails || issuesActive;
2019
+ const sidebarTabs = issuesActive
2020
+ ? agentsPanel
2021
+ ? [
2022
+ { id: 'issues', label: 'Issues' },
2023
+ { id: 'agents', label: 'Agents' },
2024
+ ]
2025
+ : undefined
2026
+ : hasTrails
2027
+ ? [
2028
+ { id: 'trails', label: 'Trails' },
2029
+ { id: 'files', label: 'Files' },
2030
+ ]
2031
+ : undefined;
2032
+ const activeSidebarTab = issuesActive ? diagnosticsTab : sidebarView;
2033
+ // Header block: the extras, then the title/actions row (diagnostic chip +
2034
+ // description toggle), then the description that toggles out from under it.
2035
+ const sidebarHeader = (_jsxs(_Fragment, { children: [sidebarExtra, (title || description || diagnostic) && (_jsxs("div", { style: { display: 'flex', alignItems: 'flex-start', gap: 8 }, children: [title && (_jsx("h2", { style: {
2036
+ margin: 0,
2037
+ flex: 1,
2038
+ minWidth: 0,
2039
+ fontSize: theme.fontSizes[2],
2040
+ fontWeight: 600,
2041
+ color: theme.colors.text,
2042
+ fontFamily: theme.fonts.monospace,
2043
+ }, children: title })), _jsxs("div", { style: {
2044
+ display: 'flex',
2045
+ alignItems: 'center',
2046
+ gap: 4,
2047
+ flexShrink: 0,
2048
+ // Title's flex:1 already pushes this cluster right; without a
2049
+ // title, keep the actions flush right instead of flush-left.
2050
+ marginLeft: title ? undefined : 'auto',
2051
+ }, children: [diagnostic && (_jsx(SubsystemDiagnosticToggle, { ...diagnostic, active: issuesActive, onToggle: toggleIssues })), 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: {
2052
+ display: 'inline-flex',
2053
+ alignItems: 'center',
2054
+ justifyContent: 'center',
2055
+ flexShrink: 0,
2056
+ width: 22,
2057
+ height: 22,
2058
+ padding: 0,
2059
+ border: 'none',
2060
+ borderRadius: 4,
2061
+ background: descriptionVisible || descToggleHover ? theme.colors.border : 'transparent',
2062
+ color: descriptionVisible || descToggleHover ? theme.colors.text : (theme.colors.textMuted ?? theme.colors.textSecondary),
2063
+ cursor: 'pointer',
2064
+ transition: 'background 120ms ease, color 120ms ease',
2065
+ }, children: _jsx(FileText, { 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, enableKeyboardScrolling: false, autoFocusOnVisible: false }) })), sidebarAfterDescription] }));
2066
+ // Lower panel: the Issues/Agents diagnostics stack, or the Files/Trails
2067
+ // stack with the trail flow list / repo file tree.
2068
+ const sidebarBody = issuesActive ? (diagnosticsTab === 'agents' && agentsPanel ? (_jsx(SubsystemAgentsPanel, { ...agentsPanel })) : (_jsx(SubsystemIssueList, { issues: issues ?? [], focusCategory: focusIssueCategory, laneStatus: laneStatus, onSelectIssue: focusIssueTarget, onDeselectIssue: unfocusIssueTarget, onApplyFix: onApplyIssueFix, onHoverIssue: onHoverIssue, onExpandedCategoriesChange: handleExpandedCategoriesChange }))) : sidebarView === 'trails' && trails && trails.length > 0 ? (_jsx(TrailsPanel, { trails: trails, expandedTrails: expandedTrails, focusedTrailId: focusedTrailId, focusedStepIndex: focusedStepIndex, hoveredTrailStep: hoveredTrailStep, onToggleCollapsed: toggleTrailCollapsed, onFocusFlow: focusTrailEdges, onClearFocus: clearTrailFocus, onFocusStep: focusTrailStep, onHoverStep: (tl, i) => {
2069
+ setHoveredTrailStep({ trailId: tl.id, stepIndex: i });
2070
+ onTrailStepHover?.({ trailId: tl.id, stepIndex: i });
2071
+ }, onHoverFlow: (tl) => {
2072
+ setHoveredTrailStep({ trailId: tl.id, stepIndex: null });
2073
+ onTrailStepHover?.(null);
2074
+ }, onLeaveStep: () => {
2075
+ setHoveredTrailStep(null);
2076
+ onTrailStepHover?.(null);
2077
+ }, onReorder: onReorderTrails, proposedAliases: proposedAliases })) : treeFilePaths.length > 0 ? (_jsx(_Fragment, { children: repoGroups.groups.map((group, i) => {
2078
+ const groupKey = group.repoKey ?? '__no-repo__';
2079
+ const collapsed = collapsedRepos.has(groupKey);
2080
+ return (_jsxs("div", { style: {
2081
+ flex: collapsed ? '0 0 auto' : 1,
2082
+ minHeight: collapsed ? 0 : undefined,
2033
2083
  display: 'flex',
2034
2084
  flexDirection: 'column',
2035
- overflow: 'hidden',
2036
- }, children: [_jsxs("div", { style: {
2037
- flex: showDesc ? 1 : '0 0 auto',
2038
- minHeight: 0,
2039
- overflowY: 'auto',
2040
- padding: '16px',
2041
- display: 'flex',
2042
- flexDirection: 'column',
2043
- gap: 12,
2044
- }, children: [sidebarExtra, (title || description || diagnostic) && (_jsxs("div", { style: { display: 'flex', alignItems: 'flex-start', gap: 8 }, children: [title && (_jsx("h2", { style: {
2045
- margin: 0,
2046
- flex: 1,
2047
- minWidth: 0,
2048
- fontSize: theme.fontSizes[2],
2049
- fontWeight: 600,
2050
- color: theme.colors.text,
2051
- fontFamily: theme.fonts.monospace,
2052
- }, children: title })), _jsxs("div", { style: {
2053
- display: 'flex',
2054
- alignItems: 'center',
2055
- gap: 4,
2056
- flexShrink: 0,
2057
- // Title's flex:1 already pushes this cluster right; without a
2058
- // title, keep the actions flush right instead of flush-left.
2059
- marginLeft: title ? undefined : 'auto',
2060
- }, children: [diagnostic && (_jsx(SubsystemDiagnosticToggle, { ...diagnostic, active: issuesActive, onToggle: toggleIssues })), 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: {
2085
+ borderTop: i > 0 ? `1px solid ${theme.colors.border}` : undefined,
2086
+ }, 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, hoveredFolder: hoveredBoundaryPath, headerless: repoGroups.multiRepo }))] }, groupKey));
2087
+ }) })) : null;
2088
+ return (_jsx(GraphWithSidebar, { hidden: !sidebarVisible, bodyHidden: !sidebarBodyVisible, stretchHeader: showDesc, header: sidebarHeader, tabs: sidebarTabs, activeTab: activeSidebarTab, onTabChange: (id) => issuesActive
2089
+ ? setDiagnosticsTab(id)
2090
+ : changeSidebarView(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: 'column', minHeight: 0 }, children: [_jsxs("div", { style: {
2091
+ position: 'relative',
2092
+ flex: collapseCanvas ? 0 : 1,
2093
+ minHeight: 0,
2094
+ height: collapseCanvas ? 0 : undefined,
2095
+ overflow: 'hidden',
2096
+ display: 'flex',
2097
+ flexDirection: 'column',
2098
+ }, children: [showEdgeLabels !== false && (_jsx("div", { style: {
2099
+ position: 'absolute',
2100
+ inset: 0,
2101
+ overflow: 'hidden',
2102
+ pointerEvents: 'none',
2103
+ zIndex: 5,
2104
+ }, children: edgeLabels.map((lbl) => {
2105
+ const mechanism = lbl.mechanism;
2106
+ const isGraphify = lbl.provenance === 'graphify';
2107
+ const color = edgeColor({ mechanism, provenance: lbl.provenance });
2108
+ // Label chrome stays the crisp rounded box for every edge — graphify
2109
+ // provenance is carried by hue (GRAPHIFY_RELATION_COLOR), the dashed
2110
+ // stroke, and the tooltip, NOT by the "ambiguous" cloud silhouette
2111
+ // (that idiom is reserved for soft authored mechanisms).
2112
+ const verifiable = true;
2113
+ const labelTitle = isGraphify
2114
+ ? 'Derived from graphify (static symbol graph)'
2115
+ : MECHANISM_DESCRIPTIONS.find(([m]) => m === mechanism)?.[2] === false
2116
+ ? 'Not directly verifiable with graphify'
2117
+ : undefined;
2118
+ const screenX = lbl.midX * viewport.zoom + viewport.x;
2119
+ const screenY = lbl.midY * viewport.zoom + viewport.y;
2120
+ const text = lbl.stepNos?.length
2121
+ ? `${lbl.stepNos.map((n) => `${n}:`).join(' ')} ${lbl.mechanism}`
2122
+ : lbl.mechanism;
2123
+ return (_jsxs("div", { "data-edge-label": lbl.id, title: labelTitle, onClick: (e) => {
2124
+ e.stopPropagation();
2125
+ selectEdge(lbl.id);
2126
+ }, style: {
2127
+ position: 'absolute',
2128
+ left: screenX,
2129
+ top: screenY,
2130
+ // Center on the flow-space midpoint, and scale with the graph
2131
+ // (flow-space size): labels shrink as you zoom out instead of
2132
+ // staying fixed-screen-size and dominating the smaller graph.
2133
+ transform: `translate(-50%, -50%) scale(${viewport.zoom})`,
2134
+ transformOrigin: 'center center',
2135
+ display: 'flex',
2136
+ alignItems: 'center',
2137
+ justifyContent: 'center',
2138
+ width: EDGE_LABEL_WIDTH,
2139
+ height: EDGE_LABEL_HEIGHT,
2140
+ boxSizing: 'border-box',
2141
+ padding: '7px 8px',
2142
+ cursor: 'pointer',
2143
+ pointerEvents: 'auto',
2144
+ opacity: lbl.dimmed ? 0.15 : 1,
2145
+ }, children: [verifiable ? (_jsx("span", { "aria-hidden": true, style: {
2146
+ position: 'absolute',
2147
+ inset: 0,
2148
+ borderRadius: 4,
2149
+ border: lbl.proposed
2150
+ ? `1.5px dashed ${color}`
2151
+ : `0.5px solid ${color}`,
2152
+ background: theme.colors.backgroundSecondary ?? theme.colors.background,
2153
+ pointerEvents: 'none',
2154
+ } })) : (_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: {
2155
+ position: 'absolute',
2156
+ left: 0,
2157
+ top: -EDGE_LABEL_CLOUD_EXTRA_TOP,
2158
+ pointerEvents: 'none',
2159
+ }, 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", strokeDasharray: lbl.proposed ? '4 3' : undefined }) })), _jsx("span", { style: {
2160
+ position: 'relative',
2161
+ display: 'block',
2162
+ maxWidth: '100%',
2163
+ overflow: 'hidden',
2164
+ textOverflow: 'ellipsis',
2165
+ whiteSpace: 'nowrap',
2166
+ fontSize: EDGE_LABEL_FONT_SIZE,
2167
+ lineHeight: 1,
2168
+ fontFamily: theme.fonts.monospace,
2169
+ fontWeight: 500,
2170
+ color,
2171
+ }, children: text }), lbl.stepIssue && (() => {
2172
+ const StepIssueIcon = ISSUE_KIND_ICON[lbl.stepIssue.kind];
2173
+ // Match IssueList: error severity = red, info severity = amber
2174
+ const issueColor = lbl.stepIssue.severity === 'error'
2175
+ ? (theme.colors.error ?? '#e5534b')
2176
+ : (theme.colors.warning ?? '#d4a017');
2177
+ const badgeBg = theme.colors.backgroundSecondary ?? theme.colors.background;
2178
+ return (_jsxs("span", { "aria-hidden": true, title: lbl.stepIssue.kind === 'step_stale'
2179
+ ? 'Call site changed since verification'
2180
+ : 'Call site not yet verified', style: {
2181
+ position: 'absolute',
2182
+ right: -7,
2183
+ bottom: -6,
2184
+ zIndex: 2,
2061
2185
  display: 'inline-flex',
2062
2186
  alignItems: 'center',
2063
2187
  justifyContent: 'center',
2064
- flexShrink: 0,
2065
- width: 22,
2066
- height: 22,
2067
- padding: 0,
2068
- border: 'none',
2188
+ gap: 3,
2189
+ minWidth: 18,
2190
+ padding: '2px 4px',
2069
2191
  borderRadius: 4,
2070
- background: descriptionVisible || descToggleHover ? theme.colors.border : 'transparent',
2071
- color: descriptionVisible || descToggleHover ? theme.colors.text : (theme.colors.textMuted ?? theme.colors.textSecondary),
2072
- cursor: 'pointer',
2073
- transition: 'background 120ms ease, color 120ms ease',
2074
- }, children: _jsx(FileText, { 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, enableKeyboardScrolling: false, autoFocusOnVisible: false }) })), sidebarAfterDescription] }), (treeFilePaths.length > 0 || hasTrails || issuesActive) && (_jsx("div", { style: {
2075
- ...(showDesc ? { height: '50%' } : { flex: 1, minHeight: 0 }),
2076
- minHeight: 160,
2077
- flexShrink: 0,
2078
- borderTop: `1px solid ${theme.colors.border}`,
2079
- display: 'flex',
2080
- flexDirection: 'column',
2081
- overflow: 'hidden',
2082
- }, children: issuesActive ? (agentsPanel ? (_jsxs("div", { style: { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }, children: [_jsx("div", { role: "tablist", "aria-label": "Diagnostics view", style: {
2083
- display: 'flex',
2084
- width: '100%',
2085
- flexShrink: 0,
2086
- borderBottom: `1px solid ${theme.colors.border}`,
2192
+ border: `1.5px solid ${issueColor}`,
2193
+ background: badgeBg,
2194
+ pointerEvents: 'none',
2195
+ }, children: [StepIssueIcon && (_jsx(StepIssueIcon, { size: 12, color: issueColor })), lbl.stepIssue.count > 1 && (_jsx("span", { style: {
2196
+ color: issueColor,
2197
+ fontFamily: theme.fonts.monospace,
2198
+ fontSize: 10,
2199
+ fontWeight: 700,
2200
+ lineHeight: 1,
2201
+ }, children: lbl.stepIssue.count }))] }));
2202
+ })()] }, lbl.id));
2203
+ }) })), _jsx(GraphLayerStyle, {}), _jsx(ReactFlow, { nodes: dispNodes, edges: dispEdges, nodeTypes: nodeTypes, edgeTypes: edgeTypes, className: GRAPH_CANVAS_CLASS, ...GRAPH_NAV_PROPS, onNodeClick: onNodeClick, onNodeDoubleClick: onNodeDoubleClick, onEdgeClick: onEdgeClick, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, proOptions: { hideAttribution: true }, onPaneClick: onPaneClick, style: {
2204
+ width: '100%',
2205
+ height: '100%',
2206
+ flex: 1,
2207
+ minHeight: 0,
2208
+ background: theme.colors.background,
2209
+ }, children: _jsx(GraphChrome, {}) }, `${baseNodesKey}-${baseEdgesKey}`), (graphTitle || overlayTrailTitle || overlayTrailStep) && (_jsxs("div", { style: {
2210
+ position: 'absolute',
2211
+ top: 10,
2212
+ left: '50%',
2213
+ transform: 'translateX(-50%)',
2214
+ zIndex: 6,
2215
+ display: 'flex',
2216
+ flexDirection: 'column',
2217
+ alignItems: 'center',
2218
+ gap: 6,
2219
+ maxWidth: '70%',
2220
+ pointerEvents: 'none',
2221
+ }, children: [graphTitle && (_jsx("div", { style: {
2222
+ maxWidth: '100%',
2223
+ overflow: 'hidden',
2224
+ textOverflow: 'ellipsis',
2225
+ whiteSpace: 'nowrap',
2226
+ padding: '6px 18px',
2227
+ fontSize: theme.fontSizes[3],
2228
+ fontWeight: 600,
2229
+ fontFamily: theme.fonts.monospace,
2230
+ color: theme.colors.text,
2087
2231
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
2088
- }, children: ['issues', 'agents'].map((tab) => (_jsx("button", { type: "button", role: "tab", "aria-selected": diagnosticsTab === tab, onClick: () => setDiagnosticsTab(tab), style: {
2089
- flex: 1,
2090
- minWidth: 0,
2091
- padding: '8px 8px',
2092
- border: 'none',
2093
- borderRadius: 0,
2094
- background: diagnosticsTab === tab ? theme.colors.background : 'transparent',
2095
- color: diagnosticsTab === tab ? theme.colors.text : theme.colors.textSecondary,
2096
- fontSize: theme.fontSizes[1],
2097
- fontFamily: theme.fonts.monospace,
2098
- textTransform: 'capitalize',
2099
- cursor: 'pointer',
2100
- }, children: tab === 'issues' ? 'Issues' : 'Agents' }, tab))) }), diagnosticsTab === 'agents' ? (_jsx(SubsystemAgentsPanel, { ...agentsPanel })) : (_jsx(SubsystemIssueList, { issues: issues ?? [], focusCategory: focusIssueCategory, laneStatus: laneStatus, onSelectIssue: focusIssueTarget, onDeselectIssue: unfocusIssueTarget, onApplyFix: onApplyIssueFix, onHoverIssue: onHoverIssue, onExpandedCategoriesChange: handleExpandedCategoriesChange }))] })) : (_jsx(SubsystemIssueList, { issues: issues ?? [], focusCategory: focusIssueCategory, laneStatus: laneStatus, onSelectIssue: focusIssueTarget, onDeselectIssue: unfocusIssueTarget, onApplyFix: onApplyIssueFix, onHoverIssue: onHoverIssue, onExpandedCategoriesChange: handleExpandedCategoriesChange }))) : (_jsxs(_Fragment, { children: [hasTrails && (_jsx("div", { role: "tablist", "aria-label": "Sidebar view", style: {
2232
+ border: `1px solid ${theme.colors.border}`,
2233
+ borderRadius: 6,
2234
+ boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
2235
+ }, children: graphTitle })), overlayTrailTitle && (_jsxs("div", { style: {
2236
+ maxWidth: '100%',
2237
+ minWidth: overlayTrailStep ? 160 : undefined,
2101
2238
  display: 'flex',
2102
- width: '100%',
2103
- flexShrink: 0,
2104
- borderBottom: `1px solid ${theme.colors.border}`,
2239
+ flexDirection: 'column',
2105
2240
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
2106
- }, children: ['trails', 'files'].map((view) => (_jsx("button", { type: "button", role: "tab", "aria-selected": sidebarView === view, onClick: () => changeSidebarView(view), style: {
2107
- flex: 1,
2108
- minWidth: 0,
2109
- padding: '8px 8px',
2110
- border: 'none',
2111
- borderRadius: 0,
2112
- background: sidebarView === view ? theme.colors.background : 'transparent',
2113
- color: sidebarView === view
2114
- ? theme.colors.text
2115
- : theme.colors.textSecondary,
2116
- fontSize: theme.fontSizes[1],
2117
- fontFamily: theme.fonts.monospace,
2118
- textTransform: 'capitalize',
2119
- cursor: 'pointer',
2120
- }, children: view === 'trails' ? 'Trails' : 'Files' }, view))) })), sidebarView === 'trails' && trails && trails.length > 0 ? (_jsx(TrailsPanel, { trails: trails, expandedTrails: expandedTrails, focusedTrailId: focusedTrailId, focusedStepIndex: focusedStepIndex, hoveredTrailStep: hoveredTrailStep, onToggleCollapsed: toggleTrailCollapsed, onFocusFlow: focusTrailEdges, onClearFocus: clearTrailFocus, onFocusStep: focusTrailStep, onHoverStep: (tl, i) => setHoveredTrailStep({ trailId: tl.id, stepIndex: i }), onHoverFlow: (tl) => setHoveredTrailStep({ trailId: tl.id, stepIndex: null }), onLeaveStep: () => setHoveredTrailStep(null), onReorder: onReorderTrails, proposedAliases: proposedAliases })) : treeFilePaths.length > 0 ? (_jsx(_Fragment, { children: repoGroups.groups.map((group, i) => {
2121
- const groupKey = group.repoKey ?? '__no-repo__';
2122
- const collapsed = collapsedRepos.has(groupKey);
2123
- return (_jsxs("div", { style: {
2124
- flex: collapsed ? '0 0 auto' : 1,
2125
- minHeight: collapsed ? 0 : undefined,
2241
+ border: `1px solid ${theme.colors.border}`,
2242
+ borderRadius: 6,
2243
+ boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
2244
+ overflow: 'hidden',
2245
+ opacity: 0.95,
2246
+ }, "aria-label": overlayTrailStep
2247
+ ? `${overlayTrailTitle}, step ${overlayTrailStep.index} of ${overlayTrailStep.total}`
2248
+ : overlayTrailTitle, children: [_jsx("div", { style: {
2249
+ padding: '5px 14px',
2250
+ fontSize: theme.fontSizes[2] ?? theme.fontSizes[1],
2251
+ fontWeight: 600,
2252
+ fontFamily: theme.fonts.monospace,
2253
+ color: theme.colors.text,
2254
+ overflow: 'hidden',
2255
+ textOverflow: 'ellipsis',
2256
+ whiteSpace: 'nowrap',
2257
+ textAlign: 'center',
2258
+ }, children: overlayTrailTitle }), overlayTrailStep && overlayTrailStep.total > 0 && (_jsx("div", { style: {
2126
2259
  display: 'flex',
2127
- flexDirection: 'column',
2128
- borderTop: i > 0 ? `1px solid ${theme.colors.border}` : undefined,
2129
- }, 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, hoveredFolder: hoveredBoundaryPath, headerless: repoGroups.multiRepo }))] }, groupKey));
2130
- }) })) : null] })) }))] })), !hideSidebar && (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription || treeFilePaths.length > 0 || hasTrails) && (_jsx("div", { onMouseDown: onSidebarResizeStart, "aria-label": "Resize sidebar", title: "Drag to resize", style: {
2131
- width: 3,
2132
- flexShrink: 0,
2133
- cursor: 'col-resize',
2134
- background: theme.colors.border,
2135
- transition: 'background 120ms ease',
2136
- zIndex: 1,
2137
- } })), _jsxs("div", { style: { flex: 1, position: 'relative', display: 'flex', flexDirection: 'column', minHeight: 0 }, children: [_jsxs("div", { style: {
2138
- position: 'relative',
2139
- flex: collapseCanvas ? 0 : 1,
2140
- minHeight: 0,
2141
- height: collapseCanvas ? 0 : undefined,
2142
- overflow: 'hidden',
2143
- display: 'flex',
2144
- flexDirection: 'column',
2145
- }, children: [showEdgeLabels !== false && (_jsx("div", { style: {
2146
- position: 'absolute',
2147
- inset: 0,
2148
- overflow: 'hidden',
2149
- pointerEvents: 'none',
2150
- zIndex: 5,
2151
- }, children: edgeLabels.map((lbl) => {
2152
- const mechanism = lbl.mechanism;
2153
- const isGraphify = lbl.provenance === 'graphify';
2154
- const color = edgeColor({ mechanism, provenance: lbl.provenance });
2155
- // Label chrome stays the crisp rounded box for every edge — graphify
2156
- // provenance is carried by hue (GRAPHIFY_RELATION_COLOR), the dashed
2157
- // stroke, and the tooltip, NOT by the "ambiguous" cloud silhouette
2158
- // (that idiom is reserved for soft authored mechanisms).
2159
- const verifiable = true;
2160
- const labelTitle = isGraphify
2161
- ? 'Derived from graphify (static symbol graph)'
2162
- : MECHANISM_DESCRIPTIONS.find(([m]) => m === mechanism)?.[2] === false
2163
- ? 'Not directly verifiable with graphify'
2164
- : undefined;
2165
- const screenX = lbl.midX * viewport.zoom + viewport.x;
2166
- const screenY = lbl.midY * viewport.zoom + viewport.y;
2167
- const text = lbl.stepNos?.length
2168
- ? `${lbl.stepNos.map((n) => `${n}:`).join(' ')} ${lbl.mechanism}`
2169
- : lbl.mechanism;
2170
- return (_jsxs("div", { "data-edge-label": lbl.id, title: labelTitle, onClick: (e) => {
2171
- e.stopPropagation();
2172
- selectEdge(lbl.id);
2173
- }, style: {
2174
- position: 'absolute',
2175
- left: screenX,
2176
- top: screenY,
2177
- // Center on the flow-space midpoint, and scale with the graph
2178
- // (flow-space size): labels shrink as you zoom out instead of
2179
- // staying fixed-screen-size and dominating the smaller graph.
2180
- transform: `translate(-50%, -50%) scale(${viewport.zoom})`,
2181
- transformOrigin: 'center center',
2182
- display: 'flex',
2183
- alignItems: 'center',
2184
- justifyContent: 'center',
2185
- width: EDGE_LABEL_WIDTH,
2186
- height: EDGE_LABEL_HEIGHT,
2187
- boxSizing: 'border-box',
2188
- padding: '7px 8px',
2189
- cursor: 'pointer',
2190
- pointerEvents: 'auto',
2191
- opacity: lbl.dimmed ? 0.15 : 1,
2192
- }, children: [verifiable ? (_jsx("span", { "aria-hidden": true, style: {
2193
- position: 'absolute',
2194
- inset: 0,
2195
- borderRadius: 4,
2196
- border: lbl.proposed
2197
- ? `1.5px dashed ${color}`
2198
- : `0.5px solid ${color}`,
2199
- background: theme.colors.backgroundSecondary ?? theme.colors.background,
2200
- pointerEvents: 'none',
2201
- } })) : (_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: {
2202
- position: 'absolute',
2203
- left: 0,
2204
- top: -EDGE_LABEL_CLOUD_EXTRA_TOP,
2205
- pointerEvents: 'none',
2206
- }, 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", strokeDasharray: lbl.proposed ? '4 3' : undefined }) })), _jsx("span", { style: {
2207
- position: 'relative',
2208
- display: 'block',
2209
- maxWidth: '100%',
2210
- overflow: 'hidden',
2211
- textOverflow: 'ellipsis',
2212
- whiteSpace: 'nowrap',
2213
- fontSize: EDGE_LABEL_FONT_SIZE,
2214
- lineHeight: 1,
2215
- fontFamily: theme.fonts.monospace,
2216
- fontWeight: 500,
2217
- color,
2218
- }, children: text }), lbl.stepIssue && (() => {
2219
- const StepIssueIcon = ISSUE_KIND_ICON[lbl.stepIssue.kind];
2220
- // Match IssueList: error severity = red, info severity = amber
2221
- const issueColor = lbl.stepIssue.severity === 'error'
2222
- ? (theme.colors.error ?? '#e5534b')
2223
- : (theme.colors.warning ?? '#d4a017');
2224
- const badgeBg = theme.colors.backgroundSecondary ?? theme.colors.background;
2225
- return (_jsxs("span", { "aria-hidden": true, title: lbl.stepIssue.kind === 'step_stale'
2226
- ? 'Call site changed since verification'
2227
- : 'Call site not yet verified', style: {
2228
- position: 'absolute',
2229
- right: -7,
2230
- bottom: -6,
2231
- zIndex: 2,
2232
- display: 'inline-flex',
2233
- alignItems: 'center',
2234
- justifyContent: 'center',
2235
- gap: 3,
2236
- minWidth: 18,
2237
- padding: '2px 4px',
2238
- borderRadius: 4,
2239
- border: `1.5px solid ${issueColor}`,
2240
- background: badgeBg,
2241
- pointerEvents: 'none',
2242
- }, children: [StepIssueIcon && (_jsx(StepIssueIcon, { size: 12, color: issueColor })), lbl.stepIssue.count > 1 && (_jsx("span", { style: {
2243
- color: issueColor,
2244
- fontFamily: theme.fonts.monospace,
2245
- fontSize: 10,
2246
- fontWeight: 700,
2247
- lineHeight: 1,
2248
- }, children: lbl.stepIssue.count }))] }));
2249
- })()] }, lbl.id));
2250
- }) })), _jsx(GraphLayerStyle, {}), _jsx(ReactFlow, { nodes: dispNodes, edges: dispEdges, nodeTypes: nodeTypes, edgeTypes: edgeTypes, className: GRAPH_CANVAS_CLASS, ...GRAPH_NAV_PROPS, onNodeClick: onNodeClick, onNodeDoubleClick: onNodeDoubleClick, onEdgeClick: onEdgeClick, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, proOptions: { hideAttribution: true }, onPaneClick: onPaneClick, style: {
2251
- width: '100%',
2252
- height: '100%',
2253
- flex: 1,
2254
- minHeight: 0,
2255
- background: theme.colors.background,
2256
- }, children: _jsx(GraphChrome, {}) }, `${baseNodesKey}-${baseEdgesKey}`), (graphTitle || overlayTrailTitle || overlayTrailStep) && (_jsxs("div", { style: {
2257
- position: 'absolute',
2258
- top: 10,
2259
- left: '50%',
2260
- transform: 'translateX(-50%)',
2261
- zIndex: 6,
2262
- display: 'flex',
2263
- flexDirection: 'column',
2264
- alignItems: 'center',
2265
- gap: 6,
2266
- maxWidth: '70%',
2267
- pointerEvents: 'none',
2268
- }, children: [graphTitle && (_jsx("div", { style: {
2269
- maxWidth: '100%',
2270
- overflow: 'hidden',
2271
- textOverflow: 'ellipsis',
2272
- whiteSpace: 'nowrap',
2273
- padding: '6px 18px',
2274
- fontSize: theme.fontSizes[3],
2275
- fontWeight: 600,
2276
- fontFamily: theme.fonts.monospace,
2277
- color: theme.colors.text,
2278
- background: theme.colors.backgroundSecondary ?? theme.colors.background,
2279
- border: `1px solid ${theme.colors.border}`,
2280
- borderRadius: 6,
2281
- boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
2282
- }, children: graphTitle })), overlayTrailTitle && (_jsxs("div", { style: {
2283
- maxWidth: '100%',
2284
- minWidth: overlayTrailStep ? 160 : undefined,
2285
- display: 'flex',
2286
- flexDirection: 'column',
2287
- background: theme.colors.backgroundSecondary ?? theme.colors.background,
2288
- border: `1px solid ${theme.colors.border}`,
2289
- borderRadius: 6,
2290
- boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
2291
- overflow: 'hidden',
2292
- opacity: 0.95,
2293
- }, "aria-label": overlayTrailStep
2294
- ? `${overlayTrailTitle}, step ${overlayTrailStep.index} of ${overlayTrailStep.total}`
2295
- : overlayTrailTitle, children: [_jsx("div", { style: {
2296
- padding: '5px 14px',
2297
- fontSize: theme.fontSizes[2] ?? theme.fontSizes[1],
2298
- fontWeight: 600,
2299
- fontFamily: theme.fonts.monospace,
2300
- color: theme.colors.text,
2301
- overflow: 'hidden',
2302
- textOverflow: 'ellipsis',
2303
- whiteSpace: 'nowrap',
2304
- textAlign: 'center',
2305
- }, children: overlayTrailTitle }), overlayTrailStep && overlayTrailStep.total > 0 && (_jsx("div", { style: {
2306
- display: 'flex',
2307
- gap: 3,
2308
- padding: '0 6px 5px',
2309
- }, "aria-hidden": "true", children: Array.from({ length: overlayTrailStep.total }, (_, i) => {
2310
- const n = i + 1;
2311
- const active = n === overlayTrailStep.index;
2312
- const done = n < overlayTrailStep.index;
2313
- return (_jsx("span", { style: {
2314
- flex: 1,
2315
- height: 2,
2316
- borderRadius: 1,
2317
- background: active
2318
- ? (theme.colors.accent ?? theme.colors.primary ?? theme.colors.text)
2319
- : done
2320
- ? (theme.colors.textSecondary ?? theme.colors.text)
2321
- : (theme.colors.border ?? 'rgba(127,127,127,0.45)'),
2322
- opacity: active ? 1 : done ? 0.75 : 0.4,
2323
- transition: 'opacity 120ms ease, background 120ms ease',
2324
- } }, n));
2325
- }) }))] })), overlayTrailStep?.annotation && (_jsx("div", { style: {
2326
- maxWidth: '100%',
2327
- padding: '6px 14px',
2328
- background: theme.colors.backgroundSecondary ?? theme.colors.background,
2329
- border: `1px solid ${theme.colors.border}`,
2330
- borderRadius: 6,
2331
- boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
2332
- textAlign: 'center',
2333
- fontSize: theme.fontSizes[0],
2334
- fontFamily: theme.fonts.body,
2335
- color: theme.colors.textMuted ?? theme.colors.textSecondary,
2336
- lineHeight: 1.35,
2337
- }, children: overlayTrailStep.annotation }))] })), selected && (_jsx("div", { style: {
2338
- position: 'absolute',
2339
- top: 10,
2340
- right: 10,
2341
- zIndex: 8,
2342
- width: 'min(560px, 70%)',
2343
- maxHeight: '46%',
2344
- overflowY: 'auto',
2345
- background: theme.colors.background,
2346
- border: `1px solid ${theme.colors.border}`,
2347
- borderRadius: 8,
2348
- boxShadow: '0 8px 24px rgba(0,0,0,0.35)',
2349
- }, children: _jsx(ComponentDeclaration, { component: selected, onOpenFile: onOpenDeclarationFile, onRelatedSelect: resolveRelatedComponent, onInspectSymbol: onInspectSymbol
2350
- ? (symbol, ref) => onInspectSymbol({
2351
- purl: selected.purl,
2352
- file: selected.file,
2353
- symbol,
2354
- ref,
2355
- })
2356
- : undefined, verification: componentVerification, fileOpen: drawerTarget?.kind === 'file' &&
2357
- !!selected.file &&
2358
- selected.file === drawerTarget.file, declarationOpen: drawerTarget?.kind === 'file' &&
2359
- drawerTarget.startLine != null &&
2360
- !!selected.file &&
2361
- selected.file === drawerTarget.file &&
2362
- selected.declarationRef?.startLine === drawerTarget.startLine }) }))] }), _jsx(FileDrawer, { title: drawerTitle, onClose: closeDrawer, fillHeight: drawerFillHeight, suppressEscape: fileOverlayOpen, hidden: hideDrawer, progress: drawerProgress, children: drawerTarget?.kind === 'trail' &&
2363
- focusedTrail &&
2364
- renderTrailViewer ? (_jsx(TrailDrawerContent, { render: renderTrailDrawerContent, trail: focusedTrail, stepIndex: drawerTarget.stepIndex, onOpenFile: onOpenFileFromTrail, proposedAliases: proposedAliases, resolveSymbol: resolveTrailSymbol, onSymbolClick: openConstructDeclaration, onVisibleStepChange: setVisibleStepIndex })) : drawerTarget?.kind === 'file' ? (_jsx(FileDrawerContent, { render: renderDrawerContent, file: drawerTarget.file, startLine: drawerTarget.startLine })) : null }), fileOverlay && (_jsxs("div", { style: {
2365
- position: 'absolute',
2366
- inset: 0,
2367
- zIndex: 8,
2368
- display: 'flex',
2369
- flexDirection: 'column',
2370
- background: theme.colors.background,
2371
- borderLeft: `1px solid ${theme.colors.border}`,
2372
- }, children: [_jsxs("div", { style: {
2373
- display: 'flex',
2374
- alignItems: 'center',
2375
- gap: 8,
2376
- padding: '6px 10px',
2377
- borderBottom: `1px solid ${theme.colors.border}`,
2378
- flexShrink: 0,
2379
- }, children: [_jsx("span", { title: fileOverlay.file, style: {
2380
- flex: 1,
2381
- minWidth: 0,
2382
- overflow: 'hidden',
2383
- textOverflow: 'ellipsis',
2384
- whiteSpace: 'nowrap',
2385
- fontFamily: theme.fonts.monospace,
2386
- fontSize: theme.fontSizes[0],
2387
- color: theme.colors.textMuted ?? theme.colors.textSecondary,
2388
- }, 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, fullFile: true }) })] })), _jsx(GraphLayoutCover, { revealed: layoutReady }), canvasOverlay, liveEvents ? _jsx(MaintainLivePanel, { ...liveEvents }) : null] })] }));
2260
+ gap: 3,
2261
+ padding: '0 6px 5px',
2262
+ }, "aria-hidden": "true", children: Array.from({ length: overlayTrailStep.total }, (_, i) => {
2263
+ const n = i + 1;
2264
+ const active = n === overlayTrailStep.index;
2265
+ const done = n < overlayTrailStep.index;
2266
+ return (_jsx("span", { style: {
2267
+ flex: 1,
2268
+ height: 2,
2269
+ borderRadius: 1,
2270
+ background: active
2271
+ ? (theme.colors.accent ?? theme.colors.primary ?? theme.colors.text)
2272
+ : done
2273
+ ? (theme.colors.textSecondary ?? theme.colors.text)
2274
+ : (theme.colors.border ?? 'rgba(127,127,127,0.45)'),
2275
+ opacity: active ? 1 : done ? 0.75 : 0.4,
2276
+ transition: 'opacity 120ms ease, background 120ms ease',
2277
+ } }, n));
2278
+ }) }))] })), overlayTrailStep?.annotation && (_jsx("div", { style: {
2279
+ maxWidth: '100%',
2280
+ padding: '6px 14px',
2281
+ background: theme.colors.backgroundSecondary ?? theme.colors.background,
2282
+ border: `1px solid ${theme.colors.border}`,
2283
+ borderRadius: 6,
2284
+ boxShadow: '0 1px 4px rgba(0,0,0,0.25)',
2285
+ textAlign: 'center',
2286
+ fontSize: theme.fontSizes[0],
2287
+ fontFamily: theme.fonts.body,
2288
+ color: theme.colors.textMuted ?? theme.colors.textSecondary,
2289
+ lineHeight: 1.35,
2290
+ }, children: overlayTrailStep.annotation }))] })), selected && (_jsx("div", { style: {
2291
+ position: 'absolute',
2292
+ top: 10,
2293
+ right: 10,
2294
+ zIndex: 8,
2295
+ width: 'min(560px, 70%)',
2296
+ maxHeight: '46%',
2297
+ overflowY: 'auto',
2298
+ background: theme.colors.background,
2299
+ border: `1px solid ${theme.colors.border}`,
2300
+ borderRadius: 8,
2301
+ boxShadow: '0 8px 24px rgba(0,0,0,0.35)',
2302
+ }, children: _jsx(ComponentDeclaration, { component: selected, onOpenFile: onOpenDeclarationFile, onRelatedSelect: resolveRelatedComponent, onInspectSymbol: onInspectSymbol
2303
+ ? (symbol, ref) => onInspectSymbol({
2304
+ purl: selected.purl,
2305
+ file: selected.file,
2306
+ symbol,
2307
+ ref,
2308
+ })
2309
+ : undefined, verification: componentVerification, fileOpen: drawerTarget?.kind === 'file' &&
2310
+ !!selected.file &&
2311
+ selected.file === drawerTarget.file, declarationOpen: drawerTarget?.kind === 'file' &&
2312
+ drawerTarget.startLine != null &&
2313
+ !!selected.file &&
2314
+ selected.file === drawerTarget.file &&
2315
+ selected.declarationSpan?.startLine === drawerTarget.startLine }) }))] }), _jsx(FileDrawer, { title: drawerTitle, onClose: closeDrawer, fillHeight: drawerFillHeight, suppressEscape: fileOverlayOpen, hidden: hideDrawer, progress: drawerProgress, children: drawerTarget?.kind === 'trail' &&
2316
+ focusedTrail &&
2317
+ renderTrailViewer ? (_jsx(TrailDrawerContent, { render: renderTrailDrawerContent, trail: focusedTrail, stepIndex: drawerTarget.stepIndex, onOpenFile: onOpenFileFromTrail, proposedAliases: proposedAliases, resolveSymbol: resolveTrailSymbol, onSymbolClick: openConstructDeclaration, onVisibleStepChange: setVisibleStepIndex })) : drawerTarget?.kind === 'file' ? (_jsx(FileDrawerContent, { render: renderDrawerContent, file: drawerTarget.file, startLine: drawerTarget.startLine, endLine: drawerTarget.endLine })) : null }), fileOverlay && (_jsxs("div", { style: {
2318
+ position: 'absolute',
2319
+ inset: 0,
2320
+ zIndex: 8,
2321
+ display: 'flex',
2322
+ flexDirection: 'column',
2323
+ background: theme.colors.background,
2324
+ borderLeft: `1px solid ${theme.colors.border}`,
2325
+ }, children: [_jsxs("div", { style: {
2326
+ display: 'flex',
2327
+ alignItems: 'center',
2328
+ gap: 8,
2329
+ padding: '6px 10px',
2330
+ borderBottom: `1px solid ${theme.colors.border}`,
2331
+ flexShrink: 0,
2332
+ }, children: [_jsx("span", { title: fileOverlay.file, style: {
2333
+ flex: 1,
2334
+ minWidth: 0,
2335
+ overflow: 'hidden',
2336
+ textOverflow: 'ellipsis',
2337
+ whiteSpace: 'nowrap',
2338
+ fontFamily: theme.fonts.monospace,
2339
+ fontSize: theme.fontSizes[0],
2340
+ color: theme.colors.textMuted ?? theme.colors.textSecondary,
2341
+ }, 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 }) })] })), _jsx(GraphLayoutCover, { revealed: layoutReady }), canvasOverlay, liveEvents ? _jsx(MaintainLivePanel, { ...liveEvents }) : null] }) }));
2389
2342
  }
2390
2343
  /** Sidebar header for one repo's tree: owner avatar + repo name + file count.
2391
2344
  * Clicking toggles the tree's visibility. */