@principal-ai/subsystems-react 0.56.2 → 0.56.4

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 (56) 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/DecorationPanel.d.ts +37 -0
  6. package/dist/subsystem/DecorationPanel.d.ts.map +1 -0
  7. package/dist/subsystem/DecorationPanel.js +147 -0
  8. package/dist/subsystem/DecorationPanel.js.map +1 -0
  9. package/dist/subsystem/DiagnosticToggle.d.ts +7 -1
  10. package/dist/subsystem/DiagnosticToggle.d.ts.map +1 -1
  11. package/dist/subsystem/DiagnosticToggle.js +8 -7
  12. package/dist/subsystem/DiagnosticToggle.js.map +1 -1
  13. package/dist/subsystem/FileDrawer.d.ts +3 -1
  14. package/dist/subsystem/FileDrawer.d.ts.map +1 -1
  15. package/dist/subsystem/FileDrawer.js +74 -8
  16. package/dist/subsystem/FileDrawer.js.map +1 -1
  17. package/dist/subsystem/GraphSidebar.d.ts +4 -2
  18. package/dist/subsystem/GraphSidebar.d.ts.map +1 -1
  19. package/dist/subsystem/GraphSidebar.js +3 -1
  20. package/dist/subsystem/GraphSidebar.js.map +1 -1
  21. package/dist/subsystem/SubsystemComponentGraph.d.ts +10 -13
  22. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  23. package/dist/subsystem/SubsystemComponentGraph.js +112 -272
  24. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  25. package/dist/subsystem/TrailStepsNav.d.ts.map +1 -1
  26. package/dist/subsystem/TrailStepsNav.js +6 -4
  27. package/dist/subsystem/TrailStepsNav.js.map +1 -1
  28. package/dist/subsystem/TrailsPanel.d.ts.map +1 -1
  29. package/dist/subsystem/TrailsPanel.js +14 -10
  30. package/dist/subsystem/TrailsPanel.js.map +1 -1
  31. package/dist/subsystem/decoration.d.ts +112 -0
  32. package/dist/subsystem/decoration.d.ts.map +1 -0
  33. package/dist/subsystem/decoration.js +54 -0
  34. package/dist/subsystem/decoration.js.map +1 -0
  35. package/package.json +3 -3
  36. package/src/index.ts +37 -0
  37. package/src/stories/Canvases/C4/C4ToComponentDrill.stories.tsx +38 -71
  38. package/src/stories/Canvases/Subsystem/GraphTitle.stories.tsx +1 -2
  39. package/src/stories/Canvases/Subsystem/StepEmphasis.stories.tsx +528 -0
  40. package/src/stories/Components/DecorationPanel.stories.tsx +160 -0
  41. package/src/subsystem/DecorationPanel.test.tsx +128 -0
  42. package/src/subsystem/DecorationPanel.tsx +334 -0
  43. package/src/subsystem/DiagnosticToggle.tsx +20 -5
  44. package/src/subsystem/FileDrawer.test.tsx +103 -0
  45. package/src/subsystem/FileDrawer.tsx +85 -6
  46. package/src/subsystem/GraphSidebar.test.tsx +18 -0
  47. package/src/subsystem/GraphSidebar.tsx +6 -1
  48. package/src/subsystem/SubsystemComponentGraph.sidebar.test.tsx +210 -0
  49. package/src/subsystem/SubsystemComponentGraph.tsx +185 -414
  50. package/src/subsystem/TrailStepsNav.test.tsx +32 -0
  51. package/src/subsystem/TrailStepsNav.tsx +6 -4
  52. package/src/subsystem/TrailsPanel.test.tsx +20 -0
  53. package/src/subsystem/TrailsPanel.tsx +11 -1
  54. package/src/subsystem/decoration.test.ts +72 -0
  55. package/src/subsystem/decoration.ts +151 -0
  56. package/src/subsystem/stepEmphasis.test.ts +110 -0
@@ -16,7 +16,7 @@
16
16
  */
17
17
 
18
18
  import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
