@principal-ai/subsystems-react 0.53.6 → 0.55.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 (110) 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/IssueList.d.ts +4 -5
  35. package/dist/subsystem/IssueList.d.ts.map +1 -1
  36. package/dist/subsystem/IssueList.js +33 -28
  37. package/dist/subsystem/IssueList.js.map +1 -1
  38. package/dist/subsystem/SubsystemComponentGraph.d.ts +33 -0
  39. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  40. package/dist/subsystem/SubsystemComponentGraph.js +367 -414
  41. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  42. package/dist/subsystem/TrailStepsNav.d.ts +48 -0
  43. package/dist/subsystem/TrailStepsNav.d.ts.map +1 -0
  44. package/dist/subsystem/TrailStepsNav.js +189 -0
  45. package/dist/subsystem/TrailStepsNav.js.map +1 -0
  46. package/dist/subsystem/TrailsPanel.d.ts.map +1 -1
  47. package/dist/subsystem/TrailsPanel.js +3 -4
  48. package/dist/subsystem/TrailsPanel.js.map +1 -1
  49. package/dist/subsystem/__fixtures__/c4Drilldown.d.ts +151 -0
  50. package/dist/subsystem/__fixtures__/c4Drilldown.d.ts.map +1 -0
  51. package/dist/subsystem/__fixtures__/c4Drilldown.js +783 -0
  52. package/dist/subsystem/__fixtures__/c4Drilldown.js.map +1 -0
  53. package/dist/subsystem/c4.d.ts +39 -8
  54. package/dist/subsystem/c4.d.ts.map +1 -1
  55. package/dist/subsystem/c4.js.map +1 -1
  56. package/dist/subsystem/declarationRef.d.ts +26 -18
  57. package/dist/subsystem/declarationRef.d.ts.map +1 -1
  58. package/dist/subsystem/declarationRef.js +9 -4
  59. package/dist/subsystem/declarationRef.js.map +1 -1
  60. package/dist/subsystem/model.d.ts +19 -1
  61. package/dist/subsystem/model.d.ts.map +1 -1
  62. package/dist/subsystem/model.js +12 -2
  63. package/dist/subsystem/model.js.map +1 -1
  64. package/dist/subsystem/nodes.d.ts.map +1 -1
  65. package/dist/subsystem/nodes.js +24 -4
  66. package/dist/subsystem/nodes.js.map +1 -1
  67. package/dist/subsystem/trailBrief.d.ts.map +1 -1
  68. package/dist/subsystem/trailBrief.js +6 -1
  69. package/dist/subsystem/trailBrief.js.map +1 -1
  70. package/package.json +3 -3
  71. package/src/index.ts +9 -2
  72. package/src/pierre/PierreTrailCodeView.tsx +9 -13
  73. package/src/stories/Subsystem/C4Graph/C4ToComponentDrill.stories.tsx +496 -0
  74. package/src/stories/Subsystem/ComponentGraph/AnimatedProgression.stories.tsx +7 -7
  75. package/src/stories/Subsystem/ComponentGraph/Constructs.stories.tsx +19 -25
  76. package/src/stories/Subsystem/ComponentGraph/DetailPanel.stories.tsx +8 -10
  77. package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +40 -33
  78. package/src/stories/Subsystem/ComponentGraph/ProposedTrails.stories.tsx +6 -6
  79. package/src/stories/Subsystem/ComponentGraph/TrailAutoplay.stories.tsx +7 -7
  80. package/src/stories/Subsystem/ComponentGraph/Trails.stories.tsx +27 -30
  81. package/src/stories/Subsystem/ComponentGraph/fixtures.ts +1 -2
  82. package/src/stories/Subsystem/ConstructsCatalog.stories.tsx +10 -13
  83. package/src/stories/Subsystem/EgoGraph/EgoGraph.stories.tsx +3 -4
  84. package/src/stories/Subsystem/IssueIcons.stories.tsx +65 -0
  85. package/src/stories/Subsystem/Sidebar/GraphSidebar.stories.tsx +206 -0
  86. package/src/subsystem/C4Graph.groups.test.ts +96 -2
  87. package/src/subsystem/C4Graph.tsx +1377 -165
  88. package/src/subsystem/C4NodeCard.test.ts +1 -2
  89. package/src/subsystem/C4NodeCard.tsx +24 -2
  90. package/src/subsystem/ComponentDeclaration.tsx +18 -8
  91. package/src/subsystem/ConstructsCatalog.tsx +23 -8
  92. package/src/subsystem/GraphSidebar.test.tsx +152 -0
  93. package/src/subsystem/GraphSidebar.tsx +300 -0
  94. package/src/subsystem/GraphWithSidebar.test.tsx +78 -0
  95. package/src/subsystem/GraphWithSidebar.tsx +60 -0
  96. package/src/subsystem/IssueList.tsx +34 -29
  97. package/src/subsystem/SubsystemComponentGraph.tsx +290 -376
  98. package/src/subsystem/TrailStepsNav.test.tsx +179 -0
  99. package/src/subsystem/TrailStepsNav.tsx +372 -0
  100. package/src/subsystem/TrailsPanel.tsx +3 -4
  101. package/src/subsystem/__fixtures__/c4Drilldown.test.ts +338 -0
  102. package/src/subsystem/__fixtures__/c4Drilldown.ts +847 -0
  103. package/src/subsystem/c4.ts +41 -8
  104. package/src/subsystem/declarationRef.ts +28 -19
  105. package/src/subsystem/model.test.ts +32 -12
  106. package/src/subsystem/model.ts +30 -1
  107. package/src/subsystem/nodes.group.test.tsx +82 -0
  108. package/src/subsystem/nodes.tsx +30 -2
  109. package/src/subsystem/trailBrief.test.ts +2 -2
  110. package/src/subsystem/trailBrief.ts +6 -1
