@principal-ai/subsystems-react 0.28.6 → 0.29.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 (43) hide show
  1. package/dist/pierre/PierreWalkthroughCodeView.js +1 -1
  2. package/dist/pierre/PierreWalkthroughCodeView.js.map +1 -1
  3. package/dist/stories/Subsystem/ComponentGraph/fixtures.d.ts.map +1 -1
  4. package/dist/stories/Subsystem/ComponentGraph/fixtures.js +0 -1
  5. package/dist/stories/Subsystem/ComponentGraph/fixtures.js.map +1 -1
  6. package/dist/subsystem/ConstructsCatalog.d.ts.map +1 -1
  7. package/dist/subsystem/ConstructsCatalog.js +1 -0
  8. package/dist/subsystem/ConstructsCatalog.js.map +1 -1
  9. package/dist/subsystem/FileDrawer.d.ts +5 -4
  10. package/dist/subsystem/FileDrawer.d.ts.map +1 -1
  11. package/dist/subsystem/FileDrawer.js +89 -54
  12. package/dist/subsystem/FileDrawer.js.map +1 -1
  13. package/dist/subsystem/IssueList.d.ts +9 -1
  14. package/dist/subsystem/IssueList.d.ts.map +1 -1
  15. package/dist/subsystem/IssueList.js +15 -4
  16. package/dist/subsystem/IssueList.js.map +1 -1
  17. package/dist/subsystem/SubsystemComponentGraph.d.ts +8 -2
  18. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  19. package/dist/subsystem/SubsystemComponentGraph.js +5 -5
  20. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  21. package/dist/subsystem/model.d.ts +3 -3
  22. package/dist/subsystem/model.d.ts.map +1 -1
  23. package/dist/subsystem/model.js +0 -4
  24. package/dist/subsystem/model.js.map +1 -1
  25. package/dist/subsystem/nodes.js +1 -1
  26. package/dist/subsystem/nodes.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/pierre/PierreWalkthroughCodeView.tsx +1 -1
  29. package/src/stories/Subsystem/ComponentGraph/Basics.stories.tsx +2 -2
  30. package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +6 -6
  31. package/src/stories/Subsystem/ComponentGraph/EdgeViews.stories.tsx +7 -7
  32. package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +1 -1
  33. package/src/stories/Subsystem/ComponentGraph/ModuleBadges.stories.tsx +5 -5
  34. package/src/stories/Subsystem/ComponentGraph/Modules.stories.tsx +2 -2
  35. package/src/stories/Subsystem/ComponentGraph/Scenarios.stories.tsx +2 -2
  36. package/src/stories/Subsystem/ComponentGraph/fixtures.ts +0 -1
  37. package/src/subsystem/ConstructsCatalog.tsx +1 -0
  38. package/src/subsystem/FileDrawer.tsx +130 -70
  39. package/src/subsystem/IssueList.tsx +28 -3
  40. package/src/subsystem/SubsystemComponentGraph.tsx +16 -5
  41. package/src/subsystem/model.test.ts +2 -2
  42. package/src/subsystem/model.ts +1 -6
  43. package/src/subsystem/nodes.tsx +1 -1
@@ -5,19 +5,20 @@
5
5
  * children by the graph component.
6
6
  */
7
7
 
8
- import { useEffect, useState } from 'react';
9
- import type { ReactNode } from 'react';
10
- import { useTheme } from '@principal-ade/industry-theme';
11
- import { X } from 'lucide-react';
8
+ import { useEffect, useState } from "react";
9
+ import type { ReactNode } from "react";
10
+ import { useTheme } from "@principal-ade/industry-theme";
11
+ import { Minimize2, Maximize2, X } from "lucide-react";
12
12
 
13
13
  /** Matches `transition: height …` below — callers that fitView after open
14
14
  * should wait at least this long so the canvas has its reduced height. */
15
15
  export const FILE_DRAWER_HEIGHT_MS = 200;
16
16
 
