@principal-ai/subsystems-react 0.37.0 → 0.37.2
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.
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/subsystem/ComponentDeclaration.d.ts +16 -1
- package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
- package/dist/subsystem/ComponentDeclaration.js +75 -7
- package/dist/subsystem/ComponentDeclaration.js.map +1 -1
- package/dist/subsystem/ConstructsCatalog.d.ts +13 -3
- package/dist/subsystem/ConstructsCatalog.d.ts.map +1 -1
- package/dist/subsystem/ConstructsCatalog.js +273 -220
- package/dist/subsystem/ConstructsCatalog.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +36 -28
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/SymbolInspectionCard.d.ts.map +1 -1
- package/dist/subsystem/SymbolInspectionCard.js +1 -1
- package/dist/subsystem/SymbolInspectionCard.js.map +1 -1
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +7 -1
- package/dist/subsystem/model.js.map +1 -1
- package/dist/utils/edgeLabel.d.ts +51 -0
- package/dist/utils/edgeLabel.d.ts.map +1 -0
- package/dist/utils/edgeLabel.js +81 -0
- package/dist/utils/edgeLabel.js.map +1 -0
- package/dist/utils/elkLayout.d.ts +6 -0
- package/dist/utils/elkLayout.d.ts.map +1 -1
- package/dist/utils/elkLayout.js +27 -6
- package/dist/utils/elkLayout.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +7 -0
- package/src/stories/Subsystem/ConstructsCatalog.stories.tsx +457 -0
- package/src/subsystem/ComponentDeclaration.tsx +130 -7
- package/src/subsystem/ConstructsCatalog.tsx +431 -341
- package/src/subsystem/SubsystemComponentGraph.tsx +74 -29
- package/src/subsystem/SymbolInspectionCard.tsx +10 -9
- package/src/subsystem/model.ts +7 -1
- package/src/utils/edgeLabel.test.ts +23 -0
- package/src/utils/edgeLabel.ts +95 -0
- 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,21 @@ 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
|
+
/** For `external` constructs, render the construct name as the body instead
|
|
186
|
+
* of `external '…'` — the kind is named by the caller's header. */
|
|
187
|
+
externalName?: boolean;
|
|
188
|
+
/** Controlled purpose/description visibility. When provided it overrides the
|
|
189
|
+
* internal toggle, so a host can drive it from a shared header. */
|
|
190
|
+
showPurpose?: boolean;
|
|
191
|
+
/** Render a gutter beside the declaration body and label the construct's own
|
|
192
|
+
* line with its known start line — only that line is accurate, so the rest
|
|
193
|
+
* of the gutter stays blank and aligned. The label opens the file at that
|
|
194
|
+
* line when `onOpenFile` is set. */
|
|
195
|
+
lineNumbers?: boolean;
|
|
177
196
|
}
|
|
178
197
|
|
|
179
198
|
function verificationSummary(
|
|
@@ -318,6 +337,10 @@ export function ComponentDeclaration({
|
|
|
318
337
|
declarationOpen = false,
|
|
319
338
|
showRepoIdentity = true,
|
|
320
339
|
fileBadgeChrome = false,
|
|
340
|
+
hideActions = false,
|
|
341
|
+
externalName = false,
|
|
342
|
+
showPurpose: showPurposeProp,
|
|
343
|
+
lineNumbers = false,
|
|
321
344
|
}: ComponentDeclarationProps) {
|
|
322
345
|
const { theme, mode } = useTheme();
|
|
323
346
|
const pierreSyntaxTheme = resolvePierreSyntaxThemeName(mode);
|
|
@@ -325,7 +348,8 @@ export function ComponentDeclaration({
|
|
|
325
348
|
'path' | 'line' | 'desc' | 'action' | null
|
|
326
349
|
>(null);
|
|
327
350
|
const [showFile, setShowFile] = useState(defaultShowFile);
|
|
328
|
-
const [
|
|
351
|
+
const [showPurposeState, setShowPurposeState] = useState(false);
|
|
352
|
+
const showPurpose = showPurposeProp ?? showPurposeState;
|
|
329
353
|
const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
|
|
330
354
|
const color = componentColor(component, pierreSyntaxTheme);
|
|
331
355
|
const inlineChrome = fileBadgeChrome && showFile && !!component.file;
|
|
@@ -399,6 +423,13 @@ export function ComponentDeclaration({
|
|
|
399
423
|
parseSourceLocation(verification?.anchor?.source_location) ??
|
|
400
424
|
undefined;
|
|
401
425
|
|
|
426
|
+
const externalKind =
|
|
427
|
+
(component.declaration?.kind ?? component.construct) === 'external';
|
|
428
|
+
const externalNameText =
|
|
429
|
+
component.name?.trim() ||
|
|
430
|
+
(component.declaration?.kind === 'external' ? component.declaration.label : '') ||
|
|
431
|
+
component.alias;
|
|
432
|
+
|
|
402
433
|
const openDeclarationFile = () => {
|
|
403
434
|
if (!onOpenFile || !component.file) return;
|
|
404
435
|
onOpenFile(
|
|
@@ -497,7 +528,7 @@ export function ComponentDeclaration({
|
|
|
497
528
|
{!inlineChrome && lineLocationLabel}
|
|
498
529
|
{!inlineChrome &&
|
|
499
530
|
component.purpose?.trim() &&
|
|
500
|
-
toggleBtn(showPurpose, () =>
|
|
531
|
+
toggleBtn(showPurpose, () => setShowPurposeState((v) => !v), 'Toggle description', AlignLeft)}
|
|
501
532
|
</span>
|
|
502
533
|
);
|
|
503
534
|
|
|
@@ -560,7 +591,7 @@ export function ComponentDeclaration({
|
|
|
560
591
|
'purl',
|
|
561
592
|
),
|
|
562
593
|
);
|
|
563
|
-
} else if (!showRepoIdentity && !inlineChrome) {
|
|
594
|
+
} else if (!showRepoIdentity && !inlineChrome && !hideActions) {
|
|
564
595
|
lines.push(line(headerActions, 'actions'));
|
|
565
596
|
}
|
|
566
597
|
|
|
@@ -695,7 +726,7 @@ export function ComponentDeclaration({
|
|
|
695
726
|
aria-label={showPurpose ? 'Hide description' : 'Show description'}
|
|
696
727
|
onClick={(e) => {
|
|
697
728
|
e.stopPropagation();
|
|
698
|
-
|
|
729
|
+
setShowPurposeState((v) => !v);
|
|
699
730
|
}}
|
|
700
731
|
{...segHover('desc')}
|
|
701
732
|
style={{
|
|
@@ -888,14 +919,17 @@ export function ComponentDeclaration({
|
|
|
888
919
|
);
|
|
889
920
|
|
|
890
921
|
const declLines: ReactNode[][] = [[]];
|
|
922
|
+
const declLineText: string[] = [''];
|
|
891
923
|
let di = 0;
|
|
892
924
|
let offset = 0;
|
|
893
925
|
for (const tok of tokens) {
|
|
894
926
|
if (tok.kind === 'newline') {
|
|
895
927
|
declLines.push([]);
|
|
928
|
+
declLineText.push('');
|
|
896
929
|
di++;
|
|
897
930
|
continue;
|
|
898
931
|
}
|
|
932
|
+
declLineText[di] += tok.text;
|
|
899
933
|
const color = tok.color ?? tokenColor[tok.kind] ?? declarationTextColor;
|
|
900
934
|
let i = 0;
|
|
901
935
|
while (i < tok.text.length) {
|
|
@@ -956,8 +990,97 @@ export function ComponentDeclaration({
|
|
|
956
990
|
}
|
|
957
991
|
offset += tok.text.length;
|
|
958
992
|
}
|
|
993
|
+
// The rendered line carrying the construct's own name gets the known line
|
|
994
|
+
// number — a method stub wraps it in `class Host {`, so the number belongs on
|
|
995
|
+
// the member line, not the synthetic first row. Falls back to the first line.
|
|
996
|
+
const constructName =
|
|
997
|
+
component.name || component.symbol?.split('.').pop() || '';
|
|
998
|
+
let labelDi = -1;
|
|
999
|
+
if (constructName) {
|
|
1000
|
+
for (let li = 0; li < declLines.length; li++) {
|
|
1001
|
+
if (declLines[li].length > 0 && declLineText[li].includes(constructName)) {
|
|
1002
|
+
labelDi = li;
|
|
1003
|
+
break;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
if (labelDi === -1) {
|
|
1008
|
+
labelDi = declLines.findIndex((l) => l.length > 0);
|
|
1009
|
+
}
|
|
1010
|
+
const externalNameLine = externalName && externalKind;
|
|
1011
|
+
if (externalNameLine) {
|
|
1012
|
+
// The caller's header names the kind, so the body just names the construct.
|
|
1013
|
+
lines.push(
|
|
1014
|
+
<div
|
|
1015
|
+
key="external-name"
|
|
1016
|
+
style={{
|
|
1017
|
+
display: 'block',
|
|
1018
|
+
whiteSpace: 'pre-wrap',
|
|
1019
|
+
overflowWrap: 'anywhere',
|
|
1020
|
+
minWidth: 0,
|
|
1021
|
+
minHeight: 18,
|
|
1022
|
+
}}
|
|
1023
|
+
>
|
|
1024
|
+
<span style={{ color }}>{externalNameText}</span>
|
|
1025
|
+
</div>,
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
959
1028
|
for (let li = 0; li < declLines.length; li++) {
|
|
1029
|
+
if (externalNameLine) break;
|
|
960
1030
|
if (declLines[li].length === 0) continue;
|
|
1031
|
+
if (lineNumbers && declarationStartLine != null) {
|
|
1032
|
+
// Only the construct's own line is known; the rest of the gutter stays
|
|
1033
|
+
// blank (but aligned) since later rendered lines needn't map to source.
|
|
1034
|
+
const n = li === labelDi ? declarationStartLine : null;
|
|
1035
|
+
const interactive = n != null && !!onOpenFile && !!component.file;
|
|
1036
|
+
const gutterStyle: CSSProperties = {
|
|
1037
|
+
flexShrink: 0,
|
|
1038
|
+
width: GUTTER_NUMBER_WIDTH,
|
|
1039
|
+
paddingRight: GUTTER_NUMBER_GAP,
|
|
1040
|
+
border: 'none',
|
|
1041
|
+
background: 'transparent',
|
|
1042
|
+
textAlign: 'right',
|
|
1043
|
+
color: muted,
|
|
1044
|
+
fontFamily: 'inherit',
|
|
1045
|
+
fontSize: 'inherit',
|
|
1046
|
+
lineHeight: 'inherit',
|
|
1047
|
+
cursor: interactive ? 'pointer' : 'default',
|
|
1048
|
+
userSelect: 'none',
|
|
1049
|
+
};
|
|
1050
|
+
lines.push(
|
|
1051
|
+
<div
|
|
1052
|
+
key={`decl-${li}`}
|
|
1053
|
+
style={{ display: 'flex', alignItems: 'flex-start', minWidth: 0 }}
|
|
1054
|
+
>
|
|
1055
|
+
{n == null ? (
|
|
1056
|
+
<span style={gutterStyle} aria-hidden="true" />
|
|
1057
|
+
) : interactive ? (
|
|
1058
|
+
<button
|
|
1059
|
+
type="button"
|
|
1060
|
+
title={`Open line ${n}`}
|
|
1061
|
+
onClick={() => onOpenFile!(component.file, { startLine: n })}
|
|
1062
|
+
style={gutterStyle}
|
|
1063
|
+
>
|
|
1064
|
+
{n}
|
|
1065
|
+
</button>
|
|
1066
|
+
) : (
|
|
1067
|
+
<span style={gutterStyle}>{n}</span>
|
|
1068
|
+
)}
|
|
1069
|
+
<div
|
|
1070
|
+
style={{
|
|
1071
|
+
flex: 1,
|
|
1072
|
+
minWidth: 0,
|
|
1073
|
+
whiteSpace: 'pre-wrap',
|
|
1074
|
+
overflowWrap: 'anywhere',
|
|
1075
|
+
minHeight: 18,
|
|
1076
|
+
}}
|
|
1077
|
+
>
|
|
1078
|
+
{declLines[li]}
|
|
1079
|
+
</div>
|
|
1080
|
+
</div>,
|
|
1081
|
+
);
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
961
1084
|
lines.push(
|
|
962
1085
|
<div
|
|
963
1086
|
key={`decl-${li}`}
|
|
@@ -986,7 +1109,7 @@ export function ComponentDeclaration({
|
|
|
986
1109
|
{purposeLine.trim()}
|
|
987
1110
|
</span>,
|
|
988
1111
|
`purpose${i}`,
|
|
989
|
-
false,
|
|
1112
|
+
lineNumbers ? GUTTER_NUMBER_WIDTH + GUTTER_NUMBER_GAP : false,
|
|
990
1113
|
'normal',
|
|
991
1114
|
),
|
|
992
1115
|
),
|
|
@@ -1015,7 +1138,7 @@ export function ComponentDeclaration({
|
|
|
1015
1138
|
minWidth: 0,
|
|
1016
1139
|
boxSizing: 'border-box',
|
|
1017
1140
|
background: theme.colors.backgroundSecondary,
|
|
1018
|
-
padding: '10px 12px',
|
|
1141
|
+
padding: lineNumbers ? '10px 12px 10px 0' : '10px 12px',
|
|
1019
1142
|
fontFamily: theme.fonts.monospace,
|
|
1020
1143
|
fontSize: theme.fontSizes[1],
|
|
1021
1144
|
lineHeight: 1.7,
|