@principal-ai/subsystems-react 0.38.0 → 0.39.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 (54) hide show
  1. package/dist/graphify/consolidated.d.ts +8 -0
  2. package/dist/graphify/consolidated.d.ts.map +1 -1
  3. package/dist/graphify/construct.d.ts +18 -0
  4. package/dist/graphify/construct.d.ts.map +1 -1
  5. package/dist/graphify/construct.js +23 -0
  6. package/dist/graphify/construct.js.map +1 -1
  7. package/dist/graphify/index.d.ts +2 -2
  8. package/dist/graphify/index.d.ts.map +1 -1
  9. package/dist/graphify/index.js +1 -1
  10. package/dist/graphify/index.js.map +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/subsystem/IssueList.d.ts +49 -3
  16. package/dist/subsystem/IssueList.d.ts.map +1 -1
  17. package/dist/subsystem/IssueList.js +180 -79
  18. package/dist/subsystem/IssueList.js.map +1 -1
  19. package/dist/subsystem/SubsystemComponentGraph.d.ts +9 -1
  20. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  21. package/dist/subsystem/SubsystemComponentGraph.js +461 -12
  22. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  23. package/dist/subsystem/formatDeclaration.js +19 -5
  24. package/dist/subsystem/formatDeclaration.js.map +1 -1
  25. package/dist/subsystem/model.d.ts +38 -0
  26. package/dist/subsystem/model.d.ts.map +1 -1
  27. package/dist/subsystem/model.js.map +1 -1
  28. package/dist/subsystem/nodes.d.ts.map +1 -1
  29. package/dist/subsystem/nodes.js +124 -67
  30. package/dist/subsystem/nodes.js.map +1 -1
  31. package/dist/subsystem/symbolRefs.d.ts.map +1 -1
  32. package/dist/subsystem/symbolRefs.js +2 -0
  33. package/dist/subsystem/symbolRefs.js.map +1 -1
  34. package/package.json +3 -3
  35. package/src/graphify/consolidated.ts +8 -0
  36. package/src/graphify/construct.test.ts +61 -1
  37. package/src/graphify/construct.ts +33 -0
  38. package/src/graphify/index.ts +6 -1
  39. package/src/index.ts +1 -0
  40. package/src/stories/Subsystem/ComponentGraph/IssueOverlay.stories.tsx +625 -0
  41. package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +7 -1
  42. package/src/stories/Subsystem/ComponentGraph/NodeAnatomy.stories.tsx +40 -73
  43. package/src/stories/Subsystem/ComponentGraph/StoreValueType.stories.tsx +261 -0
  44. package/src/stories/Subsystem/StoreValueTypeDeclaration.stories.tsx +182 -0
  45. package/src/subsystem/IssueList.focus.test.tsx +113 -0
  46. package/src/subsystem/IssueList.icon.test.tsx +95 -0
  47. package/src/subsystem/IssueList.tsx +267 -111
  48. package/src/subsystem/SubsystemComponentGraph.tsx +530 -12
  49. package/src/subsystem/formatDeclaration.test.ts +64 -0
  50. package/src/subsystem/formatDeclaration.ts +20 -5
  51. package/src/subsystem/model.ts +46 -0
  52. package/src/subsystem/nodes.group.test.tsx +52 -0
  53. package/src/subsystem/nodes.tsx +102 -2
  54. package/src/subsystem/symbolRefs.ts +2 -0