17
17
  /** Bottom panel that slides up from the bottom of the graph area.
18
- * Sits in normal flow (canvas shrinks while open, nothing covered)
19
- * and animates via height; stays mounted so open/close animates.
20
- * `fillHeight` expands to the full graph column (for whole-file reading). */
18
+ * Bottom-anchored with an animated height; an in-flow spacer reserves its
19
+ * footprint so the canvas shrinks while open (nothing covered). Stays mounted
20
+ * so open/close/maximize animate. The header's maximize button (or a
21
+ * double-click) grows it over the whole graph column for whole-file reading. */
21
22
  export function FileDrawer({
22
23
  title,
23
24
  onClose,
@@ -38,85 +39,144 @@ export function FileDrawer({
38
39
  const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
39
40
  const open = title !== null;
40
41
  const [closeHover, setCloseHover] = useState(false);
42
+ const [maxHover, setMaxHover] = useState(false);
43
+ // Full-height mode: the maximize button or double-clicking the header.
44
+ // The panel is always bottom-anchored with an animated height, and a spacer
45
+ // in normal flow reserves its footprint, so maximizing grows it upward over
46
+ // the still-mounted graph instead of snapping to full.
47
+ const [maximized, setMaximized] = useState(false);
48
+ // A closed drawer reopens at the default height, not maximized.
49
+ useEffect(() => {
50
+ if (!open) setMaximized(false);
51
+ }, [open]);
52
+ const fullHeight = open && (maximized || fillHeight);
53
+ // Space the panel takes out of the canvas's flow (0 when it overlays fully).
54
+ const reservedHeight = fullHeight ? 0 : open ? "45%" : 0;
41
55
 
42
56
  useEffect(() => {
43
57
  if (!open || suppressEscape) return;
44
58
  const onKey = (e: KeyboardEvent) => {
45
- if (e.key === 'Escape') onClose();
59
+ if (e.key === "Escape") onClose();
46
60
  };
47
- window.addEventListener('keydown', onKey);
48
- return () => window.removeEventListener('keydown', onKey);
61
+ window.addEventListener("keydown", onKey);
62
+ return () => window.removeEventListener("keydown", onKey);
49
63
  }, [open, onClose, suppressEscape]);
50
64
 
51
65
  return (
52
- <div
53
- style={{
54
- position: 'relative',
55
- // Above the absolute edge-label overlay (zIndex 5), which spans the
56
- // whole graph-area container including this panel's slice.
57
- zIndex: 6,
58
- flexShrink: 0,
59
- flex: open && fillHeight ? 1 : undefined,
60
- height: open ? (fillHeight ? undefined : '45%') : 0,
61
- minHeight: 0,
62
- overflow: 'hidden',
63
- display: 'flex',
64
- flexDirection: 'column',
65
- background: theme.colors.background,
66
- borderTop: open ? `1px solid ${theme.colors.border}` : 'none',
67
- transition: `height ${FILE_DRAWER_HEIGHT_MS}ms ease`,
68
- }}
69
- >
66
+ <>
67
+ {/* Reserves the drawer's footprint in the column so the canvas shrinks
68
+ while the drawer is open. Animates with the panel, so the boundary
69
+ between graph and code slides smoothly. */}
70
70
  <div
71
+ aria-hidden="true"
71
72
  style={{
72
- display: 'flex',
73
- alignItems: 'center',
74
- gap: 8,
75
- padding: '6px 10px',
76
- borderBottom: `1px solid ${theme.colors.border}`,
77
73
  flexShrink: 0,
74
+ height: reservedHeight,
75
+ transition: `height ${FILE_DRAWER_HEIGHT_MS}ms ease`,
76
+ }}
77
+ />
78
+ {/* The panel — bottom-anchored and grown via `height`, so maximize/fill
79
+ reads as dragging up over the diagram. */}
80
+ <div
81
+ style={{
82
+ position: "absolute",
83
+ left: 0,
84
+ right: 0,
85
+ bottom: 0,
86
+ // Above the edge-label overlay (zIndex 5), React Flow, and overlays
87
+ // (zIndex 6–8); 30 lifts it over the whole graph area when maximized.
88
+ zIndex: maximized ? 30 : 6,
89
+ height: open ? (fullHeight ? "100%" : "45%") : 0,
90
+ minHeight: 0,
91
+ overflow: "hidden",
92
+ display: "flex",
93
+ flexDirection: "column",
94
+ background: theme.colors.background,
95
+ borderTop: open ? `1px solid ${theme.colors.border}` : "none",
96
+ transition: `height ${FILE_DRAWER_HEIGHT_MS}ms ease`,
78
97
  }}
79
98
  >
80
- <span
81
- title={title ?? undefined}
82
- style={{
83
- flex: 1,
84
- minWidth: 0,
85
- overflow: 'hidden',
86
- textOverflow: 'ellipsis',
87
- whiteSpace: 'nowrap',
88
- fontFamily: theme.fonts.monospace,
89
- fontSize: theme.fontSizes[0],
90
- color: muted,
91
- }}
92
- >
93
- {title}
94
- </span>
95
- <button
96
- type="button"
97
- onClick={onClose}
98
- onMouseEnter={() => setCloseHover(true)}
99
- onMouseLeave={() => setCloseHover(false)}
100
- aria-label="Close file"
99
+ <div
100
+ onDoubleClick={() => setMaximized((v) => !v)}
101
101
  style={{
102
- display: 'inline-flex',
103
- alignItems: 'center',
104
- justifyContent: 'center',
105
- width: 22,
106
- height: 22,
107
- padding: 0,
108
- border: 'none',
109
- borderRadius: 4,
110
- background: closeHover ? theme.colors.border : 'transparent',
111
- color: closeHover ? theme.colors.text : muted,
112
- cursor: 'pointer',
113
- transition: 'background 120ms ease, color 120ms ease',
102
+ display: "flex",
103
+ alignItems: "center",
104
+ gap: 8,
105
+ padding: "6px 10px",
106
+ borderBottom: `1px solid ${theme.colors.border}`,
107
+ flexShrink: 0,
108
+ cursor: "default",
109
+ userSelect: "none",
114
110
  }}
115
111
  >
116
- <X size={14} />
117
- </button>
112
+ <span
113
+ title={title ?? undefined}
114
+ style={{
115
+ flex: 1,
116
+ minWidth: 0,
117
+ overflow: "hidden",
118
+ textOverflow: "ellipsis",
119
+ whiteSpace: "nowrap",
120
+ fontFamily: theme.fonts.monospace,
121
+ fontSize: theme.fontSizes[0],
122
+ color: muted,
123
+ }}
124
+ >
125
+ {title}
126
+ </span>
127
+ <button
128
+ type="button"
129
+ onClick={() => setMaximized((v) => !v)}
130
+ onMouseEnter={() => setMaxHover(true)}
131
+ onMouseLeave={() => setMaxHover(false)}
132
+ aria-label={maximized ? "Minimize code view" : "Maximize code view"}
133
+ aria-pressed={maximized}
134
+ title={maximized ? "Minimize code view" : "Maximize code view"}
135
+ style={{
136
+ display: "inline-flex",
137
+ alignItems: "center",
138
+ justifyContent: "center",
139
+ width: 22,
140
+ height: 22,
141
+ padding: 0,
142
+ border: "none",
143
+ borderRadius: 4,
144
+ background: maxHover ? theme.colors.border : "transparent",
145
+ color: maxHover ? theme.colors.text : muted,
146
+ cursor: "pointer",
147
+ transition: "background 120ms ease, color 120ms ease",
148
+ }}
149
+ >
150
+ {maximized ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
151
+ </button>
152
+ <button
153
+ type="button"
154
+ onClick={onClose}
155
+ onMouseEnter={() => setCloseHover(true)}
156
+ onMouseLeave={() => setCloseHover(false)}
157
+ aria-label="Close file"
158
+ style={{
159
+ display: "inline-flex",
160
+ alignItems: "center",
161
+ justifyContent: "center",
162
+ width: 22,
163
+ height: 22,
164
+ padding: 0,
165
+ border: "none",
166
+ borderRadius: 4,
167
+ background: closeHover ? theme.colors.border : "transparent",
168
+ color: closeHover ? theme.colors.text : muted,
169
+ cursor: "pointer",
170
+ transition: "background 120ms ease, color 120ms ease",
171
+ }}
172
+ >
173
+ <X size={14} />
174
+ </button>
175
+ </div>
176
+ <div style={{ flex: 1, minHeight: 0, overflow: "auto" }}>
177
+ {children}
178
+ </div>
118
179
  </div>
119
- <div style={{ flex: 1, minHeight: 0, overflow: 'auto' }}>{children}</div>
120
- </div>
180
+ </>
121
181
  );
122
182
  }
@@ -11,7 +11,7 @@
11
11
  * (e.g. focus the node on the graph, or apply the fix in the report).
12
12
  */
13
13
 
14
- import { useState } from 'react';
14
+ import { useEffect, useState } from 'react';
15
15
  import type { LucideIcon } from 'lucide-react';
16
16
  import {
17
17
  ChevronDown,
@@ -499,6 +499,14 @@ export interface SubsystemIssueListProps {
499
499
  onSelectIssue?: (issue: SubsystemIssue) => void;
500
500
  onApplyFix?: (issue: SubsystemIssue) => void;
501
501
  onHoverIssue?: (issue: SubsystemIssue | null) => void;
502
+ /**
503
+ * Land the list focused on one verification layer: that category starts
504
+ * expanded and every other category starts collapsed. Used when the list is
505
+ * opened from a lane-specific entry point (e.g. the Maintainer tab's lane
506
+ * badges) so the caller's intent — "show me this layer's findings" — is
507
+ * honoured without an extra click.
508
+ */
509
+ focusCategory?: SubsystemIssueCategory;
502
510
  }
503
511
 
504
512
  export function SubsystemIssueList({
@@ -506,15 +514,32 @@ export function SubsystemIssueList({
506
514
  onSelectIssue,
507
515
  onApplyFix,
508
516
  onHoverIssue,
517
+ focusCategory,
509
518
  }: SubsystemIssueListProps) {
510
519
  const { theme } = useTheme();
511
520
  const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
512
521
  const categories = groupIssuesByCategory(issues);
513
- // Collapsed categories (by id). Everything starts expanded.
522
+ // Collapsed categories (by id). Everything starts expanded, unless the list
523
+ // was opened focused on a single layer — then every other layer starts
524
+ // collapsed so the focused category's findings are what you land on.
514
525
  const [collapsed, setCollapsed] = useState<Set<SubsystemIssueCategory>>(
515
- () => new Set(),
526
+ () =>
527
+ focusCategory
528
+ ? new Set(
529
+ SUBSYSTEM_ISSUE_CATEGORIES.filter((c) => c !== focusCategory),
530
+ )
531
+ : new Set(),
516
532
  );
517
533
  const [hovered, setHovered] = useState<SubsystemIssueCategory | null>(null);
534
+ // Re-apply the focus when it changes on an already-mounted list (e.g. the
535
+ // host switches which lane the open model is focused on).
536
+ useEffect(() => {
537
+ setCollapsed(
538
+ focusCategory
539
+ ? new Set(SUBSYSTEM_ISSUE_CATEGORIES.filter((c) => c !== focusCategory))
540
+ : new Set(),
541
+ );
542
+ }, [focusCategory]);
518
543
  const toggleCollapsed = (category: SubsystemIssueCategory) => {
519
544
  setCollapsed((prev) => {
520
545
  const next = new Set(prev);
@@ -55,7 +55,11 @@ import { ConstructsCatalog } from './ConstructsCatalog';
55
55
  import type { SubsystemOpenFileOptions } from './declarationRef';
56
56
  import { SubsystemComponentNode, SubsystemGroupNode, SubsystemEdge, SUBSYSTEM_CALLBACKS, hexWithAlpha, EDGE_DIM_ALPHA, fileMatchForNode, flowElementVisibility } from './nodes';
57
57
  import { SubsystemDiagnosticToggle, type SubsystemDiagnostic } from './DiagnosticToggle';
58
- import { SubsystemIssueList, type SubsystemIssue } from './IssueList';
58
+ import {
59
+ SubsystemIssueList,
60
+ type SubsystemIssue,
61
+ type SubsystemIssueCategory,
62
+ } from './IssueList';
59
63
  import { SubsystemFileTree } from './SubsystemFileTree';
60
64
  import { GraphLayoutCover } from './GraphLayoutCover';
61
65
  import { GRAPH_NAV_PROPS, GraphChrome } from './graphChrome';
@@ -172,7 +176,7 @@ export interface SubsystemComponentGraphProps {
172
176
  * Which edge vocabulary the canvas draws. The relation and walkthrough
173
177
  * vocabularies are disjoint, so a graph carrying both shows one or the
174
178
  * other — never both. Edges outside the view are hidden (labels go too).
175
- * - `relations`: topology relation edges (`imports`, `extends`, …)
179
+ * - `relations`: topology relation edges (`extends`, `implements`, …)
176
180
  * - `walkthroughs`: walkthrough hop edges (`calls`, `feeds`, …), including
177
181
  * step numbers when a flow is focused/hovered
178
182
  * Leave unset to let the sidebar's Files / Walkthroughs tab drive it: Files
@@ -246,6 +250,12 @@ export interface SubsystemComponentGraphProps {
246
250
  * diagnostics on iff `issues` is non-empty, and the title-row chip toggles it.
247
251
  */
248
252
  showIssues?: boolean;
253
+ /**
254
+ * When the issues list is active, land it focused on this verification
255
+ * layer: that category expands, the rest start collapsed. Optional — the
256
+ * plain issues view expands everything.
257
+ */
258
+ focusIssueCategory?: SubsystemIssueCategory;
249
259
  /** Click an issue — focus its target on the graph / open detail. */
250
260
  onSelectIssue?: (issue: SubsystemIssue) => void;
251
261
  /** Apply an issue's deterministic fix. */
@@ -375,7 +385,7 @@ interface InnerProps extends SubsystemComponentGraphProps {
375
385
  measured: { w: number; h: number } | null;
376
386
  }
377
387
 
378
- function Inner({ components, relations, walkthroughs, initialWalkthroughId, onReorderWalkthroughs, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, edgeView, title, hideSidebar, walkthroughStepMode = 'focus', autoPlayWalkthroughs = false, walkthroughAutoPlayIntervalMs = WALKTHROUGH_PLAY_PAUSE_MS, zoomOnWalkthroughFocus = true, graphTitle, showWalkthroughTitle = false, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, diagnostic, issues, showIssues, onSelectIssue, onApplyIssueFix, onHoverIssue, renderFileView, renderFileViewer, renderWalkthroughViewer, onFileSelect, onVerifyComponent, componentVerification, persistKey }: InnerProps) {
388
+ function Inner({ components, relations, walkthroughs, initialWalkthroughId, onReorderWalkthroughs, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, edgeView, title, hideSidebar, walkthroughStepMode = 'focus', autoPlayWalkthroughs = false, walkthroughAutoPlayIntervalMs = WALKTHROUGH_PLAY_PAUSE_MS, zoomOnWalkthroughFocus = true, graphTitle, showWalkthroughTitle = false, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, diagnostic, issues, showIssues, focusIssueCategory, onSelectIssue, onApplyIssueFix, onHoverIssue, renderFileView, renderFileViewer, renderWalkthroughViewer, onFileSelect, onVerifyComponent, componentVerification, persistKey }: InnerProps) {
379
389
  const { theme } = useTheme();
380
390
  const { fitView } = useReactFlow();
381
391
  const viewport = useViewport();
@@ -972,7 +982,7 @@ function Inner({ components, relations, walkthroughs, initialWalkthroughId, onRe
972
982
  };
973
983
  // Edges outside the selected view are hidden entirely (labels included).
974
984
  const edgeInView = (e: Edge): boolean => {
975
- const mechanism = (e.data as { mechanism?: string } | undefined)?.mechanism ?? 'imports';
985
+ const mechanism = (e.data as { mechanism?: string } | undefined)?.mechanism ?? 'uses';
976
986
  return resolvedEdgeView === 'relations'
977
987
  ? isRelationMechanism(mechanism)
978
988
  : isWalkthroughMechanism(mechanism);
@@ -1560,7 +1570,7 @@ function Inner({ components, relations, walkthroughs, initialWalkthroughId, onRe
1560
1570
  } | undefined;
1561
1571
  return {
1562
1572
  id: e.id,
1563
- mechanism: d?.mechanism ?? 'imports',
1573
+ mechanism: d?.mechanism ?? 'uses',
1564
1574
  dimmed: d?.dimmed === true,
1565
1575
  midX: d?.labelX ?? 0,
1566
1576
  midY: d?.labelY ?? 0,
@@ -1738,6 +1748,7 @@ function Inner({ components, relations, walkthroughs, initialWalkthroughId, onRe
1738
1748
  {issuesActive ? (
1739
1749
  <SubsystemIssueList
1740
1750
  issues={issues ?? []}
1751
+ focusCategory={focusIssueCategory}
1741
1752
  onSelectIssue={onSelectIssue}
1742
1753
  onApplyFix={onApplyIssueFix}
1743
1754
  onHoverIssue={onHoverIssue}
@@ -40,9 +40,9 @@ const comps: SubsystemComponent[] = [
40
40
  ];
41
41
 
42
42
  const relations = [
43
- { id: 'e1', from: 'transcript', to: 'reader', relationType: 'imports' as const },
43
+ { id: 'e1', from: 'transcript', to: 'reader', relationType: 'references' as const },
44
44
  // 'host' is NOT a component — this is the cross-package external case.
45
- { id: 'e2', from: 'reader', to: 'host', relationType: 'imports' as const, refs: ['bun/index.ts'] },
45
+ { id: 'e2', from: 'reader', to: 'host', relationType: 'references' as const, refs: ['bun/index.ts'] },
46
46
  ];
47
47
 
48
48
  const doc = { components: comps, relations };
@@ -91,7 +91,6 @@ export interface SubsystemDeclToken {
91
91
  * Belongs on `relations[]`, not on walkthrough hops.
92
92
  */
93
93
  export type SubsystemRelationType =
94
- | 'imports'
95
94
  | 'extends'
96
95
  | 'inherits'
97
96
  | 'implements'
@@ -120,7 +119,7 @@ export type SubsystemEdgeMechanism =
120
119
  /**
121
120
  * Which edge vocabulary the canvas draws. The two vocabularies are disjoint;
122
121
  * a view shows only edges (and their labels) from the selected source.
123
- * - `relations`: only topology relation edges (`imports`, `extends`, …)
122
+ * - `relations`: only topology relation edges (`extends`, `implements`, …)
124
123
  * - `walkthroughs`: only walkthrough hop edges (`calls`, `feeds`, …)
125
124
  */
126
125
  export type SubsystemEdgeView = 'relations' | 'walkthroughs';
@@ -945,7 +944,6 @@ export type SubsystemGraphEdge = Edge<SubsystemGraphEdgeData>;
945
944
  * source (the two unions are disjoint).
946
945
  */
947
946
  export const SUBSYSTEM_RELATION_TYPES = [
948
- 'imports',
949
947
  'extends',
950
948
  'inherits',
951
949
  'implements',
@@ -986,7 +984,6 @@ export function isWalkthroughMechanism(
986
984
  }
987
985
 
988
986
  export const MECHANISM_COLOR: Record<SubsystemEdgeMechanism, string> = {
989
- imports: '#0893d2', // blue
990
987
  calls: '#4ec9b0', // teal
991
988
  extends: '#b48ead', // purple
992
989
  inherits: '#9b6fd0', // purple
@@ -1004,7 +1001,6 @@ export const MECHANISM_COLOR: Record<SubsystemEdgeMechanism, string> = {
1004
1001
  };
1005
1002
 
1006
1003
  export const MECHANISM_STYLE: Record<SubsystemEdgeMechanism, 'solid' | 'dashed' | 'dotted'> = {
1007
- imports: 'solid',
1008
1004
  calls: 'solid',
1009
1005
  extends: 'dashed',
1010
1006
  inherits: 'dashed',
@@ -1024,7 +1020,6 @@ export const MECHANISM_STYLE: Record<SubsystemEdgeMechanism, 'solid' | 'dashed'
1024
1020
  /** Mechanism → [description, verifiable-with-graphify]. Drives the "not
1025
1021
  * directly verifiable" styling of edge labels. */
1026
1022
  export const MECHANISM_DESCRIPTIONS: [SubsystemEdgeMechanism, string, boolean][] = [
1027
- ['imports', 'import statement (code-level dependency)', true],
1028
1023
  ['calls', 'function/method call (call graph edge)', true],
1029
1024
  ['extends', 'class inheritance', true],
1030
1025
  ['inherits', 'class inheritance', true],
@@ -539,7 +539,7 @@ export function SubsystemEdge({
539
539
  markerEnd,
540
540
  }: EdgeProps<SubsystemGraphEdge>) {
541
541
  const path = data?.elkPath ?? '';
542
- const mechanism = data?.mechanism ?? 'imports';
542
+ const mechanism = data?.mechanism ?? 'uses';
543
543
  const color = MECHANISM_COLOR[mechanism] ?? '#888';
544
544
  // Dash style comes from the mechanism table (dashed = inverted-control or
545
545
  // observational relationships: hierarchy, registration, watches).