@principal-ai/subsystems-react 0.37.0 → 0.37.1

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 (40) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/subsystem/ComponentDeclaration.d.ts +13 -1
  6. package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
  7. package/dist/subsystem/ComponentDeclaration.js +58 -7
  8. package/dist/subsystem/ComponentDeclaration.js.map +1 -1
  9. package/dist/subsystem/ConstructsCatalog.d.ts +6 -3
  10. package/dist/subsystem/ConstructsCatalog.d.ts.map +1 -1
  11. package/dist/subsystem/ConstructsCatalog.js +206 -217
  12. package/dist/subsystem/ConstructsCatalog.js.map +1 -1
  13. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  14. package/dist/subsystem/SubsystemComponentGraph.js +36 -28
  15. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  16. package/dist/subsystem/SymbolInspectionCard.d.ts.map +1 -1
  17. package/dist/subsystem/SymbolInspectionCard.js +1 -1
  18. package/dist/subsystem/SymbolInspectionCard.js.map +1 -1
  19. package/dist/subsystem/model.d.ts.map +1 -1
  20. package/dist/subsystem/model.js +7 -1
  21. package/dist/subsystem/model.js.map +1 -1
  22. package/dist/utils/edgeLabel.d.ts +51 -0
  23. package/dist/utils/edgeLabel.d.ts.map +1 -0
  24. package/dist/utils/edgeLabel.js +81 -0
  25. package/dist/utils/edgeLabel.js.map +1 -0
  26. package/dist/utils/elkLayout.d.ts +6 -0
  27. package/dist/utils/elkLayout.d.ts.map +1 -1
  28. package/dist/utils/elkLayout.js +27 -6
  29. package/dist/utils/elkLayout.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/index.ts +7 -0
  32. package/src/stories/Subsystem/ConstructsCatalog.stories.tsx +443 -0
  33. package/src/subsystem/ComponentDeclaration.tsx +100 -7
  34. package/src/subsystem/ConstructsCatalog.tsx +291 -323
  35. package/src/subsystem/SubsystemComponentGraph.tsx +74 -29
  36. package/src/subsystem/SymbolInspectionCard.tsx +10 -9
  37. package/src/subsystem/model.ts +7 -1
  38. package/src/utils/edgeLabel.test.ts +23 -0
  39. package/src/utils/edgeLabel.ts +95 -0
  40. package/src/utils/elkLayout.ts +41 -6
@@ -6,7 +6,7 @@
6
6
  * comments. File content lives in the bottom FileDrawer, not here.
7
7
  */
8
8
 
9
- import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
9
+ import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from 'react';
10
10
  import { createPortal } from 'react-dom';
11
11
  import { AlignLeft, FileText } from 'lucide-react';
12
12
  import { useTheme } from '@principal-ade/industry-theme';
@@ -138,6 +138,10 @@ function DetailLink({ children, onClick }: { children: ReactNode; onClick?: () =
138
138
  );
139
139
  }
140
140
 
141
+ /** Width of the `lineNumbers` gutter column and the gap after it. */
142
+ const GUTTER_NUMBER_WIDTH = 40;
143
+ const GUTTER_NUMBER_GAP = 8;
144
+
141
145
  /** Panel props. Callbacks are wired by the graph; standalone usage without
142
146
  * them renders the same panel with nothing clickable. */
