@principal-ai/subsystems-react 0.24.4 → 0.24.5
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/components/session-events/SessionEventFeed.d.ts +2 -2
- package/dist/components/session-events/SessionEventFeed.js +9 -9
- package/dist/components/session-events/SessionEventFeed.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pierre/scrollAnchor.d.ts.map +1 -1
- package/dist/pierre/scrollAnchor.js +9 -1
- package/dist/pierre/scrollAnchor.js.map +1 -1
- package/dist/subsystem/ComponentDeclaration.d.ts +5 -2
- package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
- package/dist/subsystem/ComponentDeclaration.js +18 -3
- package/dist/subsystem/ComponentDeclaration.js.map +1 -1
- package/dist/subsystem/ConstructsCatalog.d.ts.map +1 -1
- package/dist/subsystem/ConstructsCatalog.js +32 -11
- package/dist/subsystem/ConstructsCatalog.js.map +1 -1
- package/dist/subsystem/IssueList.d.ts +4 -2
- package/dist/subsystem/IssueList.d.ts.map +1 -1
- package/dist/subsystem/IssueList.js +103 -27
- package/dist/subsystem/IssueList.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +3 -1
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/declarationRef.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/session-events/SessionEventFeed.tsx +9 -9
- package/src/index.ts +1 -0
- package/src/pierre/scrollAnchor.ts +7 -1
- package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +21 -14
- package/src/subsystem/ComponentDeclaration.tsx +30 -3
- package/src/subsystem/ConstructsCatalog.tsx +52 -28
- package/src/subsystem/IssueList.tsx +115 -27
- package/src/subsystem/SubsystemComponentGraph.tsx +5 -0
- package/src/subsystem/declarationRef.ts +1 -1
|
@@ -88,6 +88,7 @@ export function ConstructsCatalog({
|
|
|
88
88
|
const { theme, mode } = useTheme();
|
|
89
89
|
const pierreTheme = resolvePierreSyntaxThemeName(mode);
|
|
90
90
|
const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
|
|
91
|
+
const accentColor = theme.colors.accent ?? theme.colors.info;
|
|
91
92
|
const [visibleIds, setVisibleIds] = useState<string[]>(() =>
|
|
92
93
|
components[0] ? [components[0].id] : [],
|
|
93
94
|
);
|
|
@@ -121,6 +122,20 @@ export function ConstructsCatalog({
|
|
|
121
122
|
?.scrollIntoView({ block: 'nearest' });
|
|
122
123
|
}, [visibleIds]);
|
|
123
124
|
|
|
125
|
+
// Keep the declaration whose file is open in view, so the source and its
|
|
126
|
+
// signature stay legible together.
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (!drawer) return;
|
|
129
|
+
const match =
|
|
130
|
+
components.find(
|
|
131
|
+
(c) => c.file === drawer.file && c.declarationRef?.startLine === drawer.startLine,
|
|
132
|
+
) ?? components.find((c) => c.file === drawer.file);
|
|
133
|
+
if (!match) return;
|
|
134
|
+
document
|
|
135
|
+
.getElementById(`construct-signature-${match.id}`)
|
|
136
|
+
?.scrollIntoView({ block: 'nearest' });
|
|
137
|
+
}, [components, drawer]);
|
|
138
|
+
|
|
124
139
|
// Drop ids that left the model; if nothing remains, show the first construct.
|
|
125
140
|
useEffect(() => {
|
|
126
141
|
const ids = new Set(components.map((c) => c.id));
|
|
@@ -423,6 +438,7 @@ export function ConstructsCatalog({
|
|
|
423
438
|
);
|
|
424
439
|
const role = rightBadgeLabel(c);
|
|
425
440
|
const roleColor = rightBadgeColor(c);
|
|
441
|
+
const rowOpen = drawer != null && !!c.file && c.file === drawer.file;
|
|
426
442
|
return (
|
|
427
443
|
<li key={c.id} role="presentation">
|
|
428
444
|
<button
|
|
@@ -447,9 +463,12 @@ export function ConstructsCatalog({
|
|
|
447
463
|
? theme.colors.background
|
|
448
464
|
: 'transparent',
|
|
449
465
|
cursor: 'pointer',
|
|
450
|
-
boxShadow:
|
|
451
|
-
? `inset 0 0 0 1px ${theme.colors.border}`
|
|
452
|
-
:
|
|
466
|
+
boxShadow: [
|
|
467
|
+
visible ? `inset 0 0 0 1px ${theme.colors.border}` : null,
|
|
468
|
+
rowOpen ? `inset 2px 0 0 ${accentColor}` : null,
|
|
469
|
+
]
|
|
470
|
+
.filter(Boolean)
|
|
471
|
+
.join(', ') || undefined,
|
|
453
472
|
outline: focused && !visible ? `1px dotted ${muted}` : undefined,
|
|
454
473
|
outlineOffset: -1,
|
|
455
474
|
opacity: visible ? 1 : 0.72,
|
|
@@ -547,31 +566,36 @@ export function ConstructsCatalog({
|
|
|
547
566
|
}}
|
|
548
567
|
>
|
|
549
568
|
{visibleComponents.length > 0 ? (
|
|
550
|
-
visibleComponents.map((c) =>
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
569
|
+
visibleComponents.map((c) => {
|
|
570
|
+
const open = drawer != null && !!c.file && c.file === drawer.file;
|
|
571
|
+
return (
|
|
572
|
+
<div
|
|
573
|
+
key={c.id}
|
|
574
|
+
id={`construct-signature-${c.id}`}
|
|
575
|
+
style={{
|
|
576
|
+
border: `1px solid ${open ? accentColor : theme.colors.border}`,
|
|
577
|
+
boxShadow: open ? `0 0 0 1px ${accentColor}` : undefined,
|
|
578
|
+
borderRadius: 8,
|
|
579
|
+
overflow: 'hidden',
|
|
580
|
+
flexShrink: 0,
|
|
581
|
+
}}
|
|
582
|
+
>
|
|
583
|
+
<ComponentDeclaration
|
|
584
|
+
component={c}
|
|
585
|
+
onOpenFile={fileViewer ? onOpenFile : undefined}
|
|
586
|
+
defaultShowFile={!!fileViewer}
|
|
587
|
+
onRelatedSelect={onRelatedSelect}
|
|
588
|
+
onVerify={onVerifyComponent}
|
|
589
|
+
fileOpen={open}
|
|
590
|
+
verification={
|
|
591
|
+
componentVerification && focusedId === c.id
|
|
592
|
+
? componentVerification
|
|
593
|
+
: undefined
|
|
594
|
+
}
|
|
595
|
+
/>
|
|
596
|
+
</div>
|
|
597
|
+
);
|
|
598
|
+
})
|
|
575
599
|
) : (
|
|
576
600
|
<p
|
|
577
601
|
style={{
|
|
@@ -27,6 +27,7 @@ export type SubsystemIssueTargetKind =
|
|
|
27
27
|
| 'relation'
|
|
28
28
|
| 'module'
|
|
29
29
|
| 'walkthrough'
|
|
30
|
+
| 'repo'
|
|
30
31
|
| 'graph';
|
|
31
32
|
|
|
32
33
|
/**
|
|
@@ -34,6 +35,7 @@ export type SubsystemIssueTargetKind =
|
|
|
34
35
|
* constructs → static topology → runtime topology → walkthrough.
|
|
35
36
|
*/
|
|
36
37
|
export type SubsystemIssueCategory =
|
|
38
|
+
| 'repo'
|
|
37
39
|
| 'construct'
|
|
38
40
|
| 'static-topology'
|
|
39
41
|
| 'runtime-topology'
|
|
@@ -41,6 +43,7 @@ export type SubsystemIssueCategory =
|
|
|
41
43
|
|
|
42
44
|
/** Layer order + display names, shared by the list and its headers. */
|
|
43
45
|
export const SUBSYSTEM_ISSUE_CATEGORIES: SubsystemIssueCategory[] = [
|
|
46
|
+
'repo',
|
|
44
47
|
'construct',
|
|
45
48
|
'static-topology',
|
|
46
49
|
'runtime-topology',
|
|
@@ -51,6 +54,7 @@ export const SUBSYSTEM_ISSUE_CATEGORY_LABEL: Record<
|
|
|
51
54
|
SubsystemIssueCategory,
|
|
52
55
|
string
|
|
53
56
|
> = {
|
|
57
|
+
repo: 'Repositories',
|
|
54
58
|
construct: 'Constructs',
|
|
55
59
|
'static-topology': 'Static topology',
|
|
56
60
|
'runtime-topology': 'Runtime topology',
|
|
@@ -60,17 +64,18 @@ export const SUBSYSTEM_ISSUE_CATEGORY_LABEL: Record<
|
|
|
60
64
|
/** Default `kind` → layer. `boundary_*` splits by module (static) vs process (runtime). */
|
|
61
65
|
const KIND_CATEGORY: Record<string, SubsystemIssueCategory> = {
|
|
62
66
|
missing_file: 'construct',
|
|
63
|
-
|
|
67
|
+
symbol_ambiguous: 'construct',
|
|
68
|
+
symbol_unmatched: 'construct',
|
|
64
69
|
stale_declaration: 'construct',
|
|
65
70
|
construct_mismatch: 'construct',
|
|
66
71
|
construct_unconfirmed: 'construct',
|
|
67
72
|
signature_mismatch: 'construct',
|
|
68
73
|
signature_unconfirmed: 'construct',
|
|
69
|
-
|
|
74
|
+
repo_unresolved: 'repo',
|
|
75
|
+
graphify_unavailable: 'repo',
|
|
70
76
|
topology_broken_endpoint: 'static-topology',
|
|
71
77
|
topology_import_unconfirmed: 'static-topology',
|
|
72
78
|
topology_relation_unconfirmed: 'static-topology',
|
|
73
|
-
boundary_module_without_file: 'static-topology',
|
|
74
79
|
boundary_module_file_mismatch: 'static-topology',
|
|
75
80
|
boundary_process_nest_disagree: 'runtime-topology',
|
|
76
81
|
walkthrough: 'walkthrough',
|
|
@@ -123,24 +128,26 @@ export function humanizeIssueKind(kind: string): string {
|
|
|
123
128
|
}
|
|
124
129
|
|
|
125
130
|
/**
|
|
126
|
-
* Curated
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
131
|
+
* Curated labels. Construct-layer findings stay fully qualified ("Construct
|
|
132
|
+
* declaration file missing", "Construct type mismatch") rather than relying on
|
|
133
|
+
* the category header — this UI is new and the extra words read clearer.
|
|
134
|
+
* Topology findings drop the `topology_`/`boundary_` prefix since the layer
|
|
135
|
+
* already scopes them. Explicit `issue.kindLabel` wins.
|
|
130
136
|
*/
|
|
131
137
|
const KIND_LABEL: Record<string, string> = {
|
|
132
|
-
missing_file: '
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
missing_file: 'Construct declaration file missing',
|
|
139
|
+
symbol_ambiguous: 'Multiple construct symbol matches',
|
|
140
|
+
symbol_unmatched: 'Construct symbol unconfirmed',
|
|
141
|
+
stale_declaration: 'Construct declaration drift',
|
|
142
|
+
construct_mismatch: 'Construct type mismatch',
|
|
143
|
+
construct_unconfirmed: 'Construct type unconfirmed',
|
|
144
|
+
signature_mismatch: 'Construct signature mismatch',
|
|
145
|
+
signature_unconfirmed: 'Construct signature unconfirmed',
|
|
146
|
+
repo_unresolved: 'No repository clone',
|
|
147
|
+
graphify_unavailable: 'Graphify cache unavailable',
|
|
148
|
+
topology_broken_endpoint: 'Construct relationship broken',
|
|
141
149
|
topology_import_unconfirmed: 'Import unconfirmed',
|
|
142
|
-
topology_relation_unconfirmed: '
|
|
143
|
-
boundary_module_without_file: 'Declaration file undefined',
|
|
150
|
+
topology_relation_unconfirmed: 'Relationship unconfirmed',
|
|
144
151
|
boundary_module_file_mismatch: 'Declaration file outside module',
|
|
145
152
|
boundary_process_nest_disagree: 'Module spans multiple process contexts',
|
|
146
153
|
walkthrough: 'Step issue',
|
|
@@ -151,6 +158,44 @@ export function issueKindLabel(issue: SubsystemIssue): string {
|
|
|
151
158
|
return issue.kindLabel ?? KIND_LABEL[issue.kind] ?? humanizeIssueKind(issue.kind);
|
|
152
159
|
}
|
|
153
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Display order within the list, by verification pipeline — not severity.
|
|
163
|
+
* Mirrors the per-component check order (file → symbol → declaration → type →
|
|
164
|
+
* signature), then topology (integrity → corroboration → membership) and the
|
|
165
|
+
* remaining layers. Within a stage, "unconfirmed" precedes "mismatch": you
|
|
166
|
+
* reach a verdict before you can find it wrong.
|
|
167
|
+
*/
|
|
168
|
+
const KIND_ORDER: Record<string, number> = {
|
|
169
|
+
// Repositories — availability precondition
|
|
170
|
+
repo_unresolved: 0,
|
|
171
|
+
graphify_unavailable: 1,
|
|
172
|
+
// Constructs — file → symbol → declaration → type → signature
|
|
173
|
+
missing_file: 10,
|
|
174
|
+
symbol_unmatched: 11,
|
|
175
|
+
symbol_ambiguous: 12,
|
|
176
|
+
stale_declaration: 13,
|
|
177
|
+
construct_unconfirmed: 14,
|
|
178
|
+
construct_mismatch: 15,
|
|
179
|
+
signature_unconfirmed: 16,
|
|
180
|
+
signature_mismatch: 17,
|
|
181
|
+
// Static topology — edge integrity → corroboration → membership
|
|
182
|
+
topology_broken_endpoint: 20,
|
|
183
|
+
topology_relation_unconfirmed: 21,
|
|
184
|
+
topology_import_unconfirmed: 22,
|
|
185
|
+
boundary_module_file_mismatch: 23,
|
|
186
|
+
// Runtime topology
|
|
187
|
+
boundary_process_nest_disagree: 30,
|
|
188
|
+
// Walkthrough
|
|
189
|
+
walkthrough: 40,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const KIND_ORDER_FALLBACK = 100;
|
|
193
|
+
|
|
194
|
+
/** Sort key for a kind; unknown kinds sort last. */
|
|
195
|
+
export function issueKindOrder(kind: string): number {
|
|
196
|
+
return KIND_ORDER[kind] ?? KIND_ORDER_FALLBACK;
|
|
197
|
+
}
|
|
198
|
+
|
|
154
199
|
export function severityRank(severity: SubsystemIssueSeverity): number {
|
|
155
200
|
return SEVERITY_RANK[severity] ?? SEVERITY_RANK.info;
|
|
156
201
|
}
|
|
@@ -244,7 +289,12 @@ export function groupIssuesByCategory(
|
|
|
244
289
|
}
|
|
245
290
|
return SUBSYSTEM_ISSUE_CATEGORIES.map((category) => {
|
|
246
291
|
const grouped = byCategory.get(category) ?? [];
|
|
247
|
-
|
|
292
|
+
// Logical pipeline order, then by target label (stable for the rest).
|
|
293
|
+
grouped.sort((a, b) => {
|
|
294
|
+
const byKind = issueKindOrder(a.kind) - issueKindOrder(b.kind);
|
|
295
|
+
if (byKind !== 0) return byKind;
|
|
296
|
+
return (a.target?.label ?? '').localeCompare(b.target?.label ?? '');
|
|
297
|
+
});
|
|
248
298
|
return {
|
|
249
299
|
category,
|
|
250
300
|
label: SUBSYSTEM_ISSUE_CATEGORY_LABEL[category],
|
|
@@ -267,9 +317,10 @@ function severityColor(
|
|
|
267
317
|
|
|
268
318
|
const TARGET_KIND_LABEL: Record<SubsystemIssueTargetKind, string> = {
|
|
269
319
|
component: 'component',
|
|
270
|
-
relation: '
|
|
320
|
+
relation: 'relationship',
|
|
271
321
|
module: 'module',
|
|
272
322
|
walkthrough: 'flow',
|
|
323
|
+
repo: 'repo',
|
|
273
324
|
graph: 'model',
|
|
274
325
|
};
|
|
275
326
|
|
|
@@ -443,6 +494,19 @@ export function SubsystemIssueList({
|
|
|
443
494
|
const { theme } = useTheme();
|
|
444
495
|
const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
|
|
445
496
|
const categories = groupIssuesByCategory(issues);
|
|
497
|
+
// Collapsed categories (by id). Everything starts expanded.
|
|
498
|
+
const [collapsed, setCollapsed] = useState<Set<SubsystemIssueCategory>>(
|
|
499
|
+
() => new Set(),
|
|
500
|
+
);
|
|
501
|
+
const [hovered, setHovered] = useState<SubsystemIssueCategory | null>(null);
|
|
502
|
+
const toggleCollapsed = (category: SubsystemIssueCategory) => {
|
|
503
|
+
setCollapsed((prev) => {
|
|
504
|
+
const next = new Set(prev);
|
|
505
|
+
if (next.has(category)) next.delete(category);
|
|
506
|
+
else next.add(category);
|
|
507
|
+
return next;
|
|
508
|
+
});
|
|
509
|
+
};
|
|
446
510
|
|
|
447
511
|
return (
|
|
448
512
|
<div style={{ flex: 1, minHeight: 0, overflowY: 'auto' }}>
|
|
@@ -453,19 +517,39 @@ export function SubsystemIssueList({
|
|
|
453
517
|
const accent = category.severity
|
|
454
518
|
? severityColor(category.severity, theme.colors, muted)
|
|
455
519
|
: theme.colors.success;
|
|
520
|
+
const isCollapsed = collapsed.has(category.category);
|
|
521
|
+
const isHovered = hovered === category.category;
|
|
456
522
|
return (
|
|
457
523
|
<div key={category.category}>
|
|
458
|
-
<
|
|
524
|
+
<button
|
|
525
|
+
type="button"
|
|
526
|
+
aria-expanded={!isCollapsed}
|
|
527
|
+
onClick={() => toggleCollapsed(category.category)}
|
|
528
|
+
onMouseEnter={() => setHovered(category.category)}
|
|
529
|
+
onMouseLeave={() => setHovered(null)}
|
|
459
530
|
style={{
|
|
460
531
|
position: 'sticky',
|
|
461
532
|
top: 0,
|
|
462
533
|
zIndex: 1,
|
|
534
|
+
width: '100%',
|
|
535
|
+
boxSizing: 'border-box',
|
|
463
536
|
display: 'flex',
|
|
464
537
|
alignItems: 'center',
|
|
465
|
-
gap:
|
|
466
|
-
padding: '
|
|
467
|
-
background:
|
|
538
|
+
gap: 8,
|
|
539
|
+
padding: '9px 12px',
|
|
540
|
+
background: isHovered
|
|
541
|
+
? (theme.colors.backgroundHover ??
|
|
542
|
+
theme.colors.backgroundSecondary ??
|
|
543
|
+
theme.colors.background)
|
|
544
|
+
: (theme.colors.backgroundTertiary ??
|
|
545
|
+
theme.colors.backgroundSecondary ??
|
|
546
|
+
theme.colors.background),
|
|
547
|
+
border: 'none',
|
|
548
|
+
borderTop: `1px solid ${theme.colors.border}`,
|
|
468
549
|
borderBottom: `1px solid ${theme.colors.border}`,
|
|
550
|
+
cursor: 'pointer',
|
|
551
|
+
textAlign: 'left',
|
|
552
|
+
fontFamily: theme.fonts.body,
|
|
469
553
|
}}
|
|
470
554
|
>
|
|
471
555
|
<span
|
|
@@ -473,9 +557,9 @@ export function SubsystemIssueList({
|
|
|
473
557
|
flex: 1,
|
|
474
558
|
minWidth: 0,
|
|
475
559
|
fontSize: theme.fontSizes[1],
|
|
476
|
-
fontWeight:
|
|
560
|
+
fontWeight: 700,
|
|
561
|
+
letterSpacing: 0.2,
|
|
477
562
|
color: clean ? muted : theme.colors.text,
|
|
478
|
-
fontFamily: theme.fonts.body,
|
|
479
563
|
whiteSpace: 'nowrap',
|
|
480
564
|
overflow: 'hidden',
|
|
481
565
|
textOverflow: 'ellipsis',
|
|
@@ -498,8 +582,12 @@ export function SubsystemIssueList({
|
|
|
498
582
|
{category.count}
|
|
499
583
|
</span>
|
|
500
584
|
)}
|
|
501
|
-
|
|
585
|
+
<span style={{ display: 'inline-flex', color: muted, flexShrink: 0 }}>
|
|
586
|
+
{isCollapsed ? <ChevronRight size={14} /> : <ChevronDown size={14} />}
|
|
587
|
+
</span>
|
|
588
|
+
</button>
|
|
502
589
|
{!clean &&
|
|
590
|
+
!isCollapsed &&
|
|
503
591
|
category.issues.map((issue) => (
|
|
504
592
|
<SubsystemIssueCard
|
|
505
593
|
key={issue.id}
|
|
@@ -1993,6 +1993,11 @@ function Inner({ components, relations, walkthroughs, onSelect, onEdgeSelect, me
|
|
|
1993
1993
|
onRelatedSelect={resolveRelatedComponent}
|
|
1994
1994
|
onVerify={onVerifyComponent}
|
|
1995
1995
|
verification={componentVerification}
|
|
1996
|
+
fileOpen={
|
|
1997
|
+
drawerTarget?.kind === 'file' &&
|
|
1998
|
+
!!selected.file &&
|
|
1999
|
+
selected.file === drawerTarget.file
|
|
2000
|
+
}
|
|
1996
2001
|
/>
|
|
1997
2002
|
</div>
|
|
1998
2003
|
)}
|