@@ -0,0 +1,113 @@
1
+ import { afterEach, describe, expect, test } from 'bun:test';
2
+ import { createElement } from 'react';
3
+ import { Window } from 'happy-dom';
4
+ import { cleanup, fireEvent, render } from '@testing-library/react/pure';
5
+ import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
6
+ import { SubsystemIssueList, type SubsystemIssue } from './IssueList';
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
+ const issues: SubsystemIssue[] = [
28
+ {
29
+ id: 'ct',
30
+ severity: 'error',
31
+ kind: 'missing_file',
32
+ message: 'construct finding',
33
+ target: { kind: 'component', id: 'alpha', label: 'Alpha' },
34
+ },
35
+ {
36
+ id: 'wl',
37
+ severity: 'info',
38
+ kind: 'walkthrough',
39
+ message: 'walkthrough finding',
40
+ target: { kind: 'walkthrough', id: 'w', label: 'Walkthrough' },
41
+ },
42
+ ];
43
+
44
+ function setup() {
45
+ const events: string[] = [];
46
+ const view = render(
47
+ createElement(
48
+ ThemeProvider,
49
+ { theme: defaultEditorTheme },
50
+ createElement(SubsystemIssueList, {
51
+ issues,
52
+ onSelectIssue: (i) => events.push(`select:${i.id}`),
53
+ onDeselectIssue: (i) => events.push(`deselect:${i.id}`),
54
+ }),
55
+ ),
56
+ );
57
+ const category = (name: string) =>
58
+ [...view.container.querySelectorAll('button')].find((b) =>
59
+ b.textContent?.includes(name),
60
+ ) as HTMLElement;
61
+ const card = (label: string) =>
62
+ [...view.container.querySelectorAll('[role="button"]')].find((b) =>
63
+ b.textContent?.includes(label),
64
+ ) as HTMLElement;
65
+ return { ...view, events, category, card };
66
+ }
67
+
68
+ describe('collapsing a category retracts the focus it established', () => {
69
+ // Closing a CATEGORY unmounts its cards, so a card stops being expanded
70
+ // without ever toggling itself. A host that framed a target on expand has to
71
+ // hear about it, or the canvas stays parked on a target the list no longer
72
+ // shows as open.
73
+ test('clicking the open category emits a deselect for its expanded card', () => {
74
+ const { events, category, card } = setup();
75
+ fireEvent.click(category('Constructs'));
76
+ fireEvent.click(card('Alpha'));
77
+ expect(events).toEqual(['select:ct']);
78
+
79
+ fireEvent.click(category('Constructs'));
80
+ expect(events).toEqual(['select:ct', 'deselect:ct']);
81
+ });
82
+
83
+ test('opening a sibling layer retracts the other layer’s card', () => {
84
+ // The accordion closes every other layer, so that layer's cards unmount
85
+ // on this click — same obligation as collapsing the layer directly.
86
+ const { events, category, card } = setup();
87
+ fireEvent.click(category('Constructs'));
88
+ fireEvent.click(card('Alpha'));
89
+
90
+ fireEvent.click(category('Walkthrough'));
91
+ expect(events).toEqual(['select:ct', 'deselect:ct']);
92
+ });
93
+
94
+ test('collapsing a layer with nothing expanded emits no deselect', () => {
95
+ const { events, category } = setup();
96
+ fireEvent.click(category('Constructs'));
97
+ fireEvent.click(category('Constructs'));
98
+ expect(events).toEqual([]);
99
+ });
100
+
101
+ test('collapsing the card itself still emits exactly one deselect', () => {
102
+ // The ref is cleared by the card's own collapse, so closing the layer
103
+ // afterwards must not re-report it.
104
+ const { events, category, card } = setup();
105
+ fireEvent.click(category('Constructs'));
106
+ fireEvent.click(card('Alpha'));
107
+ fireEvent.click(card('Alpha'));
108
+ expect(events).toEqual(['select:ct', 'deselect:ct']);
109
+
110
+ fireEvent.click(category('Constructs'));
111
+ expect(events).toEqual(['select:ct', 'deselect:ct']);
112
+ });
113
+ });
@@ -0,0 +1,95 @@
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 { SubsystemIssueCard, type SubsystemIssue } from './IssueList';
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
+ const issue: SubsystemIssue = {
28
+ id: 'i1',
29
+ severity: 'info',
30
+ kind: 'walkthrough',
31
+ message: 'Step 2 references a line that moved.',
32
+ target: {
33
+ kind: 'step',
34
+ id: 'flow-1',
35
+ label: 'Checkout',
36
+ detail: 'step 2',
37
+ stepIndex: 1,
38
+ },
39
+ };
40
+
41
+ function renderCard(overrides: Partial<SubsystemIssue> = {}) {
42
+ return render(
43
+ createElement(
44
+ ThemeProvider,
45
+ { theme: defaultEditorTheme },
46
+ createElement(SubsystemIssueCard as never, {
47
+ issue: { ...issue, ...overrides },
48
+ } as never),
49
+ ),
50
+ );
51
+ }
52
+
53
+ // lucide emits "lucide lucide-<name>", so match on substring rather than the
54
+ // whole class string.
55
+ const hasIcon = (container: HTMLElement, name: string) =>
56
+ [...container.querySelectorAll('svg')].some((s) =>
57
+ (s.getAttribute('class') ?? '').includes(`lucide-${name}`),
58
+ );
59
+
60
+ describe('issue card icon', () => {
61
+ test('a step finding wears the step icon, not the flow lane icon', () => {
62
+ // The audit kind is `walkthrough` (the whole flow), so without a
63
+ // target-shape icon this card would read as a comment on the flow rather
64
+ // than on the one step that is wrong.
65
+ const { container } = renderCard();
66
+ expect(hasIcon(container, 'footprints')).toBe(true);
67
+ expect(hasIcon(container, 'route')).toBe(false);
68
+ });
69
+
70
+ test('a finding about the flow as a whole keeps the lane icon', () => {
71
+ const { container } = renderCard({
72
+ target: { kind: 'walkthrough', id: 'flow-1', label: 'Checkout' },
73
+ });
74
+ expect(hasIcon(container, 'route')).toBe(true);
75
+ expect(hasIcon(container, 'footprints')).toBe(false);
76
+ });
77
+
78
+ test('an explicit kind icon outranks the target-shape icon', () => {
79
+ // A boundary finding about a module is about the region, and says so.
80
+ const { container } = renderCard({
81
+ kind: 'boundary_process_nest_disagree',
82
+ target: { kind: 'step', id: 'flow-1', label: 'Checkout', stepIndex: 0 },
83
+ });
84
+ expect(hasIcon(container, 'split')).toBe(true);
85
+ expect(hasIcon(container, 'footprints')).toBe(false);
86
+ });
87
+
88
+ test('a construct rung still outranks both', () => {
89
+ const { container } = renderCard({
90
+ kind: 'missing_file',
91
+ target: { kind: 'step', id: 'flow-1', label: 'Checkout', stepIndex: 0 },
92
+ });
93
+ expect(hasIcon(container, 'file-x')).toBe(true);
94
+ });
95
+ });