19
- import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
19
+ import type { CSSProperties, MouseEvent as ReactMouseEvent, ReactNode } from 'react';
20
20
  import {
21
21
  ReactFlow,
22
22
  ReactFlowProvider,
@@ -32,7 +32,7 @@ import {
32
32
  applyNodeChanges,
33
33
  } from '@xyflow/react';
34
34
  import { useTheme } from '@principal-ade/industry-theme';
35
- import { FileText, ArrowDown, ArrowRight, Unlink, X } from 'lucide-react';
35
+ import { ArrowDown, ArrowRight, Files, Route, Unlink, X } from 'lucide-react';
36
36
  import { IndustryMarkdownSlide } from 'themed-markdown';
37
37
  import {
38
38
  buildSubsystemGraph,
@@ -80,8 +80,9 @@ import {
80
80
  type SubsystemIssue,
81
81
  type SubsystemIssueCategory,
82
82
  } from './IssueList';
83
- import { SubsystemFileTree } from './SubsystemFileTree';
84
83
  import { GraphLayoutCover } from './GraphLayoutCover';
84
+ import { SubsystemFileTree } from './SubsystemFileTree';
85
+ import { buildTreePathMapping } from './paths';
85
86
  import { GRAPH_CANVAS_CLASS, GRAPH_NAV_PROPS, GraphChrome, GraphLayerStyle, LAYOUT_FLIP_CLASS, LAYOUT_FLIP_EDGE_MS, LAYOUT_FLIP_NODE_MS } from './graphChrome';
86
87
  import { ComponentDeclaration } from './ComponentDeclaration';
87
88
  import type { ComponentVerificationState } from './ComponentDeclaration';
@@ -89,9 +90,7 @@ import type { DeclarationSymbolRef, SymbolInspection } from './symbolRefs';
89
90
  import { FileDrawer, FILE_DRAWER_HEIGHT_MS } from './FileDrawer';
90
91
  import { GraphWithSidebar } from './GraphWithSidebar';
91
92
  import type { GraphSidebarTab } from './GraphSidebar';
92
- import { buildRepoGroups, repoAvatarUrl, type RepoGroup } from './paths';
93
93
  import { TrailsPanel, TRAIL_PLAY_PAUSE_MS } from './TrailsPanel';
94
- import { TrailStackBreadcrumb } from './TrailStackBreadcrumb';
95
94
  import type { StepEmphasisMap } from './stepEmphasis';
96
95
  import {
97
96
  EDGE_LABEL_WIDTH,
@@ -145,7 +144,7 @@ type DrawerTarget =
145
144
  /**
146
145
  * Per-model UI state persisted to `localStorage`, keyed by `persistKey`.
147
146
  * Stores the trail working set (which flows are expanded and which
148
- * flow/step is selected) and which sidebar/diagnostics tab is showing, so
147
+ * flow/step is selected) and which diagnostics tab is showing, so
149
148
  * tabbing away from a model and back lands you where you left off. Transient
150
149
  * things (hover, drag, camera) are not saved.
151
150
  */
@@ -157,7 +156,6 @@ interface PersistedViewState {
157
156
  * under `principal.subsystems.sidebarWidth.<persistKey>`); reading the old
158
157
  * field still restores a width saved before that move. */
159
158
  sidebarWidth?: number;
160
- sidebarView?: 'files' | 'trails';
161
159
  diagnosticsTab?: 'issues' | 'agents';
162
160
  /** Flow direction chosen with the canvas toggle, restored on the next visit. */
163
161
  layoutDirection?: SubsystemLayoutDirection;
@@ -196,8 +194,7 @@ export interface SubsystemComponentGraphProps {
196
194
  components: SubsystemComponent[];
197
195
  /**
198
196
  * Ordered runtime trails — one trail per flow. When present the
199
- * sidebar's bottom half offers a Files/Trails toggle:
200
- * the flows panel lists each trail's steps (`symbol` or `file:line`);
197
+ * sidebar's bottom half lists each trail's steps (`symbol` or `file:line`);
201
198
  * clicking a flow row toggles its steps; clicking a step focuses that
202
199
  * step's edge and (when `renderTrailViewer` is set) opens the code
203
200
  * drawer on that flow's snippets. Opened flows stay on the canvas
@@ -322,15 +319,14 @@ export interface SubsystemComponentGraphProps {
322
319
  * - `graphify`: graphify-native static edges (`imports`, `contains`, …)
323
320
  * - `trails`: trail step edges (`calls`, `feeds`, …), including
324
321
  * step numbers when a flow is focused/hovered
325
- * Leave unset to let the sidebar's Files / Trails tab drive it: Files
326
- * draws graphify edges, Trails draws runtime steps. Without visible
327
- * tabs, defaults to `trails`.
322
+ * Defaults to `trails` when trails exist, otherwise `graphify` when
323
+ * static relations exist.
328
324
  */
329
325
  edgeView?: SubsystemEdgeView;
330
- /** Subsystem title displayed in the sidebar. */
326
+ /** Subsystem title displayed above the sidebar and graph. */
331
327
  title?: string;
332
328
  /**
333
- * Suppresses the sidebar entirely (title, description, file tree,
329
+ * Suppresses the header and sidebar entirely (title, description,
334
330
  * trails) for graph-only embeds. Pair with `graphTitle` to keep the
335
331
  * subsystem name visible as an overlay on the canvas.
336
332
  */
@@ -398,10 +394,10 @@ export interface SubsystemComponentGraphProps {
398
394
  /** Rendered in the sidebar under the description (e.g. selection inspector). */
399
395
  sidebarAfterDescription?: ReactNode;
400
396
  /**
401
- * Diagnostics status chip in the sidebar title row (beside the description
402
- * toggle). Shows the last verification pass's state as an icon color + count,
397
+ * Diagnostics status chip in the full-width graph header.
398
+ * Shows the last verification pass's state as an icon color + count,
403
399
  * and toggles the sidebar between the diagnostics list and the normal
404
- * files/trails view. Omit to hide the chip.
400
+ * trails view. Omit to hide the chip.
405
401
  */
406
402
  diagnostic?: SubsystemDiagnostic;
407
403
  /**
@@ -412,7 +408,7 @@ export interface SubsystemComponentGraphProps {
412
408
  issues?: SubsystemIssue[];
413
409
  /**
414
410
  * Pin diagnostics mode (controlled). When omitted, the sidebar seeds
415
- * diagnostics on iff `issues` is non-empty, and the title-row chip toggles it.
411
+ * diagnostics on iff `issues` is non-empty, and the header chip toggles it.
416
412
  */
417
413
  showIssues?: boolean;
418
414
  /**
@@ -463,8 +459,7 @@ export interface SubsystemComponentGraphProps {
463
459
  */
464
460
  hideDrawer?: boolean;
465
461
  /**
466
- * Called when a file in the sidebar file tree is clicked (repo-root-relative
467
- * path). The tree is derived from the components' `file` values.
462
+ * Called when a source file is opened (repo-root-relative path).
468
463
  */
469
464
  onFileSelect?: (file: string) => void;
470
465
  /** Live verification status for the selected component. */
@@ -613,11 +608,12 @@ const TrailDrawerContent = memo(function TrailDrawerContent({
613
608
 
614
609
  interface InnerProps extends SubsystemComponentGraphProps {
615
610
  measured: { w: number; h: number } | null;
611
+ filesActive: boolean;
616
612
  }
617
613
 
618
- 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, 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 }: InnerProps) {
614
+ 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, focusIssueCategory, laneStatus, onSelectIssue, onApplyIssueFix, onHoverIssue, renderFileViewer, renderTrailViewer, onFileSelect, componentVerification, onInspectSymbol, boundaryColors, codeProcessKeys, hideDrawer = false, persistKey, liveEvents, agentsPanel }: InnerProps) {
619
615
  const { theme } = useTheme();
620
- const { fitView, fitBounds, screenToFlowPosition } = useReactFlow();
616
+ const { fitView, fitBounds } = useReactFlow();
621
617
  const viewport = useViewport();
622
618
  // Restored once per mount from `localStorage` (see `readViewState`). Each
623
619
  // graph is its own tab/mount, so `persistKey` is stable for a mount.
@@ -796,75 +792,6 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
796
792
  drawerOpenRef.current = drawerTarget != null;
797
793
  // Pending deferred fit after opening the drawer; cancelled on re-entry / unmount.
798
794
  const pendingFocusFitRef = useRef<number | null>(null);
799
- // Component the pointer is over (null on leave) → transient tree highlight.
800
- const [hoveredComponentAlias, setHoveredComponentId] = useState<string | null>(null);
801
- // Boundary frame hovered on the canvas → highlight the matching folder row in
802
- // the sidebar file tree, so a frame's place in the repo tree is visible.
803
- const [hoveredRegion, setHoveredRegion] = useState<{
804
- kind: string;
805
- key: string;
806
- } | null>(null);
807
- const hoveredBoundaryPath =
808
- hoveredRegion && (hoveredRegion.kind === 'module' || hoveredRegion.kind === 'directory')
809
- ? hoveredRegion.key
810
- : null;
811
- // Path frames (module / directory) as absolute flow-coord rects, hit-tested
812
- // on pointer move. A pointer hit-test keeps the frames' interior hoverable
813
- // WITHOUT an overlay that would block edges / child nodes.
814
- const pathFrameRects = useMemo(() => {
815
- const out: Array<{ kind: string; key: string; x: number; y: number; w: number; h: number }> = [];
816
- for (const n of built.nodes) {
817
- if (n.type !== 'subsystem-group') continue;
818
- const region = (n.data as { region?: { kind?: string; key?: string } } | undefined)?.region;
819
- if (!region?.key || (region.kind !== 'module' && region.kind !== 'directory')) continue;
820
- const r = built.absoluteRects.get(n.id);
821
- if (!r) continue;
822
- out.push({ kind: region.kind, key: region.key, x: r.x, y: r.y, w: r.width, h: r.height });
823
- }
824
- return out;
825
- }, [built.nodes, built.absoluteRects]);
826
- useEffect(() => {
827
- if (pathFrameRects.length === 0) return;
828
- let raf = 0;
829
- let last: { x: number; y: number } | null = null;
830
- const overCanvas = (e: MouseEvent): boolean =>
831
- e.composedPath().some(
832
- (el) => el instanceof HTMLElement && el.classList.contains(GRAPH_CANVAS_CLASS),
833
- );
834
- const onMove = (e: MouseEvent) => {
835
- if (!overCanvas(e)) {
836
- last = null;
837
- setHoveredRegion((prev) => (prev == null ? prev : null));
838
- return;
839
- }
840
- last = { x: e.clientX, y: e.clientY };
841
- if (raf) return;
842
- raf = window.requestAnimationFrame(() => {
843
- raf = 0;
844
- if (!last) return;
845
- const p = screenToFlowPosition(last);
846
- let best: { kind: string; key: string } | null = null;
847
- let bestArea = Number.POSITIVE_INFINITY;
848
- for (const f of pathFrameRects) {
849
- if (p.x >= f.x && p.x <= f.x + f.w && p.y >= f.y && p.y <= f.y + f.h) {
850
- const area = f.w * f.h;
851
- if (area < bestArea) {
852
- bestArea = area;
853
- best = { kind: f.kind, key: f.key };
854
- }
855
- }
856
- }
857
- setHoveredRegion((prev) =>
858
- prev?.kind === best?.kind && prev?.key === best?.key ? prev : best,
859
- );
860
- });
861
- };
862
- window.addEventListener('mousemove', onMove);
863
- return () => {
864
- window.removeEventListener('mousemove', onMove);
865
- if (raf) cancelAnimationFrame(raf);
866
- };
867
- }, [pathFrameRects, screenToFlowPosition]);
868
795
  // Trail focus — selected flow (or step) is full strength; other
869
796
  // opened-flow members stay visible but dimmed; everything else is hidden.
870
797
  // Restored from the persisted view state when a `persistKey` is set.
@@ -884,49 +811,28 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
884
811
  trailId: string;
885
812
  stepIndex: number | null;
886
813
  } | null>(null);
887
- // Sidebar bottom half: which panel is shown when trails exist.
888
- // The persisted value is membership-checked rather than trusted: a stale
889
- // localStorage entry from before the walkthrough->trail rename would match
890
- // neither panel and render a blank sidebar with no selected tab.
891
- const [sidebarView, setSidebarView] = useState<'files' | 'trails'>(() => {
892
- const stored = persisted.sidebarView;
893
- const restored: 'files' | 'trails' =
894
- stored === 'files' || stored === 'trails' ? stored : 'trails';
895
- return trails?.length ? restored : 'files';
896
- });
897
814
  // Diagnostics area tab: issues list vs the Maintain agent pipeline.
898
815
  const [diagnosticsTab, setDiagnosticsTab] = useState<'issues' | 'agents'>(
899
816
  () => persisted.diagnosticsTab ?? 'issues',
900
817
  );
901
- // One edge source at a time. When the caller doesn't pick, the sidebar's
902
- // Files / Trails tab picks: Files draws graphify static edges,
903
- // Trails draws runtime step edges. Without visible tabs (no trails,
904
- // or a sidebar-less embed) fall back to trail edges.
905
- const sidebarTabsVisible = !hideSidebar && (trails?.length ?? 0) > 0;
818
+ // One edge source at a time. Prefer runtime steps when trails exist;
819
+ // otherwise draw static relations, unless the caller picks explicitly.
906
820
  // A solo trail layout only draws that flow's steps, so the trail edge source
907
821
  // is the only one that can render anything.
908
822
  const resolvedEdgeView: SubsystemEdgeView = committedTrailId
909
823
  ? 'trails'
910
824
  : edgeView ??
911
- (sidebarTabsVisible
912
- ? (sidebarView === 'trails' ? 'trails' : 'graphify')
913
- : (trails?.length ?? 0) > 0
914
- ? 'trails'
915
- : (graphifyRelations?.length ?? 0) > 0
916
- ? 'graphify'
917
- : 'trails');
825
+ ((trails?.length ?? 0) > 0
826
+ ? 'trails'
827
+ : (graphifyRelations?.length ?? 0) > 0
828
+ ? 'graphify'
829
+ : 'trails');
918
830
  // Trail flows the user has expanded (via the title row). Closed by
919
831
  // default so a graph with several flows doesn't dump every step list at once.
920
832
  // Restored from the persisted view state when a `persistKey` is set.
921
833
  const [expandedTrails, setExpandedTrails] = useState<Set<string>>(
922
834
  () => new Set(persisted.expandedTrails ?? []),
923
835
  );
924
- // Sidebar description visibility. Hidden by default so the files/flows
925
- // panel gets the vertical room; the title-row toggle reveals it.
926
- const [descriptionVisible, setDescriptionVisible] = useState(false);
927
- const [descToggleHover, setDescToggleHover] = useState(false);
928
- // `true` only when a description exists AND the user opened it.
929
- const showDesc = !!description && descriptionVisible;
930
836
  // Sidebar width lives in the shared GraphWithSidebar chrome — draggable and
931
837
  // persisted under `principal.subsystems.sidebarWidth.<persistKey>`. The graph
932
838
  // caps it at half the view width so the canvas keeps room to breathe;
@@ -951,10 +857,6 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
951
857
  if (!persistKey) return;
952
858
  writeViewState(persistKey, { focusedStepIndex });
953
859
  }, [persistKey, focusedStepIndex]);
954
- useEffect(() => {
955
- if (!persistKey) return;
956
- writeViewState(persistKey, { sidebarView });
957
- }, [persistKey, sidebarView]);
958
860
  useEffect(() => {
959
861
  if (!persistKey) return;
960
862
  writeViewState(persistKey, { diagnosticsTab });
@@ -1282,12 +1184,10 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
1282
1184
  };
1283
1185
  SUBSYSTEM_CALLBACKS.onEdgeSelect = selectEdge;
1284
1186
  SUBSYSTEM_CALLBACKS.maxNodeWidth = maxNodeWidth;
1285
- SUBSYSTEM_CALLBACKS.onHover = setHoveredComponentId;
1286
1187
  return () => {
1287
1188
  SUBSYSTEM_CALLBACKS.onSelect = undefined;
1288
1189
  SUBSYSTEM_CALLBACKS.onEdgeSelect = undefined;
1289
1190
  SUBSYSTEM_CALLBACKS.maxNodeWidth = undefined;
1290
- SUBSYSTEM_CALLBACKS.onHover = undefined;
1291
1191
  };
1292
1192
  }, [components, onSelect, maxNodeWidth, selectEdge]);
1293
1193
 
@@ -1937,23 +1837,16 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
1937
1837
  issueModuleFocusRef.current = null;
1938
1838
  }, []);
