@principal-ai/subsystems-react 0.20.5 → 0.20.6

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 (36) hide show
  1. package/dist/graphify/construct.d.ts +23 -0
  2. package/dist/graphify/construct.d.ts.map +1 -0
  3. package/dist/graphify/construct.js +77 -0
  4. package/dist/graphify/construct.js.map +1 -0
  5. package/dist/graphify/index.d.ts +2 -2
  6. package/dist/graphify/index.d.ts.map +1 -1
  7. package/dist/graphify/index.js +1 -1
  8. package/dist/graphify/index.js.map +1 -1
  9. package/dist/index.d.ts +3 -3
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +2 -2
  12. package/dist/index.js.map +1 -1
  13. package/dist/subsystem/ComponentDeclaration.js +1 -1
  14. package/dist/subsystem/ComponentDeclaration.js.map +1 -1
  15. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  16. package/dist/subsystem/SubsystemComponentGraph.js +23 -2
  17. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  18. package/dist/subsystem/model.d.ts +24 -7
  19. package/dist/subsystem/model.d.ts.map +1 -1
  20. package/dist/subsystem/model.js +25 -2
  21. package/dist/subsystem/model.js.map +1 -1
  22. package/dist/subsystem/nodes.d.ts.map +1 -1
  23. package/dist/subsystem/nodes.js +18 -10
  24. package/dist/subsystem/nodes.js.map +1 -1
  25. package/package.json +1 -1
  26. package/src/graphify/{kind.test.ts → construct.test.ts} +20 -20
  27. package/src/graphify/{kind.ts → construct.ts} +17 -17
  28. package/src/graphify/index.ts +5 -2
  29. package/src/index.ts +7 -4
  30. package/src/stories/Subsystem/ComponentGraph/Captures.stories.tsx +2 -6
  31. package/src/stories/Subsystem/ComponentGraph/Proposed.stories.tsx +254 -0
  32. package/src/subsystem/ComponentDeclaration.tsx +1 -1
  33. package/src/subsystem/SubsystemComponentGraph.tsx +23 -2
  34. package/src/subsystem/model.test.ts +22 -0
  35. package/src/subsystem/model.ts +38 -9
  36. package/src/subsystem/nodes.tsx +21 -14
@@ -8,6 +8,7 @@ import {
8
8
  buildSubsystemGraph,
9
9
  deriveNameFromSymbol,
10
10
  constructBadgeLabel,
11
+ rightBadgeLabel,
11
12
  nodeMinWidthForBadges,
12
13
  formatPurl,
13
14
  packageColor,
@@ -156,6 +157,27 @@ describe('subsystem graph model', () => {
156
157
  role: 'entry',
157
158
  });
158
159
  expect(withRole).toBeGreaterThan(stereotype);
160
+
161
+ const withProposed = nodeMinWidthForBadges({
162
+ construct: 'function',
163
+ proposed: true,
164
+ });
165
+ expect(withProposed).toBeGreaterThan(plain);
166
+
167
+ const withBoth = nodeMinWidthForBadges({
168
+ construct: 'function',
169
+ role: 'entry',
170
+ proposed: true,
171
+ });
172
+ // proposed-only label is shorter than entry · proposed; still wider than plain
173
+ expect(withBoth).toBe(withProposed);
174
+ });
175
+
176
+ test('rightBadgeLabel prefers proposed over role', () => {
177
+ expect(rightBadgeLabel({})).toBeNull();
178
+ expect(rightBadgeLabel({ proposed: true })).toBe('proposed');
179
+ expect(rightBadgeLabel({ role: 'entry' })).toBe('entry');
180
+ expect(rightBadgeLabel({ role: 'service', proposed: true })).toBe('proposed');
159
181
  });
160
182
 
