@principal-ai/subsystems-react 0.55.3 → 0.56.0

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 (107) hide show
  1. package/dist/index.d.ts +12 -2
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +6 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/pierre/PierreFileView.js +3 -3
  6. package/dist/pierre/PierreFileView.js.map +1 -1
  7. package/dist/pierre/PierreTrailCodeView.d.ts +1 -6
  8. package/dist/pierre/PierreTrailCodeView.d.ts.map +1 -1
  9. package/dist/pierre/PierreTrailCodeView.js +21 -18
  10. package/dist/pierre/PierreTrailCodeView.js.map +1 -1
  11. package/dist/pierre/fileAvailability.d.ts +4 -7
  12. package/dist/pierre/fileAvailability.d.ts.map +1 -1
  13. package/dist/pierre/fileAvailability.js +5 -10
  14. package/dist/pierre/fileAvailability.js.map +1 -1
  15. package/dist/subsystem/C4Graph.d.ts.map +1 -1
  16. package/dist/subsystem/C4Graph.js +1 -1
  17. package/dist/subsystem/C4Graph.js.map +1 -1
  18. package/dist/subsystem/DecorationPanel.d.ts +37 -0
  19. package/dist/subsystem/DecorationPanel.d.ts.map +1 -0
  20. package/dist/subsystem/DecorationPanel.js +147 -0
  21. package/dist/subsystem/DecorationPanel.js.map +1 -0
  22. package/dist/subsystem/FileDrawer.d.ts +3 -1
  23. package/dist/subsystem/FileDrawer.d.ts.map +1 -1
  24. package/dist/subsystem/FileDrawer.js +2 -2
  25. package/dist/subsystem/FileDrawer.js.map +1 -1
  26. package/dist/subsystem/IssueList.d.ts.map +1 -1
  27. package/dist/subsystem/IssueList.js +26 -0
  28. package/dist/subsystem/IssueList.js.map +1 -1
  29. package/dist/subsystem/SubsystemComponentGraph.d.ts +9 -5
  30. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  31. package/dist/subsystem/SubsystemComponentGraph.js +37 -47
  32. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  33. package/dist/subsystem/TrailStackBreadcrumb.d.ts +21 -0
  34. package/dist/subsystem/TrailStackBreadcrumb.d.ts.map +1 -0
  35. package/dist/subsystem/TrailStackBreadcrumb.js +77 -0
  36. package/dist/subsystem/TrailStackBreadcrumb.js.map +1 -0
  37. package/dist/subsystem/TrailStepsNav.d.ts +4 -3
  38. package/dist/subsystem/TrailStepsNav.d.ts.map +1 -1
  39. package/dist/subsystem/TrailStepsNav.js +13 -16
  40. package/dist/subsystem/TrailStepsNav.js.map +1 -1
  41. package/dist/subsystem/TrailsPanel.d.ts +9 -7
  42. package/dist/subsystem/TrailsPanel.d.ts.map +1 -1
  43. package/dist/subsystem/TrailsPanel.js +126 -76
  44. package/dist/subsystem/TrailsPanel.js.map +1 -1
  45. package/dist/subsystem/decoration.d.ts +112 -0
  46. package/dist/subsystem/decoration.d.ts.map +1 -0
  47. package/dist/subsystem/decoration.js +54 -0
  48. package/dist/subsystem/decoration.js.map +1 -0
  49. package/dist/subsystem/model.d.ts +5 -18
  50. package/dist/subsystem/model.d.ts.map +1 -1
  51. package/dist/subsystem/model.js +3 -11
  52. package/dist/subsystem/model.js.map +1 -1
  53. package/dist/subsystem/nodes.d.ts.map +1 -1
  54. package/dist/subsystem/nodes.js +17 -24
  55. package/dist/subsystem/nodes.js.map +1 -1
  56. package/dist/subsystem/stepEmphasis.d.ts +78 -0
  57. package/dist/subsystem/stepEmphasis.d.ts.map +1 -0
  58. package/dist/subsystem/stepEmphasis.js +89 -0
  59. package/dist/subsystem/stepEmphasis.js.map +1 -0
  60. package/dist/subsystem/trailBrief.d.ts.map +1 -1
  61. package/dist/subsystem/trailBrief.js +20 -0
  62. package/dist/subsystem/trailBrief.js.map +1 -1
  63. package/dist/subsystem/trailStack.d.ts +53 -0
  64. package/dist/subsystem/trailStack.d.ts.map +1 -0
  65. package/dist/subsystem/trailStack.js +154 -0
  66. package/dist/subsystem/trailStack.js.map +1 -0
  67. package/dist/subsystem/trailStepDisplay.d.ts +38 -0
  68. package/dist/subsystem/trailStepDisplay.d.ts.map +1 -0
  69. package/dist/subsystem/trailStepDisplay.js +41 -0
  70. package/dist/subsystem/trailStepDisplay.js.map +1 -0
  71. package/package.json +3 -3
  72. package/src/index.ts +47 -1
  73. package/src/pierre/PierreFileView.tsx +3 -3
  74. package/src/pierre/PierreTrailCodeView.tsx +25 -28
  75. package/src/pierre/fileAvailability.test.ts +0 -6
  76. package/src/pierre/fileAvailability.ts +5 -13
  77. package/src/stories/Canvases/Subsystem/Deprecated.stories.tsx +3 -17
  78. package/src/stories/Canvases/Subsystem/NodeAnatomy.stories.tsx +3 -31
  79. package/src/stories/Canvases/Subsystem/StepEmphasis.stories.tsx +528 -0
  80. package/src/stories/Canvases/Subsystem/Trails.stories.tsx +0 -187
  81. package/src/stories/Components/DecorationPanel.stories.tsx +160 -0
  82. package/src/subsystem/C4Graph.tsx +0 -1
  83. package/src/subsystem/DecorationPanel.test.tsx +128 -0
  84. package/src/subsystem/DecorationPanel.tsx +334 -0
  85. package/src/subsystem/FileDrawer.tsx +6 -0
  86. package/src/subsystem/IssueList.tsx +26 -0
  87. package/src/subsystem/SubsystemComponentGraph.tsx +68 -65
  88. package/src/subsystem/TrailStackBreadcrumb.test.tsx +109 -0
  89. package/src/subsystem/TrailStackBreadcrumb.tsx +172 -0
  90. package/src/subsystem/TrailStepsNav.test.tsx +35 -0
  91. package/src/subsystem/TrailStepsNav.tsx +18 -19
  92. package/src/subsystem/TrailsPanel.test.tsx +149 -0
  93. package/src/subsystem/TrailsPanel.tsx +165 -39
  94. package/src/subsystem/decoration.test.ts +72 -0
  95. package/src/subsystem/decoration.ts +151 -0
  96. package/src/subsystem/model.test.ts +7 -22
  97. package/src/subsystem/model.ts +6 -20
  98. package/src/subsystem/nodes.tsx +19 -28
  99. package/src/subsystem/stepEmphasis.test.ts +110 -0
  100. package/src/subsystem/stepEmphasis.ts +151 -0
  101. package/src/subsystem/trailBrief.test.ts +29 -0
  102. package/src/subsystem/trailBrief.ts +20 -0
  103. package/src/subsystem/trailStack.test.ts +133 -0
  104. package/src/subsystem/trailStack.ts +229 -0
  105. package/src/subsystem/trailStepDisplay.ts +68 -0
  106. package/src/stories/Canvases/Subsystem/Proposed.stories.tsx +0 -256
  107. package/src/stories/Canvases/Subsystem/ProposedTrails.stories.tsx +0 -184