1939
1839
 
1940
- // Sidebar file trees — one per repo on multi-repo graphs, each under its
1941
- // own owner-avatar header. Clicking a header collapses that repo's tree.
1942
- const repoGroups = useMemo(() => buildRepoGroups(components), [components]);
1943
1840
  const hasTrails = useMemo(() => (trails?.length ?? 0) > 0, [trails]);
1841
+ const treePaths = useMemo(() => buildTreePathMapping(components), [components]);
1944
1842
  // Alias → component, so the Trails panel can show human hop names instead
1945
1843
  // of model-local aliases (and the site/`via` distinction can read).
1946
1844
  const componentByAlias = useMemo(
1947
1845
  () => new Map(components.map((c) => [c.alias, c])),
1948
1846
  [components],
1949
1847
  );
1950
- // Diagnostics view in the sidebar. Uncontrolled unless the host pins
1951
- // `showIssues`: the title-row chip toggles it, seeded from issues presence.
1952
- const [diagnosticsOpen, setDiagnosticsOpen] = useState<boolean>(
1953
- showIssues ?? (issues?.length ?? 0) > 0,
1954
- );
1955
- const controlledIssues = showIssues !== undefined;
1956
- const issuesActive = controlledIssues ? showIssues : diagnosticsOpen;
1848
+ // The outer header owns the diagnostics toggle and passes its resolved state.
1849
+ const issuesActive = showIssues === true;
1957
1850
  // Layer focus only applies while the diagnostics list is on screen — closing
