@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.
- 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 +13 -1
- package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
- package/dist/subsystem/ComponentDeclaration.js +58 -7
- package/dist/subsystem/ComponentDeclaration.js.map +1 -1
- package/dist/subsystem/ConstructsCatalog.d.ts +6 -3
- package/dist/subsystem/ConstructsCatalog.d.ts.map +1 -1
- package/dist/subsystem/ConstructsCatalog.js +206 -217
- 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 +443 -0
- package/src/subsystem/ComponentDeclaration.tsx +100 -7
- package/src/subsystem/ConstructsCatalog.tsx +291 -323
- 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,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 [
|
|
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, () =>
|
|
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
|
-
|
|
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,
|