161
183
  test('deriveNameFromSymbol falls back to file basename for modules', () => {
@@ -126,6 +126,13 @@ export interface SubsystemComponent {
126
126
  purl: string;
127
127
  /** One-line purpose shown on the node. */
128
128
  purpose?: string;
129
+ /**
130
+ * Design / migration placeholder — participates in edges and flows but is
131
+ * not a live source declaration yet. Verification skips source checks until
132
+ * promoted (`proposed` cleared, `file` + `symbol` filled). Orthogonal to
133
+ * `construct` (intended shape) and `role` (topology).
134
+ */
135
+ proposed?: boolean;
129
136
  /**
130
137
  * Semantic role — where the node sits in the topology (boundary element,
131
138
  * external system), orthogonal to `construct` (what it is). Drives the
@@ -179,13 +186,6 @@ export interface SubsystemComponent {
179
186
  * letting ELK guess the order.
180
187
  */
181
188
  layer?: number;
182
- /**
183
- * How this session *occupied* the component — `edited` (modified it) vs
184
- * `analyzed` (read deeply / built an understanding) vs `referenced`
185
- * (touched in passing). Drives clustering: edits strengthen a subsystem,
186
- * but a read-only investigation can still form one around an analyzed seam.
187
- */
188
- capture?: 'edited' | 'analyzed' | 'referenced';
189
189
  /**
190
190
  * Graphify drill-down detail for this component, when the facet is anchored
191
191
  * to a graphify node. Discriminated by kind (class/function/type/module/store/
@@ -525,6 +525,33 @@ export const ROLE_LABEL: Record<SubsystemComponentRole, string> = {
525
525
  service: 'service',
526
526
  };
527
527
 
528
+ /** Right-badge accent for `proposed: true` (no role, or combined with role). */
529
+ export const PROPOSED_COLOR = '#b8860b'; // darkgoldenrod — not-yet-in-source
530
+
531
+ /**
532
+ * Top-right badge label: proposed wins over role when both are set
533
+ * (proposed is the temporary exception to scan for; role stays on the model).
534
+ * Returns null when neither applies.
535
+ */
536
+ export function rightBadgeLabel(component: {
537
+ role?: SubsystemComponentRole;
538
+ proposed?: boolean;
539
+ }): string | null {
540
+ if (component.proposed) return 'proposed';
541
+ if (component.role != null) return ROLE_LABEL[component.role];
542
+ return null;
543
+ }
544
+
545
+ /** Color for the top-right badge (role and/or proposed). */
546
+ export function rightBadgeColor(component: {
547
+ role?: SubsystemComponentRole;
548
+ proposed?: boolean;
549
+ }): string | null {
550
+ if (component.proposed) return PROPOSED_COLOR;
551
+ if (component.role != null) return ROLE_COLOR[component.role];
552
+ return null;
553
+ }
554
+
528
555
  /**
529
556
  * Primary badge text for a node: prefer framework stereotype over the
530
557
  * language construct so a React UI unit reads as "component" / "hook"
@@ -576,13 +603,15 @@ export function nodeMinWidthForBadges(component: {
576
603
  framework?: string;
577
604
  stereotype?: string;
578
605
  role?: SubsystemComponentRole;
606
+ proposed?: boolean;
579
607
  entityKind?: string;
580
608
  }): number {
581
609
  const left = estimateBadgeLabelWidth(constructBadgeLabel(component));
582
- if (component.role == null) {
610
+ const rightLabel = rightBadgeLabel(component);
611
+ if (rightLabel == null) {
583
612
  return Math.max(NODE_CSS_MIN_WIDTH, BADGE_EDGE_INSET + left + BADGE_EDGE_INSET);
584
613
  }
585
- const right = estimateBadgeLabelWidth(ROLE_LABEL[component.role]);
614
+ const right = estimateBadgeLabelWidth(rightLabel);
586
615
  return Math.max(
587
616
  NODE_CSS_MIN_WIDTH,
588
617
  BADGE_EDGE_INSET + left + BADGE_PAIR_GAP + right + BADGE_EDGE_INSET,
@@ -19,9 +19,10 @@ import { useTheme } from '@principal-ade/industry-theme';
19
19
  import {
20
20
  MECHANISM_COLOR,
21
21
  MECHANISM_STYLE,
22
- ROLE_COLOR,
23
- ROLE_LABEL,
22
+ PROPOSED_COLOR,
24
23
  constructBadgeLabel,
24
+ rightBadgeLabel,
25
+ rightBadgeColor,
25
26
  deriveNameFromSymbol,
26
27
  BADGE_EDGE_INSET,
27
28
  nodeMinWidthForBadges,
@@ -92,11 +93,20 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
92
93
  // `symbol` is the source of truth; `name` is derived from it consistently.
93
94
  const displayName = deriveNameFromSymbol(c.symbol, c.construct, c.name, c.file, c.stereotype);
94
95
  // Top badges are absolutely positioned — widen the node so they nowrap
95
- // instead of wrapping, including when construct + role badges share the top.
96
+ // instead of wrapping, including when construct + role/proposed share the top.
96
97
  const badgeMinWidth = nodeMinWidthForBadges(c);
98
+ const topRightLabel = rightBadgeLabel(c);
99
+ const topRightColor = rightBadgeColor(c);
97
100
  // Set while a file is open in the drawer: true → spotlight, false → dim,
98
101
  // absent (no file open) → neutral.
99
102
  const fileMatch = data.fileMatch as boolean | undefined;
103
+ // Border: proposed uses the goldenrod accent (dashed); construct color stays
104
+ // on the left badge. File-open spotlight still wins with primary.
105
+ const borderColor = fileMatch
106
+ ? theme.colors.primary
107
+ : c.proposed
108
+ ? PROPOSED_COLOR
109
+ : color;
100
110
  // Selection is stamped into data by the graph component — React Flow's own
101
111
  // `selected` never updates because the node stops click propagation.
102
112
  const isSelected = selected || (data.isSelected as boolean | undefined) === true;
@@ -129,10 +139,9 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
129
139
  padding: '6px 10px',
130
140
  borderRadius: 8,
131
141
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
132
- // Selected / file-matched nodes get a thicker border; kind colors are
133
- // kept for selection (thickness only). The file-open spotlight keeps
134
- // its primary tint.
135
- border: `${isSelected || fileMatch ? 4 : 2}px solid ${fileMatch ? theme.colors.primary : color}`,
142
+ // Selected / file-matched nodes get a thicker border. Proposed nodes
143
+ // use a dashed goldenrod border; left construct badge keeps construct color.
144
+ border: `${isSelected || fileMatch ? 4 : 2}px ${c.proposed ? 'dashed' : 'solid'} ${borderColor}`,
136
145
  boxShadow: fileMatch
137
146
  ? `0 1px 4px rgba(0,0,0,0.25), 0 0 12px ${theme.colors.primary}55`
138
147
  : '0 1px 4px rgba(0,0,0,0.25)',
@@ -167,10 +176,8 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
167
176
  {constructBadgeLabel(c)}
168
177
  </div>
169
178
 
170
- {/* Role badge — top-right, only when the node carries a topology role.
171
- Role color (entry orange / service blue) on the badge; the node
172
- border stays construct-colored. */}
173
- {c.role != null && (
179
+ {/* Right badge — proposed wins over role when both are set. */}
180
+ {topRightLabel != null && topRightColor != null && (
174
181
  <div
175
182
  style={{
176
183
  position: 'absolute',
@@ -183,14 +190,14 @@ export function SubsystemComponentNode(props: NodeProps<Node<SubsystemGraphNodeD
183
190
  textTransform: 'uppercase',
184
191
  lineHeight: '17px',
185
192
  whiteSpace: 'nowrap',
186
- color: ROLE_COLOR[c.role],
193
+ color: topRightColor,
187
194
  background: theme.colors.backgroundSecondary ?? theme.colors.background,
188
- border: `1px solid ${ROLE_COLOR[c.role]}`,
195
+ border: `1px solid ${topRightColor}`,
189
196
  borderRadius: 4,
190
197
  padding: '0 5px',
191
198
  }}
192
199
  >
193
- {ROLE_LABEL[c.role]}
200
+ {topRightLabel}
194
201
  </div>
195
202
  )}
196
203