1958
1851
  // it clears any dimming the expanded layers were driving.
1959
1852
  useEffect(() => {
@@ -1961,35 +1854,6 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
1961
1854
  setExpandedIssueCategories([]);
1962
1855
  }
1963
1856
  }, [issuesActive]);
1964
- const toggleIssues = () => {
1965
- if (!controlledIssues) setDiagnosticsOpen((v) => !v);
1966
- diagnostic?.onToggle?.();
1967
- };
1968
- const [collapsedRepos, setCollapsedRepos] = useState<Set<string>>(new Set());
1969
- const toggleRepoCollapsed = useCallback((key: string) => {
1970
- setCollapsedRepos((prev) => {
1971
- const next = new Set(prev);
1972
- if (next.has(key)) next.delete(key);
1973
- else next.add(key);
1974
- return next;
1975
- });
1976
- }, []);
1977
- const treeFiles = useMemo(
1978
- () => repoGroups.groups.flatMap((g) => g.entries.map((e) => e.file)),
1979
- [repoGroups],
1980
- );
1981
- const onTreeSelectFile = useCallback(
1982
- (file: string) => {
1983
- setFileOverlay(null);
1984
- if (openFileRef.current?.file === file && openFileRef.current.startLine == null) {
1985
- setDrawerTarget(null);
1986
- return;
1987
- }
1988
- setDrawerTarget({ kind: 'file', file });
1989
- onFileSelect?.(file);
1990
- },
1991
- [onFileSelect],
1992
- );
1993
1857
 