@@ -0,0 +1,334 @@
1
+ /**
2
+ * DecorationPanel — the reading surface for one `SubsystemDecoration`.
3
+ *
4
+ * A decoration is an additive, unverified layer over one or more subsystem
5
+ * models: markdown prose, an ordered list of trails (each naming its own
6
+ * model), and optional draft structure. This panel is the container's own
7
+ * surface — it deliberately does not render the graph. Selecting a trail (or
8
+ * a draft component) is handed back to the host, which decides whether to
9
+ * focus a model, open a tab, or overlay the draft.
10
+ *
11
+ * Nothing here writes: the first prototype is read-only plus an on/off toggle.
12
+ */
13
+
14
+ import type { CSSProperties } from 'react';
15
+ import { useTheme } from '@principal-ade/industry-theme';
16
+ import { Box, Eye, EyeOff, Network, Route, Sparkles } from 'lucide-react';
17
+ import { IndustryMarkdownSlide } from 'themed-markdown';
18
+ import {
19
+ decorationTrailId,
20
+ groupDecorationTrailsByModel,
21
+ type DecorationDraftComponent,
22
+ type DecorationTrailRef,
23
+ type SubsystemDecoration,
24
+ } from './decoration';
25
+
26
+ export interface DecorationPanelTrailMeta {
27
+ title: string;
28
+ stepCount?: number;
29
+ }
30
+
31
+ export interface DecorationPanelProps {
32
+ decoration: SubsystemDecoration;
33
+ /** Whether the decoration overlay is currently on. */
34
+ active: boolean;
35
+ onToggleActive: (active: boolean) => void;
36
+ /** Click a trail row. The host focuses the owning model/trail. */
37
+ onSelectTrail?: (ref: DecorationTrailRef) => void;
38
+ /** Click a draft component. Omit to render the draft list read-only. */
39
+ onSelectDraftComponent?: (component: DecorationDraftComponent) => void;
40
+ /**
41
+ * Resolve a base trail's display title/step count from its owning model.
42
+ * Draft trails carry their content inline and need no resolver.
43
+ */
44
+ resolveTrail?: (ref: DecorationTrailRef) => DecorationPanelTrailMeta | undefined;
45
+ /** Friendly model names, keyed by `modelId`. Falls back to the id. */
46
+ modelLabels?: ReadonlyMap<string, string>;
47
+ }
48
+
49
+ export function DecorationPanel({
50
+ decoration,
51
+ active,
52
+ onToggleActive,
53
+ onSelectTrail,
54
+ onSelectDraftComponent,
55
+ resolveTrail,
56
+ modelLabels,
57
+ }: DecorationPanelProps) {
58
+ const { theme } = useTheme();
59
+ const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
60
+ const secondary = theme.colors.textSecondary ?? muted;
61
+ const warning = theme.colors.warning ?? secondary;
62
+ const accent = theme.colors.accent ?? theme.colors.primary ?? theme.colors.text;
63
+ const groups = groupDecorationTrailsByModel(decoration.trails);
64
+ const draftComponents = decoration.draft?.components ?? [];
65
+ const draftEdges = decoration.draft?.edges ?? [];
66
+ const sectionLabel: CSSProperties = {
67
+ fontFamily: theme.fonts.monospace,
68
+ fontSize: theme.fontSizes[0],
69
+ fontWeight: theme.fontWeights.semibold,
70
+ letterSpacing: '0.06em',
71
+ textTransform: 'uppercase',
72
+ };
73
+
74
+ return (
75
+ <div
76
+ style={{
77
+ display: 'flex',
78
+ flexDirection: 'column',
79
+ width: '100%',
80
+ height: '100%',
81
+ minHeight: 0,
82
+ background: theme.colors.background,
83
+ color: theme.colors.text,
84
+ }}
85
+ >
86
+ <header
87
+ style={{
88
+ flexShrink: 0,
89
+ padding: '14px 16px 10px',
90
+ borderBottom: `1px solid ${theme.colors.border}`,
91
+ }}
92
+ >
93
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
94
+ <Sparkles size={15} strokeWidth={2} color={accent} aria-hidden />
95
+ <h2
96
+ style={{
97
+ flex: 1,
98
+ minWidth: 0,
99
+ margin: 0,
100
+ overflowWrap: 'anywhere',
101
+ fontFamily: theme.fonts.monospace,
102
+ fontSize: theme.fontSizes[2],
103
+ fontWeight: theme.fontWeights.semibold,
104
+ }}
105
+ >
106
+ {decoration.title}
107
+ </h2>
108
+ <button
109
+ type="button"
110
+ aria-pressed={active}
111
+ aria-label={active ? 'Turn decoration off' : 'Turn decoration on'}
112
+ title={active ? 'Turn decoration off' : 'Turn decoration on'}
113
+ onClick={() => onToggleActive(!active)}
114
+ style={{
115
+ display: 'inline-flex',
116
+ alignItems: 'center',
117
+ gap: 4,
118
+ flexShrink: 0,
119
+ padding: '3px 8px',
120
+ borderRadius: 4,
121
+ border: `1px solid ${active ? accent : theme.colors.border}`,
122
+ background: active ? `${accent}18` : 'transparent',
123
+ color: active ? accent : muted,
124
+ fontFamily: theme.fonts.monospace,
125
+ fontSize: theme.fontSizes[0],
126
+ cursor: 'pointer',
127
+ }}
128
+ >
129
+ {active ? <Eye size={12} strokeWidth={2} /> : <EyeOff size={12} strokeWidth={2} />}
130
+ {active ? 'On' : 'Off'}
131
+ </button>
132
+ </div>
133
+ </header>
134
+
135
+ <div style={{ flex: 1, minHeight: 0, overflowY: 'auto', padding: '12px 16px 18px' }}>
136
+ {decoration.description && (
137
+ <div style={{ fontSize: theme.fontSizes[1], lineHeight: theme.lineHeights.body }}>
138
+ <IndustryMarkdownSlide
139
+ content={decoration.description}
140
+ slideIdPrefix={`decoration-${decoration.id}`}
141
+ slideIndex={0}
142
+ isVisible={true}
143
+ theme={theme}
144
+ disableScroll={true}
145
+ disableBasePadding
146
+ enableKeyboardScrolling={false}
147
+ autoFocusOnVisible={false}
148
+ />
149
+ </div>
150
+ )}
151
+
152
+ <section
153
+ style={{
154
+ marginTop: 16,
155
+ paddingTop: 14,
156
+ borderTop: `1px solid ${theme.colors.border}`,
157
+ }}
158
+ >
159
+ <div style={{ ...sectionLabel, color: secondary }}>
160
+ Trails · {decoration.trails.length}
161
+ </div>
162
+ {groups.map((group) => (
163
+ <div key={group.modelId}>
164
+ <div
165
+ style={{
166
+ display: 'flex',
167
+ alignItems: 'center',
168
+ gap: 6,
169
+ margin: '12px 0 3px',
170
+ }}
171
+ >
172
+ <Network size={14} strokeWidth={2} color={secondary} aria-hidden />
173
+ <span
174
+ style={{
175
+ minWidth: 0,
176
+ overflow: 'hidden',
177
+ textOverflow: 'ellipsis',
178
+ whiteSpace: 'nowrap',
179
+ fontFamily: theme.fonts.monospace,
180
+ fontSize: theme.fontSizes[1],
181
+ fontWeight: theme.fontWeights.semibold,
182
+ color: theme.colors.text,
183
+ }}
184
+ >
185
+ {modelLabels?.get(group.modelId) ?? group.modelId}
186
+ </span>
187
+ </div>
188
+ {group.trails.map((ref) => {
189
+ const meta = resolveTrail?.(ref);
190
+ const title =
191
+ meta?.title ??
192
+ (ref.kind === 'draft' ? ref.trail.title : ref.trailId);
193
+ const stepCount =
194
+ meta?.stepCount ??
195
+ (ref.kind === 'draft' ? ref.trail.steps.length : undefined);
196
+ return (
197
+ <button
198
+ key={decorationTrailId(ref)}
199
+ type="button"
200
+ onClick={() => onSelectTrail?.(ref)}
201
+ style={{
202
+ display: 'flex',
203
+ alignItems: 'center',
204
+ gap: 8,
205
+ width: '100%',
206
+ minWidth: 0,
207
+ padding: '6px 6px 6px 0',
208
+ border: 'none',
209
+ borderRadius: 4,
210
+ background: 'transparent',
211
+ color: theme.colors.text,
212
+ textAlign: 'left',
213
+ cursor: onSelectTrail ? 'pointer' : 'default',
214
+ }}
215
+ >
216
+ <Route size={13} strokeWidth={2} color={muted} aria-hidden />
217
+ <span
218
+ style={{
219
+ flex: 1,
220
+ minWidth: 0,
221
+ overflow: 'hidden',
222
+ textOverflow: 'ellipsis',
223
+ whiteSpace: 'nowrap',
224
+ fontFamily: theme.fonts.monospace,
225
+ fontSize: theme.fontSizes[1],
226
+ }}
227
+ >
228
+ {title}
229
+ </span>
230
+ {ref.kind === 'draft' && (
231
+ <span
232
+ style={{
233
+ flexShrink: 0,
234
+ padding: '1px 5px',
235
+ borderRadius: 3,
236
+ border: `1px solid ${warning}`,
237
+ color: warning,
238
+ fontFamily: theme.fonts.monospace,
239
+ fontSize: theme.fontSizes[0],
240
+ }}
241
+ >
242
+ draft
243
+ </span>
244
+ )}
245
+ {stepCount != null && (
246
+ <span
247
+ style={{
248
+ flexShrink: 0,
249
+ fontFamily: theme.fonts.monospace,
250
+ fontSize: theme.fontSizes[0],
251
+ color: secondary,
252
+ }}
253
+ >
254
+ {stepCount}
255
+ </span>
256
+ )}
257
+ </button>
258
+ );
259
+ })}
260
+ </div>
261
+ ))}
262
+ </section>
263
+
264
+ {(draftComponents.length > 0 || draftEdges.length > 0) && (
265
+ <section style={{ marginTop: 18 }}>
266
+ <div style={{ ...sectionLabel, color: secondary }}>
267
+ Draft structure · not verified
268
+ </div>
269
+ {draftComponents.map((component) => (
270
+ <button
271
+ key={component.alias}
272
+ type="button"
273
+ onClick={() => onSelectDraftComponent?.(component)}
274
+ style={{
275
+ display: 'flex',
276
+ alignItems: 'center',
277
+ gap: 8,
278
+ width: '100%',
279
+ minWidth: 0,
280
+ padding: '6px 6px 6px 0',
281
+ border: 'none',
282
+ borderRadius: 4,
283
+ background: 'transparent',
284
+ color: theme.colors.text,
285
+ textAlign: 'left',
286
+ cursor: onSelectDraftComponent ? 'pointer' : 'default',
287
+ }}
288
+ >
289
+ <Box size={13} strokeWidth={2} color={muted} aria-hidden />
290
+ <span
291
+ style={{
292
+ flex: 1,
293
+ minWidth: 0,
294
+ overflow: 'hidden',
295
+ textOverflow: 'ellipsis',
296
+ whiteSpace: 'nowrap',
297
+ fontFamily: theme.fonts.monospace,
298
+ fontSize: theme.fontSizes[1],
299
+ }}
300
+ >
301
+ {component.name}
302
+ </span>
303
+ {component.construct && (
304
+ <span
305
+ style={{
306
+ flexShrink: 0,
307
+ fontFamily: theme.fonts.monospace,
308
+ fontSize: theme.fontSizes[0],
309
+ color: secondary,
310
+ }}
311
+ >
312
+ {component.construct}
313
+ </span>
314
+ )}
315
+ </button>
316
+ ))}
317
+ {draftEdges.length > 0 && (
318
+ <div
319
+ style={{
320
+ marginTop: 4,
321
+ fontFamily: theme.fonts.monospace,
322
+ fontSize: theme.fontSizes[0],
323
+ color: secondary,
324
+ }}
325
+ >
326
+ + {draftEdges.length} draft edge{draftEdges.length === 1 ? '' : 's'}
327
+ </div>
328
+ )}
329
+ </section>
330
+ )}
331
+ </div>
332
+ </div>
333
+ );
334
+ }
@@ -26,6 +26,7 @@ export function FileDrawer({
26
26
  suppressEscape = false,
27
27
  hidden = false,
28
28
  progress = null,
29
+ subheader = null,
29
30
  children,
30
31
  }: {
31
32
  /** Drawer chrome title; `null` closes the drawer. */
@@ -43,6 +44,8 @@ export function FileDrawer({
43
44
  * content with no position (a single file).
44
45
  */
45
46
  progress?: { index: number; total: number } | null;
47
+ /** Optional chrome row below the title (e.g. the trail call stack). */
48
+ subheader?: ReactNode;
46
49
  children?: ReactNode;
47
50
  }) {
48
51
  const { theme } = useTheme();
@@ -223,6 +226,9 @@ export function FileDrawer({
223
226
  </button>
224
227
  </div>
225
228
  </div>
229
+ {subheader != null && open && (
230
+ <div style={{ flexShrink: 0, minHeight: 0 }}>{subheader}</div>
231
+ )}
226
232
  <div style={{ flex: 1, minHeight: 0, overflow: "auto" }}>
227
233
  {children}
228
234
  </div>
@@ -123,6 +123,12 @@ const KIND_CATEGORY: Record<string, SubsystemIssueCategory> = {
123
123
  trail: 'trail',
124
124
  step_span_unconfirmed: 'trail',
125
125
  step_span_stale: 'trail',
126
+ step_site_outside_from: 'trail',
127
+ step_site_unconfirmed: 'trail',
128
+ step_site_mismatch: 'trail',
129
+ step_site_stale: 'trail',
130
+ step_target_mismatch: 'trail',
131
+ step_target_stale: 'trail',
126
132
  };
127
133
 
128
134
  /**
@@ -198,6 +204,14 @@ export const ISSUE_KIND_ICON: Record<string, LucideIcon> = {
198
204
  construct_span_stale: FileCodeCorner,
199
205
  step_span_unconfirmed: FileCodeCorner,
200
206
  step_span_stale: FileCodeCorner,
207
+ // A `calls` edge whose site does not live in the source component — the
208
+ // mechanism and the code are disagreeing, so a broken-link glyph.
209
+ step_site_outside_from: Link2Off,
210
+ step_site_unconfirmed: FileCodeCorner,
211
+ step_site_mismatch: Link2Off,
212
+ step_site_stale: FileCodeCorner,
213
+ step_target_mismatch: Link2Off,
214
+ step_target_stale: FileCodeCorner,
201
215
  };
202
216
 
203
217
  /**
@@ -288,6 +302,12 @@ const KIND_LABEL: Record<string, string> = {
288
302
  trail: 'Step issue',
289
303
  step_span_unconfirmed: 'Step span not captured',
290
304
  step_span_stale: 'Step span changed',
305
+ step_site_outside_from: 'Calls site outside source',
306
+ step_site_unconfirmed: 'Site construct not captured',
307
+ step_site_mismatch: 'Site construct mismatch',
308
+ step_site_stale: 'Site construct changed',
309
+ step_target_mismatch: 'Target construct mismatch',
310
+ step_target_stale: 'Target construct changed',
291
311
  };
292
312
 
293
313
  /** Label for an issue's kind. Explicit `kindLabel` wins; unknown kinds humanize. */
@@ -329,6 +349,12 @@ const KIND_ORDER: Record<string, number> = {
329
349
  trail: 40,
330
350
  step_span_unconfirmed: 41,
331
351
  step_span_stale: 42,
352
+ step_site_outside_from: 43,
353
+ step_site_unconfirmed: 44,
354
+ step_site_mismatch: 45,
355
+ step_site_stale: 46,
356
+ step_target_mismatch: 47,
357
+ step_target_stale: 48,
332
358
  };
333
359
 
334
360
  const KIND_ORDER_FALLBACK = 100;