143
147
  export interface ComponentDeclarationProps {
@@ -174,6 +178,18 @@ export interface ComponentDeclarationProps {
174
178
  * the file badge: drop the top-right `L#` (redundant) and put the
175
179
  * description toggle in the badge after the line segment. */
176
180
  fileBadgeChrome?: boolean;
181
+ /** Suppress the per-declaration action chrome entirely (file toggle, line
182
+ * label, description toggle). For hosts that surface those in a shared
183
+ * header instead. */
184
+ hideActions?: boolean;
185
+ /** Controlled purpose/description visibility. When provided it overrides the
186
+ * internal toggle, so a host can drive it from a shared header. */
187
+ showPurpose?: boolean;
188
+ /** Render a gutter beside the declaration body and label the construct's own
189
+ * line with its known start line — only that line is accurate, so the rest
190
+ * of the gutter stays blank and aligned. The label opens the file at that
191
+ * line when `onOpenFile` is set. */
192
+ lineNumbers?: boolean;
177
193
  }
178
194
 
179
195
  function verificationSummary(
@@ -318,6 +334,9 @@ export function ComponentDeclaration({
318
334
  declarationOpen = false,
319
335
  showRepoIdentity = true,
320
336
  fileBadgeChrome = false,
337
+ hideActions = false,
338
+ showPurpose: showPurposeProp,
339
+ lineNumbers = false,
321
340
  }: ComponentDeclarationProps) {
322
341
  const { theme, mode } = useTheme();
323
342
  const pierreSyntaxTheme = resolvePierreSyntaxThemeName(mode);
@@ -325,7 +344,8 @@ export function ComponentDeclaration({
325
344
  'path' | 'line' | 'desc' | 'action' | null
326
345
  >(null);
327
346
  const [showFile, setShowFile] = useState(defaultShowFile);
328
- const [showPurpose, setShowPurpose] = useState(false);
347
+ const [showPurposeState, setShowPurposeState] = useState(false);
348
+ const showPurpose = showPurposeProp ?? showPurposeState;
329
349
  const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
330
350
  const color = componentColor(component, pierreSyntaxTheme);
331
351
  const inlineChrome = fileBadgeChrome && showFile && !!component.file;
@@ -497,7 +517,7 @@ export function ComponentDeclaration({
497
517
  {!inlineChrome && lineLocationLabel}
498
518
  {!inlineChrome &&
499
519
  component.purpose?.trim() &&
500
- toggleBtn(showPurpose, () => setShowPurpose((v) => !v), 'Toggle description', AlignLeft)}
520
+ toggleBtn(showPurpose, () => setShowPurposeState((v) => !v), 'Toggle description', AlignLeft)}
501
521
  </span>
502
522
  );
503
523
 
@@ -560,7 +580,7 @@ export function ComponentDeclaration({
560
580
  'purl',
561
581
  ),
562
582
  );
563
- } else if (!showRepoIdentity && !inlineChrome) {
583
+ } else if (!showRepoIdentity && !inlineChrome && !hideActions) {
564
584
  lines.push(line(headerActions, 'actions'));
565
585
  }
566
586
 
@@ -695,7 +715,7 @@ export function ComponentDeclaration({
695
715
  aria-label={showPurpose ? 'Hide description' : 'Show description'}
696
716
  onClick={(e) => {
697
717
  e.stopPropagation();
698
- setShowPurpose((v) => !v);
718
+ setShowPurposeState((v) => !v);
699
719
  }}
700
720
  {...segHover('desc')}
701
721
  style={{
@@ -888,14 +908,17 @@ export function ComponentDeclaration({
888
908
  );
889
909
 
890
910
  const declLines: ReactNode[][] = [[]];
911
+ const declLineText: string[] = [''];
891
912
  let di = 0;
892
913
  let offset = 0;
893
914
  for (const tok of tokens) {
894
915
  if (tok.kind === 'newline') {
895
916
  declLines.push([]);
917
+ declLineText.push('');
896
918
  di++;
897
919
  continue;
898
920
  }
921
+ declLineText[di] += tok.text;
899
922
  const color = tok.color ?? tokenColor[tok.kind] ?? declarationTextColor;
900
923
  let i = 0;
901
924
  while (i < tok.text.length) {
@@ -956,8 +979,78 @@ export function ComponentDeclaration({
956
979
  }
957
980
  offset += tok.text.length;
958
981
  }
982
+ // The rendered line carrying the construct's own name gets the known line
983
+ // number — a method stub wraps it in `class Host {`, so the number belongs on
984
+ // the member line, not the synthetic first row. Falls back to the first line.
985
+ const constructName =
986
+ component.name || component.symbol?.split('.').pop() || '';
987
+ let labelDi = -1;
988
+ if (constructName) {
989
+ for (let li = 0; li < declLines.length; li++) {
990
+ if (declLines[li].length > 0 && declLineText[li].includes(constructName)) {
991
+ labelDi = li;
992
+ break;
993
+ }
994
+ }
995
+ }
996
+ if (labelDi === -1) {
997
+ labelDi = declLines.findIndex((l) => l.length > 0);
998
+ }
959
999
  for (let li = 0; li < declLines.length; li++) {
960
1000
  if (declLines[li].length === 0) continue;
1001
+ if (lineNumbers && declarationStartLine != null) {
1002
+ // Only the construct's own line is known; the rest of the gutter stays
1003
+ // blank (but aligned) since later rendered lines needn't map to source.
1004
+ const n = li === labelDi ? declarationStartLine : null;
1005
+ const interactive = n != null && !!onOpenFile && !!component.file;
1006
+ const gutterStyle: CSSProperties = {
1007
+ flexShrink: 0,
1008
+ width: GUTTER_NUMBER_WIDTH,
1009
+ paddingRight: GUTTER_NUMBER_GAP,
1010
+ border: 'none',
1011
+ background: 'transparent',
1012
+ textAlign: 'right',
1013
+ color: muted,
1014
+ fontFamily: 'inherit',
1015
+ fontSize: 'inherit',
1016
+ lineHeight: 'inherit',
1017
+ cursor: interactive ? 'pointer' : 'default',
1018
+ userSelect: 'none',
1019
+ };
1020
+ lines.push(
1021
+ <div
1022
+ key={`decl-${li}`}
1023
+ style={{ display: 'flex', alignItems: 'flex-start', minWidth: 0 }}
1024
+ >
1025
+ {n == null ? (
1026
+ <span style={gutterStyle} aria-hidden="true" />
1027
+ ) : interactive ? (
1028
+ <button
1029
+ type="button"
1030
+ title={`Open line ${n}`}
1031
+ onClick={() => onOpenFile!(component.file, { startLine: n })}
1032
+ style={gutterStyle}
1033
+ >
1034
+ {n}
1035
+ </button>
1036
+ ) : (
1037
+ <span style={gutterStyle}>{n}</span>
1038
+ )}
1039
+ <div
1040
+ style={{
1041
+ flex: 1,
1042
+ minWidth: 0,
1043
+ whiteSpace: 'pre-wrap',
1044
+ overflowWrap: 'anywhere',
1045
+ minHeight: 18,
1046
+ }}
1047
+ >
1048
+ {declLines[li]}
1049
+ </div>
1050
+ </div>,
1051
+ );
1052
+ continue;
1053
+ }
961
1054
  lines.push(
962
1055
  <div
963
1056
  key={`decl-${li}`}
@@ -986,7 +1079,7 @@ export function ComponentDeclaration({
986
1079
  {purposeLine.trim()}
987
1080
  </span>,
988
1081
  `purpose${i}`,
989
- false,
1082
+ lineNumbers ? GUTTER_NUMBER_WIDTH + GUTTER_NUMBER_GAP : false,
990
1083
  'normal',
991
1084
  ),
992
1085
  ),
@@ -1015,7 +1108,7 @@ export function ComponentDeclaration({
1015
1108
  minWidth: 0,
1016
1109
  boxSizing: 'border-box',
1017
1110
  background: theme.colors.backgroundSecondary,
1018
- padding: '10px 12px',
1111
+ padding: lineNumbers ? '10px 12px 10px 0' : '10px 12px',
1019
1112
  fontFamily: theme.fonts.monospace,
1020
1113
  fontSize: theme.fontSizes[1],
1021
1114
  lineHeight: 1.7,