1994
1858
  const onOpenDeclarationFile = useCallback(
1995
1859
  (file: string, opts?: SubsystemOpenFileOptions) => {
@@ -2278,9 +2142,22 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
2278
2142
  onTrailSelect?.(null);
2279
2143
  }, [onTrailSelect]);
2280
2144
 
2145
+ // A hidden trail must not leave the file view filtered to its cast.
2146
+ const filesWereActive = useRef(false);
2147
+ useEffect(() => {
2148
+ const enteringFiles = filesActive && !filesWereActive.current;
2149
+ filesWereActive.current = filesActive;
2150
+ if (!enteringFiles) return;
2151
+ clearTrailFocus();
2152
+ setExpandedTrails(new Set());
2153
+ setSelectedEdgeId(null);
2154
+ setLayoutTrailId(null);
2155
+ onTrailStepHover?.(null);
2156
+ }, [filesActive, clearTrailFocus, onTrailStepHover]);
2157
+
2281
2158
  // Host deep-link: opening the graph from a trail row selects that
2282
- // flow — expand its steps, focus its edges, and switch the sidebar to
2283
- // Trails. Guarded by a ref so a later in-tab selection isn't yanked
2159
+ // flow — expand its steps and focus its edges.
2160
+ // Guarded by a ref so a later in-tab selection isn't yanked
2284
2161
  // back; a remount (or a new id) re-applies it.
2285
2162
  const appliedInitialTrailRef = useRef<string | null>(null);
2286
2163
  useEffect(() => {
@@ -2290,26 +2167,10 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
2290
2167
  const tl = trails.find((t) => t.id === initialTrailId);
2291
2168
  if (!tl) return;
2292
2169
  appliedInitialTrailRef.current = initialTrailId;
2293
- setSidebarView('trails');
2294
2170
  setExpandedTrails((prev) => new Set(prev).add(tl.id));
2295
2171
  focusTrailEdges(tl);
2296
2172
  }, [initialTrailId, layoutReady, trails, focusTrailEdges]);
2297
2173
 
2298
- // Switching sidebar panels also switches the edge vocabulary. Leaving the
2299
- // Trails panel drops its canvas state (focus, expanded flows, selected
2300
- // edge) so the Files view's topology edges aren't gated by a flow the user
2301
- // can no longer see or un-dim.
2302
- const changeSidebarView = useCallback(
2303
- (view: 'files' | 'trails') => {
2304
- setSidebarView(view);
2305
- if (view === 'trails') return;
2306
- clearTrailFocus();
2307
- setExpandedTrails(new Set());
2308
- setSelectedEdgeId(null);
2309
- },
2310
- [clearTrailFocus],
2311
- );
2312
-
2313
2174
  // Focus a single step's edge on the canvas and open/scroll the trail
2314
2175
  // drawer to that step's snippet. Clicking the already-focused step clears
2315
2176
  // step focus and returns to whole-flow (high-level) framing.
@@ -2810,15 +2671,6 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
2810
2671
  return labels;
2811
2672
  }, [dispEdges, selectedFlowStepNos, stepIssueBadgeByEdgeId]);
2812
2673
 
2813
- // Unique source files across components → sidebar file trees.
2814
- const treeFilePaths = treeFiles;
2815
-
2816
- // The hovered node's file (null when not hovering / file-less component).
2817
- const hoveredFile = useMemo(() => {
2818
- if (!hoveredComponentAlias) return null;
2819
- return components.find((c) => c.alias === hoveredComponentAlias)?.file ?? null;
2820
- }, [components, hoveredComponentAlias]);
2821
-
2822
2674
  // Drawer content renderer. Path-keyed only — the component-keyed
2823
2675
  // `renderFileView` variant was removed; nothing in-repo passed it.
2824
2676
  const fileViewer = renderFileViewer;
@@ -2839,16 +2691,16 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
2839
2691
  [],
2840
2692
  );
2841
2693
 
2842
- // Sidebar chrome — title/description header, Files/Trails (or Issues/Agents)
2843
- // tab strip, and the lower panels. Everything renders through the shared
2694
+ // Sidebar chrome — description header, files/trails, and optional Issues/Agents
2695
+ // diagnostics. Everything renders through the shared
2844
2696
  // GraphWithSidebar; the graph keeps owning every bit of *state* (focused
2845
- // trail, active tab, description visibility) because the canvas dims from
2697
+ // trail, diagnostics tab) because the canvas dims from
2846
2698
  // the same facts the sidebar renders.
2847
2699
  const sidebarVisible =
2848
2700
  !hideSidebar &&
2849
- (title || description || diagnostic || issuesActive || sidebarExtra || sidebarAfterDescription ||
2850
- treeFilePaths.length > 0 || hasTrails);
2851
- const sidebarBodyVisible = treeFilePaths.length > 0 || hasTrails || issuesActive;
2701
+ (description || filesActive || issuesActive || sidebarExtra || sidebarAfterDescription ||
2702
+ hasTrails);
2703
+ const sidebarBodyVisible = hasTrails || filesActive || issuesActive;
2852
2704
  const sidebarTabs: GraphSidebarTab[] | undefined = issuesActive
2853
2705
  ? agentsPanel
2854
2706
  ? [
@@ -2856,85 +2708,12 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
2856
2708
  { id: 'agents', label: 'Agents' },
2857
2709
  ]
2858
2710
  : undefined