@@ -0,0 +1,78 @@
1
+ import { afterEach, describe, expect, test } from 'bun:test';
2
+ import { createElement } from 'react';
3
+ import { Window } from 'happy-dom';
4
+ import { cleanup, render } from '@testing-library/react/pure';
5
+ import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
6
+ import { GraphWithSidebar, GraphSidebar } from './GraphWithSidebar';
7
+
8
+ // Bun's happy-dom test environment isn't registered (no @happy-dom/global-
9
+ // registrator), so wire the DOM primitives React DOM needs before rendering.
10
+ const dom = new Window();
11
+ const globals = globalThis as unknown as Record<string, unknown>;
12
+ globals.window = dom;
13
+ globals.document = dom.document;
14
+ globals.navigator = dom.navigator;
15
+ globals.HTMLElement = dom.HTMLElement;
16
+ globals.Node = dom.Node;
17
+ globals.Element = dom.Element;
18
+ globals.Event = dom.Event;
19
+ globals.getComputedStyle = dom.getComputedStyle.bind(dom);
20
+ globals.requestAnimationFrame = dom.requestAnimationFrame.bind(dom);
21
+ globals.cancelAnimationFrame = dom.cancelAnimationFrame.bind(dom);
22
+ globals.localStorage = dom.localStorage;
23
+ globals.matchMedia = dom.matchMedia.bind(dom);
24
+
25
+ afterEach(cleanup);
26
+
27
+ function renderWrapper(props: Record<string, unknown>) {
28
+ return render(
29
+ createElement(
30
+ ThemeProvider,
31
+ { theme: defaultEditorTheme },
32
+ createElement(GraphWithSidebar as never, props as never),
33
+ ),
34
+ );
35
+ }
36
+
37
+ describe('GraphWithSidebar composition', () => {
38
+ test('composes GraphSidebar next to graph content', () => {
39
+ const { getByText, getByLabelText } = renderWrapper({
40
+ header: createElement('span', null, 'title'),
41
+ sidebarBody: createElement('span', null, 'panel'),
42
+ children: createElement('div', null, 'canvas'),
43
+ });
44
+ expect(getByText('title')).toBeTruthy();
45
+ expect(getByText('panel')).toBeTruthy();
46
+ expect(getByText('canvas')).toBeTruthy();
47
+ expect(getByLabelText('Resize sidebar')).toBeTruthy();
48
+ });
49
+
50
+ test('hidden renders only the content — no sidebar, no handle', () => {
51
+ const { queryByLabelText, getByText } = renderWrapper({
52
+ hidden: true,
53
+ header: createElement('span', null, 'title'),
54
+ children: createElement('div', null, 'canvas'),
55
+ });
56
+ expect(getByText('canvas')).toBeTruthy();
57
+ expect(queryByLabelText('Resize sidebar')).toBeNull();
58
+ expect(() => getByText('title')).toThrow();
59
+ });
60
+
61
+ test('wrapper exposes GraphSidebar as its own export', () => {
62
+ expect(GraphSidebar).toBeTypeOf('function');
63
+ });
64
+
65
+ test('the content slot is a flex column so a flex-filling child can size', () => {
66
+ // The component graph's canvas fills the pane with `flex: 1 1 0%`, which
67
+ // only resolves against a flex parent — a block slot collapses it to 0 and
68
+ // the canvas renders blank.
69
+ const { container } = renderWrapper({
70
+ sidebarBody: createElement('span', null, 'panel'),
71
+ children: createElement('div', null, 'canvas'),
72
+ });
73
+ const root = container.firstElementChild as HTMLElement;
74
+ const content = root.lastElementChild as HTMLElement;
75
+ expect(content.style.display).toBe('flex');
76
+ expect(content.style.flexDirection).toBe('column');
77
+ });
78
+ });
@@ -0,0 +1,60 @@
1
+ import { useState } from 'react';
2
+ import type { ReactNode } from 'react';
3
+ import { GraphSidebar } from './GraphSidebar';
4
+ import type { GraphSidebarProps } from './GraphSidebar';
5
+
6
+ /**
7
+ * GraphWithSidebar — a graph laid out beside its own sidebar.
8
+ *
9
+ * A flex row: `[GraphSidebar][graph content]`. The sidebar is the standalone
10
+ * `GraphSidebar` panel (its right edge is the resize handle, and it owns the
11
+ * width drag + persistence); this component only composes it next to a graph
12
+ * and locks the row against text selection while a resize is in progress.
13
+ *
14
+ * Every sidebar prop is forwarded straight to `GraphSidebar`: `header`,
15
+ * `tabs`/`activeTab`/`onTabChange`, `sidebarBody`, `footer`, `hidden`,
16
+ * `bodyHidden`, `stretchHeader`, `width`/`onWidthChange`, `defaultWidth`,
17
+ * `minWidth`/`maxWidth`, `onResizeEnd`, `persistKey`. See GraphSidebar for the
18
+ * semantics.
19
+ */
20
+
21
+ export interface GraphWithSidebarProps extends GraphSidebarProps {
22
+ /** The graph content to the right of the sidebar. */
23
+ children?: ReactNode;
24
+ }
25
+
26
+ export { GraphSidebar } from './GraphSidebar';
27
+ export type { GraphSidebarProps, GraphSidebarTab } from './GraphSidebar';
28
+
29
+ export function GraphWithSidebar({ children, ...sidebarProps }: GraphWithSidebarProps) {
30
+ const [dragging, setDragging] = useState(false);
31
+
32
+ return (
33
+ <div
34
+ style={{
35
+ width: '100%',
36
+ height: '100%',
37
+ display: 'flex',
38
+ flexDirection: 'row',
39
+ userSelect: dragging ? 'none' : undefined,
40
+ }}
41
+ >
42
+ <GraphSidebar {...sidebarProps} onDraggingChange={setDragging} />
43
+ <div
44
+ style={{
45
+ flex: 1,
46
+ minWidth: 0,
47
+ position: 'relative',
48
+ minHeight: 0,
49
+ // A flex column so a child that fills the pane with flex-grow (the
50
+ // component graph's canvas tree) resolves against a real flex
51
+ // parent instead of collapsing to 0.
52
+ display: 'flex',
53
+ flexDirection: 'column',
54
+ }}
55
+ >
56
+ {children}
57
+ </div>
58
+ </div>
59
+ );
60
+ }
@@ -20,14 +20,13 @@ import {
20
20
  CircleCheck,
21
21
  CircleDashed,
22
22
  CircleEllipsis,
23
- CircleHelp,
24
23
  CircleSlash,
25
24
  CircleX,
26
25
  Component,
26
+ FileCodeCorner,
27
27
  FileX,
28
28
  FolderGit2,
29
29
  Footprints,
30
- History,
31
30
  Link2Off,
32
31
  MapPin,
33
32
  Minus,
@@ -103,7 +102,8 @@ const KIND_CATEGORY: Record<string, SubsystemIssueCategory> = {
103
102
  missing_file: 'construct',
104
103
  symbol_ambiguous: 'construct',
105
104
  symbol_unmatched: 'construct',
106
- stale_declaration: 'construct',
105
+ construct_span_unconfirmed: 'construct',
106
+ construct_span_stale: 'construct',
107
107
  construct_mismatch: 'construct',
108
108
  construct_unconfirmed: 'construct',
109
109
  signature_mismatch: 'construct',
@@ -121,8 +121,8 @@ const KIND_CATEGORY: Record<string, SubsystemIssueCategory> = {
121
121
  boundary_process_rejected: 'dynamic-topology',
122
122
  boundary_process_unbacked: 'dynamic-topology',
123
123
  trail: 'trail',
124
- step_unconfirmed: 'trail',
125
- step_stale: 'trail',
124
+ step_span_unconfirmed: 'trail',
125
+ step_span_stale: 'trail',
126
126
  };
127
127
 
128
128
  /**
@@ -142,7 +142,8 @@ const KIND_RUNG: Record<string, SubsystemIssueRung> = {
142
142
  missing_file: 'file',
143
143
  symbol_unmatched: 'symbol',
144
144
  symbol_ambiguous: 'symbol',
145
- stale_declaration: 'declaration',
145
+ construct_span_unconfirmed: 'declaration',
146
+ construct_span_stale: 'declaration',
146
147
  construct_unconfirmed: 'type',
147
148
  construct_mismatch: 'type',
148
149
  signature_unconfirmed: 'signature',
@@ -173,11 +174,10 @@ export const ISSUE_RUNG_ICON: Record<SubsystemIssueRung, LucideIcon> = {
173
174
  };
174
175
 
175
176
  /**
176
- * Icons for findings that are about a BOUNDARY rather than a construct, so they
177
- * have no rung to key off. These are what earns a frame badge: a kind listed
178
- * here is badged on the region node it names (see `ISSUE_KIND_ICON` consumers),
179
- * because the finding is a property of the region's shape — no single member
180
- * construct is at fault.
177
+ * Icons for findings that name their own glyph rather than keying off a rung —
178
+ * boundary findings (which badge the region node they name) and the span
179
+ * findings (construct + step share `FileCodeCorner`). A kind listed here wins
180
+ * over its rung icon in the row; the span state is carried by color.
181
181
  */
182
182
  export const ISSUE_KIND_ICON: Record<string, LucideIcon> = {
183
183
  // One module, more than one claimed process parent — a containment that can't
@@ -191,9 +191,13 @@ export const ISSUE_KIND_ICON: Record<string, LucideIcon> = {
191
191
  boundary_process_rejected: CircleX,
192
192
  // Container-first gate: a claim cites a key no accepted container owns.
193
193
  boundary_process_unbacked: Link2Off,
194
- // Trail step verification: unconfirmed = no augmentation yet, stale = code changed
195
- step_unconfirmed: CircleHelp,
196
- step_stale: History,
194
+ // Span findings — one family, state carried by color (red = not captured,
195
+ // amber = changed). Construct span sits on the declaration rung but keeps
196
+ // this glyph; step span has no rung.
197
+ construct_span_unconfirmed: FileCodeCorner,
198
+ construct_span_stale: FileCodeCorner,
199
+ step_span_unconfirmed: FileCodeCorner,
200
+ step_span_stale: FileCodeCorner,
197
201
  };
198
202
 
199
203
  /**
@@ -264,7 +268,8 @@ const KIND_LABEL: Record<string, string> = {
264
268
  missing_file: 'Construct declaration file missing',
265
269
  symbol_ambiguous: 'Multiple construct symbol matches',
266
270
  symbol_unmatched: 'Construct symbol missing',
267
- stale_declaration: 'Construct declaration drift',
271
+ construct_span_unconfirmed: 'Construct span not captured',
272
+ construct_span_stale: 'Construct span drift',
268
273
  construct_mismatch: 'Construct type mismatch',
269
274
  construct_unconfirmed: 'Construct type unconfirmed',
270
275
  signature_mismatch: 'Construct signature mismatch',
@@ -281,8 +286,8 @@ const KIND_LABEL: Record<string, string> = {
281
286
  boundary_process_rejected: 'Container rejected',
282
287
  boundary_process_unbacked: 'Process claim unbacked',
283
288
  trail: 'Step issue',
284
- step_unconfirmed: 'Call site not verified',
285
- step_stale: 'Call site changed',
289
+ step_span_unconfirmed: 'Step span not captured',
290
+ step_span_stale: 'Step span changed',
286
291
  };
287
292
 
288
293
  /** Label for an issue's kind. Explicit `kindLabel` wins; unknown kinds humanize. */
@@ -305,7 +310,8 @@ const KIND_ORDER: Record<string, number> = {
305
310
  missing_file: 10,
306
311
  symbol_unmatched: 11,
307
312
  symbol_ambiguous: 12,
308
- stale_declaration: 13,
313
+ construct_span_unconfirmed: 12.5,
314
+ construct_span_stale: 13,
309
315
  construct_unconfirmed: 14,
310
316
  construct_mismatch: 15,
311
317
  signature_unconfirmed: 16,
@@ -321,8 +327,8 @@ const KIND_ORDER: Record<string, number> = {
321
327
  boundary_process_rejected: 35,
322
328
  // Trail — mechanical issues first, then step verification
323
329
  trail: 40,
324
- step_unconfirmed: 41,
325
- step_stale: 42,
330
+ step_span_unconfirmed: 41,
331
+ step_span_stale: 42,
326
332
  };
327
333
 
328
334
  const KIND_ORDER_FALLBACK = 100;
@@ -491,19 +497,18 @@ export function SubsystemIssueCard({
491
497
  const showDetail = expanded;
492
498
  const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
493
499
  const color = severityColor(issue.severity, theme.colors);
494
- // Leading icon: the verification rung when the finding maps to one, else the
495
- // layer's icon — every row reads with an icon.
500
+ // Leading icon: an explicit kind glyph (boundary badge, span family) wins;
501
+ // else the verification rung (construct ladder); else the target kind; else
502
+ // the layer's icon — every row reads with an icon.
496
503
  const rung = issueRung(issue.kind);
497
- // Rung icons are the construct ladder; a boundary finding names its own icon
498
- // (it has no rung); everything else falls back to its layer's icon.
499
504
  const targetIcon = issue.target?.kind
500
505
  ? ISSUE_TARGET_KIND_ICON[issue.target.kind]
501
506
  : undefined;
502
- const RowIcon: LucideIcon = rung
503
- ? ISSUE_RUNG_ICON[rung]
504
- : (ISSUE_KIND_ICON[issue.kind] ??
505
- targetIcon ??
506
- SUBSYSTEM_ISSUE_CATEGORY_ICON[issueCategory(issue)]);
507
+ const RowIcon: LucideIcon =
508
+ ISSUE_KIND_ICON[issue.kind] ??
509
+ (rung
510
+ ? ISSUE_RUNG_ICON[rung]
511
+ : (targetIcon ?? SUBSYSTEM_ISSUE_CATEGORY_ICON[issueCategory(issue)]));
507
512
  const activate = () => {
508
513
  const next = !expanded;
509
514
  setExpanded(next);