2859
- : hasTrails
2860
- ? [
2861
- { id: 'trails', label: 'Trails' },
2862
- { id: 'files', label: 'Files' },
2863
- ]
2864
- : undefined;
2865
- const activeSidebarTab = issuesActive ? diagnosticsTab : sidebarView;
2866
- // Header block: the extras, then the title/actions row (diagnostic chip +
2867
- // description toggle), then the description that toggles out from under it.
2711
+ : undefined;
2712
+ // Header block: extras, then the description.
2868
2713
  const sidebarHeader = (
2869
2714
  <>
2870
2715
  {sidebarExtra}
2871
- {(title || description || diagnostic) && (
2872
- <div style={{ display: 'flex', alignItems: 'flex-start', gap: 8 }}>
2873
- {title && (
2874
- <h2
2875
- style={{
2876
- margin: 0,
2877
- flex: 1,
2878
- minWidth: 0,
2879
- fontSize: theme.fontSizes[2],
2880
- fontWeight: 600,
2881
- color: theme.colors.text,
2882
- fontFamily: theme.fonts.monospace,
2883
- }}
2884
- >
2885
- {title}
2886
- </h2>
2887
- )}
2888
- <div
2889
- style={{
2890
- display: 'flex',
2891
- alignItems: 'center',
2892
- gap: 4,
2893
- flexShrink: 0,
2894
- // Title's flex:1 already pushes this cluster right; without a
2895
- // title, keep the actions flush right instead of flush-left.
2896
- marginLeft: title ? undefined : 'auto',
2897
- }}
2898
- >
2899
- {diagnostic && (
2900
- <SubsystemDiagnosticToggle
2901
- {...diagnostic}
2902
- active={issuesActive}
2903
- onToggle={toggleIssues}
2904
- />
2905
- )}
2906
- {description && (
2907
- <button
2908
- type="button"
2909
- aria-expanded={descriptionVisible}
2910
- aria-label={descriptionVisible ? 'Hide description' : 'Show description'}
2911
- title={descriptionVisible ? 'Hide description' : 'Show description'}
2912
- onMouseEnter={() => setDescToggleHover(true)}
2913
- onMouseLeave={() => setDescToggleHover(false)}
2914
- onClick={() => setDescriptionVisible((v) => !v)}
2915
- style={{
2916
- display: 'inline-flex',
2917
- alignItems: 'center',
2918
- justifyContent: 'center',
2919
- flexShrink: 0,
2920
- width: 22,
2921
- height: 22,
2922
- padding: 0,
2923
- border: 'none',
2924
- borderRadius: 4,
2925
- background: descriptionVisible || descToggleHover ? theme.colors.border : 'transparent',
2926
- color: descriptionVisible || descToggleHover ? theme.colors.text : (theme.colors.textMuted ?? theme.colors.textSecondary),
2927
- cursor: 'pointer',
2928
- transition: 'background 120ms ease, color 120ms ease',
2929
- }}
2930
- >
2931
- <FileText size={14} />
2932
- </button>
2933
- )}
2934
- </div>
2935
- </div>
2936
- )}
2937
- {description && descriptionVisible && (
2716
+ {description && (
2938
2717
  <div style={{ fontSize: theme.fontSizes[0], lineHeight: 1.5 }}>
2939
2718
  <IndustryMarkdownSlide
2940
2719
  content={description}
@@ -2952,8 +2731,7 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
2952
2731
  {sidebarAfterDescription}
2953
2732
  </>
2954
2733
  );
2955
- // Lower panel: the Issues/Agents diagnostics stack, or the Files/Trails
2956
- // stack with the trail flow list / repo file tree.
2734
+ // Lower panel: diagnostics, the file tree, or the trail flow list.
2957
2735
  const sidebarBody = issuesActive ? (
2958
2736
  diagnosticsTab === 'agents' && agentsPanel ? (
2959
2737
  <SubsystemAgentsPanel {...agentsPanel} />
@@ -2969,7 +2747,16 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
2969
2747
  onExpandedCategoriesChange={handleExpandedCategoriesChange}
2970
2748
  />
2971
2749
  )
2972
- ) : sidebarView === 'trails' && trails && trails.length > 0 ? (
2750
+ ) : filesActive ? (
2751
+ <SubsystemFileTree
2752
+ files={treePaths.entries.map((entry) => entry.displayPath)}
2753
+ selectedFile={openFile ? treePaths.toDisplay.get(openFile) : null}
2754
+ onSelectFile={(path) => {
2755
+ const file = treePaths.toFile.get(path);
2756
+ if (file) onOpenDeclarationFile(file);
2757
+ }}
2758
+ />
2759
+ ) : trails && trails.length > 0 ? (
2973
2760
  <TrailsPanel
2974
2761
  trails={trails}
2975
2762
  expandedTrails={expandedTrails}
@@ -3005,43 +2792,6 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
3005
2792
  layoutFocusedTrailId={layoutTrailId}
3006
2793
  onFocusLayout={toggleLayoutFocus}
3007
2794
  />
3008
- ) : treeFilePaths.length > 0 ? (
3009
- <>
3010
- {repoGroups.groups.map((group, i) => {
3011
- const groupKey = group.repoKey ?? '__no-repo__';
3012
- const collapsed = collapsedRepos.has(groupKey);
3013
- return (
3014
- <div
3015
- key={groupKey}
3016
- style={{
3017
- flex: collapsed ? '0 0 auto' : 1,
3018
- minHeight: collapsed ? 0 : undefined,
3019
- display: 'flex',
3020
- flexDirection: 'column',
3021
- borderTop: i > 0 ? `1px solid ${theme.colors.border}` : undefined,
3022
- }}
3023
- >
3024
- {repoGroups.multiRepo && (
3025
- <RepoGroupHeader
3026
- group={group}
3027
- collapsed={collapsed}
3028
- onToggle={() => toggleRepoCollapsed(groupKey)}
3029
- />
3030
- )}
3031
- {!collapsed && (
3032
- <SubsystemFileTree
3033
- files={group.entries.map((e) => e.displayPath)}
3034
- selectedFile={selected?.file ?? openFile}
3035
- hoveredFile={hoveredFile}
3036
- onSelectFile={onTreeSelectFile}
3037
- hoveredFolder={hoveredBoundaryPath}
3038
- headerless={repoGroups.multiRepo}
3039
- />
3040
- )}
3041
- </div>
3042
- );
3043
- })}
3044
- </>
3045
2795
  ) : null;
3046
2796
  const fileOverlayView = fileOverlay && (
3047
2797
  <div
@@ -3098,15 +2848,11 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
3098
2848
  <GraphWithSidebar
3099
2849
  hidden={!sidebarVisible}
3100
2850
  bodyHidden={!sidebarBodyVisible}
3101
- stretchHeader={showDesc}
2851
+ headerMaxHeight={sidebarBodyVisible ? '50%' : '100%'}
3102
2852
  header={sidebarHeader}
3103
2853
  tabs={sidebarTabs}
3104
- activeTab={activeSidebarTab}
3105
- onTabChange={(id) =>
3106
- issuesActive
3107
- ? setDiagnosticsTab(id as 'issues' | 'agents')
3108
- : changeSidebarView(id as 'files' | 'trails')
3109
- }
2854
+ activeTab={diagnosticsTab}
2855
+ onTabChange={(id) => setDiagnosticsTab(id as 'issues' | 'agents')}
3110
2856
  sidebarBody={sidebarBody}
3111
2857
  tabListLabel={issuesActive ? 'Diagnostics view' : 'Sidebar view'}
3112
2858
  defaultWidth={persisted.sidebarWidth ?? 450}
@@ -3598,22 +3344,11 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
3598
3344
  title={drawerTitle}
3599
3345
  onClose={closeDrawer}
3600
3346
  placement={committedDirection === 'vertical' ? 'left' : 'bottom'}
3347
+ onResizeEnd={() => requestAnimationFrame(() => fitView())}
3601
3348
  fillHeight={drawerFillHeight}
3602
3349
  suppressEscape={fileOverlayOpen}
3603
3350
  hidden={hideDrawer}
3604
3351
  progress={drawerProgress}
3605
- subheader={
3606
- drawerTarget?.kind === 'trail' &&
3607
- focusedTrail &&
3608
- drawerTarget.stepIndex != null ? (
3609
- <TrailStackBreadcrumb
3610
- trail={focusedTrail}
3611
- stepIndex={drawerTarget.stepIndex}
3612
- componentByAlias={componentByAlias}
3613
- onFocusStep={(index) => focusTrailStep(focusedTrail, index)}
3614
- />
3615
- ) : null
3616
- }
3617
3352
  >
3618
3353
  {drawerTarget?.kind === 'trail' &&
3619
3354
  focusedTrail &&
@@ -3648,88 +3383,48 @@ function Inner({ components, trails, graphifyRelations, orderByLine, layoutDirec
3648
3383
  );
3649
3384
  }
3650
3385
 
3651
- /** Sidebar header for one repo's tree: owner avatar + repo name + file count.
3652
- * Clicking toggles the tree's visibility. */
3653
- function RepoGroupHeader({
3654
- group,
3655
- collapsed,
3656
- onToggle,
3657
- }: {
3658
- group: RepoGroup;
3659
- collapsed: boolean;
3660
- onToggle: () => void;
3661
- }) {
3662
- const { theme } = useTheme();
3663
- const avatar = repoAvatarUrl(group.repoKey);
3664
- const label = group.repo ?? 'No repo';
3665
- return (
3666
- <div
3667
- role="button"
3668
- aria-expanded={!collapsed}
3669
- onClick={(e) => {
3670
- e.stopPropagation();
3671
- onToggle();
3672
- }}
3673
- title={collapsed ? 'Show files' : 'Hide files'}
3674
- style={{
3675
- display: 'flex',
3676
- alignItems: 'center',
3677
- gap: 8,
3678
- padding: '10px 16px 4px',
3679
- flexShrink: 0,
3680
- cursor: 'pointer',
3681
- userSelect: 'none',
3682
- }}
3683
- >
3684
- {avatar ? (
3685
- <img
3686
- src={avatar}
3687
- alt=""
3688
- width={16}
3689
- height={16}
3690
- loading="lazy"
3691
- style={{ borderRadius: 4, flexShrink: 0, background: theme.colors.border }}
3692
- />
3693
- ) : group.owner ? (
3694
- <span
3695
- style={{
3696
- width: 16,
3697
- height: 16,
3698
- borderRadius: 4,
3699
- flexShrink: 0,
3700
- display: 'inline-flex',
3701
- alignItems: 'center',
3702
- justifyContent: 'center',
3703
- fontSize: 9,
3704
- fontWeight: 700,
3705
- fontFamily: theme.fonts.monospace,
3706
- color: theme.colors.text,
3707
- background: theme.colors.border,
3708
- }}
3709
- >
3710
- {group.owner.charAt(0).toUpperCase()}
3711
- </span>
3712
- ) : null}
3713
- <span
3714
- style={{
3715
- fontSize: theme.fontSizes[1],
3716
- fontFamily: theme.fonts.monospace,
3717
- color: theme.colors.text,
3718
- fontWeight: 600,
3719
- overflow: 'hidden',
3720
- textOverflow: 'ellipsis',
3721
- whiteSpace: 'nowrap',
3722
- }}
3723
- >
3724
- {label}
3725
- </span>
3726
- </div>
3727
- );
3728
- }
3729
-
3730
3386
  export function SubsystemComponentGraph(props: SubsystemComponentGraphProps) {
3387
+ const { theme } = useTheme();
3731
3388
  const wrapRef = useRef<HTMLDivElement>(null);
3732
3389
  const [size, setSize] = useState<{ w: number; h: number } | null>(null);
3390
+ const [filesOpen, setFilesOpen] = useState(false);
3391
+ const fileCount = useMemo(
3392
+ () => buildTreePathMapping(props.components).entries.length,
3393
+ [props.components],
3394
+ );
3395
+ const hasFiles = fileCount > 0;
3396
+ const [diagnosticsOpen, setDiagnosticsOpen] = useState<boolean>(
3397
+ props.showIssues ?? (props.issues?.length ?? 0) > 0,
3398
+ );
3399
+ const issuesActive = props.showIssues ?? diagnosticsOpen;
3400
+ const filesActive = filesOpen && hasFiles && !issuesActive;
3401
+ const selectSidebarView = (view: 'trails' | 'files' | 'issues') => {
3402
+ const nextIssuesActive = view === 'issues';
3403
+ setFilesOpen(view === 'files');
3404
+ if (nextIssuesActive !== issuesActive) {
3405
+ if (props.showIssues === undefined) setDiagnosticsOpen(nextIssuesActive);
3406
+ props.diagnostic?.onToggle?.();
3407
+ }
3408
+ };
3409
+ const viewButtonStyle = (active: boolean): CSSProperties => ({
3410
+ display: 'inline-flex',
3411
+ alignItems: 'center',
3412
+ justifyContent: 'center',
3413
+ gap: 4,
3414
+ height: 28,
3415
+ padding: '4px 7px',
3416
+ fontFamily: theme.fonts.monospace,
3417
+ fontSize: theme.fontSizes[0],
3418
+ border: 'none',
3419
+ borderRadius: 4,
3420
+ background: active ? theme.colors.border : 'transparent',
3421
+ color: active ? theme.colors.text : (theme.colors.textMuted ?? theme.colors.textSecondary),
3422
+ cursor: 'pointer',
3423
+ });
3424
+
3425
+ useEffect(() => {
3426
+ if (props.initialTrailId) setFilesOpen(false);
3427
+ }, [props.initialTrailId]);
3733
3428
 
3734
3429
  // Measure the container so React Flow always has a real, non-zero pixel size
3735
3430
  // (responsive to the parent; avoids the %→0 blank canvas).
@@ -3754,7 +3449,6 @@ export function SubsystemComponentGraph(props: SubsystemComponentGraphProps) {
3754
3449
 
3755
3450
  return (
3756
3451
  <div
3757
- ref={wrapRef}
3758
3452
  style={{
3759
3453
  position: 'relative',
3760
3454
  width: '100%',
@@ -3764,7 +3458,84 @@ export function SubsystemComponentGraph(props: SubsystemComponentGraphProps) {
3764
3458
  flexDirection: 'column',
3765
3459
  }}
3766
3460
  >
3767
- <div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
3461
+ {!constructsOnly && !props.hideSidebar && (
3462
+ <header
3463
+ style={{
3464
+ flexShrink: 0,
3465
+ display: 'flex',
3466
+ alignItems: 'center',
3467
+ gap: 12,
3468
+ padding: '12px 16px',
3469
+ background: theme.colors.background,
3470
+ borderBottom: `1px solid ${theme.colors.border}`,
3471
+ }}
3472
+ >
3473
+ {props.title && (
3474
+ <h2
3475
+ style={{
3476
+ margin: 0,
3477
+ minWidth: 0,
3478
+ fontSize: theme.fontSizes[2],
3479
+ fontWeight: 600,
3480
+ color: theme.colors.text,
3481
+ fontFamily: theme.fonts.monospace,
3482
+ overflowWrap: 'anywhere',
3483
+ }}
3484
+ >
3485
+ {props.title}
3486
+ </h2>
3487
+ )}
3488
+ <div
3489
+ role="group"
3490
+ aria-label="Sidebar view"
3491
+ style={{
3492
+ marginLeft: 'auto',
3493
+ flexShrink: 0,
3494
+ display: 'grid',
3495
+ gridAutoFlow: 'column',
3496
+ gridAutoColumns: '1fr',
3497
+ alignItems: 'center',
3498
+ gap: 2,
3499
+ padding: 2,
3500
+ border: `1px solid ${theme.colors.border}`,
3501
+ borderRadius: 6,
3502
+ }}
3503
+ >
3504
+ <button
3505
+ type="button"
3506
+ aria-label="Trails"
3507
+ aria-pressed={!filesActive && !issuesActive}
3508
+ onClick={() => selectSidebarView('trails')}
3509
+ style={viewButtonStyle(!filesActive && !issuesActive)}
3510
+ >
3511
+ <Route size={14} aria-hidden />
3512
+ <span>Trails</span>
3513
+ </button>
3514
+ {hasFiles && (
3515
+ <button
3516
+ type="button"
3517
+ aria-label={`Files (${fileCount} files)`}
3518
+ title="Show files"
3519
+ aria-pressed={filesActive}
3520
+ onClick={() => selectSidebarView('files')}
3521
+ style={viewButtonStyle(filesActive)}
3522
+ >
3523
+ <Files size={14} aria-hidden />
3524
+ <span>Files</span>
3525
+ <span style={{ fontWeight: 600 }}>{fileCount}</span>
3526
+ </button>
3527
+ )}
3528
+ <SubsystemDiagnosticToggle
3529
+ {...(props.diagnostic ?? { status: 'unknown', issueCount: props.issues?.length })}
3530
+ labelText="Issues"
3531
+ segmented
3532
+ active={issuesActive}
3533
+ onToggle={() => selectSidebarView('issues')}
3534
+ />
3535
+ </div>
3536
+ </header>
3537
+ )}
3538
+ <div ref={wrapRef} style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
3768
3539
  {constructsOnly ? (
3769
3540
  <ConstructsCatalog
3770
3541
  components={props.components}
@@ -3784,7 +3555,7 @@ export function SubsystemComponentGraph(props: SubsystemComponentGraphProps) {
3784
3555
  />
3785
3556
  ) : (
3786
3557
  <ReactFlowProvider>
3787
- <Inner {...props} measured={size} />
3558
+ <Inner {...props} showIssues={issuesActive} filesActive={filesActive} measured={size} />
3788
3559
  </ReactFlowProvider>
3789
3560
  )}
3790
3561
  </div>