@principal-ai/principal-view-react 0.16.49 → 0.16.50

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 (44) hide show
  1. package/dist/graphify/anchor.d.ts +28 -0
  2. package/dist/graphify/anchor.d.ts.map +1 -0
  3. package/dist/graphify/anchor.js +132 -0
  4. package/dist/graphify/anchor.js.map +1 -0
  5. package/dist/graphify/ids.d.ts +19 -0
  6. package/dist/graphify/ids.d.ts.map +1 -0
  7. package/dist/graphify/ids.js +46 -0
  8. package/dist/graphify/ids.js.map +1 -0
  9. package/dist/graphify/index.d.ts +7 -0
  10. package/dist/graphify/index.d.ts.map +1 -1
  11. package/dist/graphify/index.js +4 -0
  12. package/dist/graphify/index.js.map +1 -1
  13. package/dist/graphify/kind.d.ts +23 -0
  14. package/dist/graphify/kind.d.ts.map +1 -0
  15. package/dist/graphify/kind.js +71 -0
  16. package/dist/graphify/kind.js.map +1 -0
  17. package/dist/graphify/signature.d.ts +87 -0
  18. package/dist/graphify/signature.d.ts.map +1 -0
  19. package/dist/graphify/signature.js +276 -0
  20. package/dist/graphify/signature.js.map +1 -0
  21. package/dist/index.d.ts +4 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/subsystem/ComponentDeclaration.d.ts +58 -3
  26. package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
  27. package/dist/subsystem/ComponentDeclaration.js +168 -35
  28. package/dist/subsystem/ComponentDeclaration.js.map +1 -1
  29. package/dist/subsystem/SubsystemComponentGraph.d.ts +5 -0
  30. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  31. package/dist/subsystem/SubsystemComponentGraph.js +2 -2
  32. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/graphify/anchor.test.ts +124 -0
  35. package/src/graphify/anchor.ts +160 -0
  36. package/src/graphify/ids.ts +48 -0
  37. package/src/graphify/index.ts +26 -0
  38. package/src/graphify/kind.test.ts +108 -0
  39. package/src/graphify/kind.ts +114 -0
  40. package/src/graphify/signature.test.ts +278 -0
  41. package/src/graphify/signature.ts +354 -0
  42. package/src/index.ts +26 -0
  43. package/src/subsystem/ComponentDeclaration.tsx +287 -40
  44. package/src/subsystem/SubsystemComponentGraph.tsx +8 -1
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { useEffect, useRef, useState, type ReactNode } from 'react';
10
- import { AlignLeft, FileText } from 'lucide-react';
10
+ import { AlignLeft, FileText, ShieldCheck } from 'lucide-react';
11
11
  import { useTheme } from '@principal-ade/industry-theme';
12
12
  import {
13
13
  KIND_COLOR,
@@ -17,6 +17,53 @@ import {
17
17
  } from './model';
18
18
  import { tokenizeComponent } from './tokenizeComponent';
19
19
 
20
+ /** Live / result state for the declaration-panel Verify control. */
21
+ export type ComponentVerificationPhase =
22
+ | 'idle'
23
+ | 'checking'
24
+ | 'done'
25
+ | 'error';
26
+
27
+ export interface ComponentVerificationState {
28
+ phase: ComponentVerificationPhase;
29
+ message?: string;
30
+ /** Mirrors host `ok` when a structured verify result is available. */
31
+ ok?: boolean;
32
+ code?: string;
33
+ file?: {
34
+ exists: boolean;
35
+ symbolDeclared?: boolean | null;
36
+ };
37
+ cache?: {
38
+ status: 'ready' | 'missing' | 'unavailable';
39
+ purl: string;
40
+ };
41
+ anchor?: {
42
+ resolution: 'exact' | 'file-only' | 'ambiguous' | 'missing';
43
+ nodeId?: string;
44
+ label?: string;
45
+ source_file?: string;
46
+ source_location?: string;
47
+ candidates?: Array<{ nodeId: string; label: string; source_file?: string }>;
48
+ };
49
+ kind?: {
50
+ claimed: string;
51
+ inferred: string;
52
+ match: boolean;
53
+ evidence?: string[];
54
+ };
55
+ signature?: {
56
+ match: boolean;
57
+ skipped: boolean;
58
+ skipCode?: string;
59
+ reason?: string;
60
+ claimed: { parameterTypes: string[]; returnTypes: string[] };
61
+ inferred: { parameterTypes: string[]; returnTypes: string[] };
62
+ /** Graphify `inline_parameter` marker count (anonymous eager args). */
63
+ inlineParameters?: number;
64
+ };
65
+ }
66
+
20
67
  /** Shared affordance for clickable text pieces. */
21
68
  const clickableStyle = {
22
69
  cursor: 'pointer',
@@ -46,8 +93,8 @@ function DetailLink({ children, onClick }: { children: ReactNode; onClick?: () =
46
93
  );
47
94
  }
48
95
 
49
- /** Panel props. The two callbacks are wired by the graph; standalone usage
50
- * without them renders the same panel with nothing clickable. */
96
+ /** Panel props. Callbacks are wired by the graph; standalone usage without
97
+ * them renders the same panel with nothing clickable. */
51
98
  export interface ComponentDeclarationProps {
52
99
  component: SubsystemComponent;
53
100
  /** File-path click → open that file in the bottom drawer. */
@@ -57,20 +104,153 @@ export interface ComponentDeclarationProps {
57
104
  onRelatedSelect?: (ref: string) => void;
58
105
  /** Max width of the declaration panel (CSS value). Defaults to none. */
59
106
  maxWidth?: string | number;
107
+ /** When set, shows a Verify control that calls back with the component id. */
108
+ onVerify?: (componentId: string) => void;
109
+ /** Live verification status for the selected component. */
110
+ verification?: ComponentVerificationState | null;
111
+ }
112
+
113
+ function verificationSummary(
114
+ v: ComponentVerificationState,
115
+ muted: string,
116
+ ok: string,
117
+ warn: string,
118
+ ): { text: string; color: string } {
119
+ if (v.phase === 'checking') {
120
+ return { text: v.message ?? 'Verifying…', color: muted };
121
+ }
122
+ if (v.phase === 'error') {
123
+ return { text: v.message ?? 'Verification failed', color: '#e5534b' };
124
+ }
125
+ if (v.phase !== 'done') {
126
+ return { text: '', color: muted };
127
+ }
128
+ const bits: string[] = [];
129
+ if (v.file) {
130
+ if (!v.file.exists) bits.push('file missing');
131
+ else if (v.file.symbolDeclared === false) bits.push('symbol not declared in file');
132
+ else if (v.file.symbolDeclared === true) bits.push('file+symbol ok');
133
+ else bits.push('file exists');
134
+ }
135
+ if (v.cache?.status === 'missing') {
136
+ return {
137
+ text: [...bits, 'graphify cache not ready — run graphify from Subsystems list'].join(' · '),
138
+ color: warn,
139
+ };
140
+ }
141
+ if (v.cache?.status === 'unavailable') {
142
+ return {
143
+ text: [...bits, 'no local checkout for this purl'].join(' · '),
144
+ color: muted,
145
+ };
146
+ }
147
+ const res = v.anchor?.resolution;
148
+ if (res === 'exact') {
149
+ const loc = v.anchor?.source_location ? ` @ ${v.anchor.source_location}` : '';
150
+ const anchorBit = `exact → ${v.anchor?.label ?? v.anchor?.nodeId ?? 'node'}${loc}`;
151
+ if (v.kind && !v.kind.match) {
152
+ const why =
153
+ v.kind.inferred === 'unknown'
154
+ ? `kind unknown (claimed ${v.kind.claimed})`
155
+ : `kind mismatch: claimed ${v.kind.claimed}, inferred ${v.kind.inferred}`;
156
+ return {
157
+ text: [...bits, anchorBit, why].join(' · '),
158
+ color: '#e5534b',
159
+ };
160
+ }
161
+ if (v.signature && !v.signature.skipped && !v.signature.match) {
162
+ const cParams = v.signature.claimed.parameterTypes.join(', ') || '∅';
163
+ const iParams = v.signature.inferred.parameterTypes.join(', ') || '∅';
164
+ const cRet = v.signature.claimed.returnTypes.join(', ') || '∅';
165
+ const iRet = v.signature.inferred.returnTypes.join(', ') || '∅';
166
+ return {
167
+ text: [
168
+ ...bits,
169
+ anchorBit,
170
+ v.kind?.match ? `kind ${v.kind.inferred}` : undefined,
171
+ `signature mismatch: params [${cParams}]≠[${iParams}] return [${cRet}]≠[${iRet}]`,
172
+ ]
173
+ .filter(Boolean)
174
+ .join(' · '),
175
+ color: '#e5534b',
176
+ };
177
+ }
178
+ const kindBit =
179
+ v.kind?.match ? `kind ${v.kind.inferred}` : undefined;
180
+ const inlineBit =
181
+ v.signature?.inlineParameters
182
+ ? `${v.signature.inlineParameters} inline param${v.signature.inlineParameters === 1 ? '' : 's'} verified`
183
+ : undefined;
184
+ if (v.signature?.skipped) {
185
+ const skipPhrase: Record<string, string> = {
186
+ no_claimed_types: 'no claimable named types (primitives/inline only)',
187
+ generic_arg_only:
188
+ 'return only as generic_arg — wrapper types (Promise/Omit/Map/Array) not read as return edges',
189
+ partially_generic_arg:
190
+ 'claimed types resolve only via generic_arg; rest unresolved',
191
+ unresolved_claimed_types:
192
+ 'claimed return type has no graphify edge (npm/global/DOM type)',
193
+ };
194
+ return {
195
+ text: [
196
+ ...bits,
197
+ anchorBit,
198
+ ...(kindBit ? [kindBit] : []),
199
+ ...(inlineBit ? [inlineBit] : []),
200
+ v.signature.skipCode
201
+ ? `params/return not fully checked — ${skipPhrase[v.signature.skipCode] ?? v.signature.skipCode}`
202
+ : 'params/return not checked',
203
+ ].join(' · '),
204
+ color: warn,
205
+ };
206
+ }
207
+ const sigBit =
208
+ v.signature && v.signature.match ? 'params/return ok' : undefined;
209
+ return {
210
+ text: [...bits, anchorBit, ...(kindBit ? [kindBit] : []), ...(sigBit ? [sigBit] : [])].join(' · '),
211
+ color: ok,
212
+ };
213
+ }
214
+ if (res === 'file-only') {
215
+ return {
216
+ text: [...bits, 'file in graph, symbol not uniquely matched'].join(' · '),
217
+ color: warn,
218
+ };
219
+ }
220
+ if (res === 'ambiguous') {
221
+ const n = v.anchor?.candidates?.length ?? 0;
222
+ return {
223
+ text: [...bits, `ambiguous (${n} candidates)`].join(' · '),
224
+ color: warn,
225
+ };
226
+ }
227
+ if (res === 'missing') {
228
+ return {
229
+ text: [...bits, 'no matching graphify node'].join(' · '),
230
+ color: '#e5534b',
231
+ };
232
+ }
233
+ return { text: bits.join(' · ') || 'done', color: muted };
60
234
  }
61
235
 
62
236
  /** Declaration panel for the selected component — its definition, code-style. */
63
- export function ComponentDeclaration({ component, onOpenFile, onRelatedSelect: _onRelatedSelect, maxWidth }: ComponentDeclarationProps) {
237
+ export function ComponentDeclaration({
238
+ component,
239
+ onOpenFile,
240
+ onRelatedSelect: _onRelatedSelect,
241
+ maxWidth,
242
+ onVerify,
243
+ verification,
244
+ }: ComponentDeclarationProps) {
64
245
  const { theme } = useTheme();
65
246
  const [fileHovered, setFileHovered] = useState(false);
66
- // Header visibility toggles — off by default so the panel opens as pure
67
- // code; the repo-row icons bring the path and description back.
68
247
  const [showFile, setShowFile] = useState(false);
69
248
  const [showPurpose, setShowPurpose] = useState(false);
70
249
  const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
71
250
  const color = KIND_COLOR[component.kind] ?? '#888';
251
+ const okColor = '#3d9a5f';
252
+ const warnColor = theme.colors.textSecondary;
72
253
 
73
- // Token-kind → theme-color map (used by the token iterator below).
74
254
  const tokenColor: Record<SubsystemDeclTokenKind, string> = {
75
255
  keyword: theme.colors.secondary,
76
256
  name: color,
@@ -78,7 +258,7 @@ export function ComponentDeclaration({ component, onOpenFile, onRelatedSelect: _
78
258
  type: theme.colors.accent,
79
259
  punctuation: muted,
80
260
  string: theme.colors.success,
81
- newline: '', // not rendered — drives line breaks
261
+ newline: '',
82
262
  };
83
263
 
84
264
  const line = (children: ReactNode, key?: string, indent?: boolean | number) => {
@@ -98,9 +278,6 @@ export function ComponentDeclaration({ component, onOpenFile, onRelatedSelect: _
98
278
  key,
99
279
  indent,
100
280
  );
101
- // Purpose is the panel's actual prose, not code — body font, readable
102
- // size/contrast, capped measure. The `//` metadata lines above it stay
103
- // muted mono on purpose.
104
281
  const purposeStyle = {
105
282
  color: theme.colors.text,
106
283
  fontFamily: theme.fonts.body,
@@ -108,17 +285,9 @@ export function ComponentDeclaration({ component, onOpenFile, onRelatedSelect: _
108
285
  lineHeight: 1.5,
109
286
  maxWidth: '52ch',
110
287
  } as const;
111
- // Trailing relationship comments (`// called by:`, `// constructed by:`,
112
- // `// implemented by:`, …) are deliberately not rendered — the graph's
113
- // edges carry interactions, and duplicated name lists read as clutter. The
114
- // data stays on the Graphify detail types for other surfaces.
288
+
115
289
  const lines: ReactNode[] = [];
116
- // Repo identity — owner avatar + name for GitHub-hosted purls; anything
117
- // else falls back to the formatted purl as a quiet comment. The avatar
118
- // hides itself on load failure (offline / blocked hosts).
119
290
  const ghMatch = /^pkg:github\/([^/]+)\/([^/#?]+)/.exec(component.purl ?? '');
120
- // Small ghost icon button for the header toggles — accent when its section
121
- // is shown, muted when hidden.
122
291
  const toggleBtn = (on: boolean, onClick: () => void, title: string, Icon: typeof FileText) => (
123
292
  <button
124
293
  type="button"
@@ -142,6 +311,44 @@ export function ComponentDeclaration({ component, onOpenFile, onRelatedSelect: _
142
311
  <Icon size={13} />
143
312
  </button>
144
313
  );
314
+ const verifyBusy = verification?.phase === 'checking';
315
+ const headerActions = (
316
+ <span style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 2 }}>
317
+ {onVerify && (
318
+ <button
319
+ type="button"
320
+ title="Verify against graphify"
321
+ aria-label="Verify against graphify"
322
+ disabled={verifyBusy}
323
+ onClick={(e) => {
324
+ e.stopPropagation();
325
+ onVerify(component.id);
326
+ }}
327
+ style={{
328
+ display: 'flex',
329
+ alignItems: 'center',
330
+ gap: 4,
331
+ height: 20,
332
+ padding: '0 6px',
333
+ border: `1px solid ${theme.colors.border}`,
334
+ borderRadius: 4,
335
+ background: 'transparent',
336
+ color: verifyBusy ? muted : theme.colors.textSecondary,
337
+ cursor: verifyBusy ? 'wait' : 'pointer',
338
+ fontSize: theme.fontSizes[0],
339
+ fontFamily: theme.fonts.body,
340
+ opacity: verifyBusy ? 0.7 : 1,
341
+ }}
342
+ >
343
+ <ShieldCheck size={12} />
344
+ {verifyBusy ? '…' : 'Verify'}
345
+ </button>
346
+ )}
347
+ {toggleBtn(showFile, () => setShowFile((v) => !v), 'Toggle file path', FileText)}
348
+ {toggleBtn(showPurpose, () => setShowPurpose((v) => !v), 'Toggle description', AlignLeft)}
349
+ </span>
350
+ );
351
+
145
352
  if (ghMatch) {
146
353
  const [, ghOwner, ghRepo] = ghMatch;
147
354
  lines.push(
@@ -166,17 +373,68 @@ export function ComponentDeclaration({ component, onOpenFile, onRelatedSelect: _
166
373
  >
167
374
  {ghRepo}
168
375
  </span>
169
- <span style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 2 }}>
170
- {toggleBtn(showFile, () => setShowFile((v) => !v), 'Toggle file path', FileText)}
171
- {toggleBtn(showPurpose, () => setShowPurpose((v) => !v), 'Toggle description', AlignLeft)}
172
- </span>
376
+ {headerActions}
173
377
  </span>,
174
378
  'repo',
175
379
  ),
176
380
  );
177
381
  } else if (component.purl) {
178
- lines.push(commentLine(`// ${formatPurl(component.purl)}`, 'purl'));
382
+ lines.push(
383
+ line(
384
+ <span style={{ display: 'flex', alignItems: 'center', gap: 6, width: '100%' }}>
385
+ <span style={commentStyle}>{`// ${formatPurl(component.purl)}`}</span>
386
+ {headerActions}
387
+ </span>,
388
+ 'purl',
389
+ ),
390
+ );
391
+ } else if (onVerify) {
392
+ lines.push(line(headerActions, 'actions'));
179
393
  }
394
+
395
+ if (verification && verification.phase !== 'idle') {
396
+ const summary = verificationSummary(verification, muted, okColor, warnColor);
397
+ if (summary.text) {
398
+ lines.push(
399
+ line(
400
+ <span
401
+ style={{
402
+ color: summary.color,
403
+ fontFamily: theme.fonts.body,
404
+ fontSize: theme.fontSizes[0],
405
+ whiteSpace: 'normal',
406
+ }}
407
+ >
408
+ {summary.text}
409
+ </span>,
410
+ 'verify-status',
411
+ ),
412
+ );
413
+ }
414
+ if (
415
+ verification.anchor?.resolution === 'ambiguous' &&
416
+ verification.anchor.candidates &&
417
+ verification.anchor.candidates.length > 0
418
+ ) {
419
+ for (const c of verification.anchor.candidates.slice(0, 5)) {
420
+ lines.push(
421
+ commentLine(
422
+ `// ${c.label}${c.source_file ? ` — ${c.source_file}` : ''}`,
423
+ `cand-${c.nodeId}`,
424
+ ),
425
+ );
426
+ }
427
+ }
428
+ if (verification.signature?.skipped && verification.signature.reason) {
429
+ lines.push(
430
+ commentLine(
431
+ `// why: ${verification.signature.reason}`,
432
+ 'verify-skip-reason',
433
+ ),
434
+ );
435
+ }
436
+ }
437
+
180
438
  if (showFile && component.file)
181
439
  lines.push(
182
440
  line(
@@ -185,8 +443,6 @@ export function ComponentDeclaration({ component, onOpenFile, onRelatedSelect: _
185
443
  onMouseEnter={() => setFileHovered(true)}
186
444
  onMouseLeave={() => setFileHovered(false)}
187
445
  style={{
188
- // Path as UI text, not a code comment — matches the repo-name
189
- // row. Quiet until hover; the underline only appears then.
190
446
  color: theme.colors.textSecondary ?? muted,
191
447
  fontFamily: theme.fonts.body,
192
448
  fontSize: theme.fontSizes[1],
@@ -201,9 +457,6 @@ export function ComponentDeclaration({ component, onOpenFile, onRelatedSelect: _
201
457
  ),
202
458
  );
203
459
 
204
- // --- Container width measurement --------------------------------------
205
- // Approximate character count from the container's clientWidth so Prettier
206
- // wraps at the same column the code block will actually use.
207
460
  const containerRef = useRef<HTMLDivElement>(null);
208
461
  const [printWidth, setPrintWidth] = useState(80);
209
462
 
@@ -212,8 +465,6 @@ export function ComponentDeclaration({ component, onOpenFile, onRelatedSelect: _
212
465
  if (!el) return;
213
466
 
214
467
  function measureWidth() {
215
- // Measure a single `ch` using an offscreen element so we don't disturb
216
- // the component's own DOM.
217
468
  const probe = document.createElement('div');
218
469
  probe.style.cssText =
219
470
  'position:absolute;visibility:hidden;white-space:pre;' +
@@ -233,9 +484,6 @@ export function ComponentDeclaration({ component, onOpenFile, onRelatedSelect: _
233
484
  return () => ro.disconnect();
234
485
  }, []);
235
486
 
236
- // --- Declaration tokens ------------------------------------------------
237
- // Tokenize asynchronously via Prettier + highlight.js. Pre-tokenized
238
- // tokens from the wire skip the pipeline entirely.
239
487
  const [tokens, setTokens] = useState(component.tokens ?? []);
240
488
  useEffect(() => {
241
489
  if (component.tokens) {
@@ -246,7 +494,9 @@ export function ComponentDeclaration({ component, onOpenFile, onRelatedSelect: _
246
494
  tokenizeComponent(component, printWidth).then((t) => {
247
495
  if (!cancelled) setTokens(t);
248
496
  });
249
- return () => { cancelled = true; };
497
+ return () => {
498
+ cancelled = true;
499
+ };
250
500
  }, [component, printWidth]);
251
501
  const declLines: ReactNode[][] = [[]];
252
502
  let di = 0;
@@ -271,12 +521,9 @@ export function ComponentDeclaration({ component, onOpenFile, onRelatedSelect: _
271
521
  );
272
522
  }
273
523
 
274
- // Purpose prose follows the declaration — identity and syntax first, the
275
- // explanation underneath. Newlines split into separate prose lines so a
276
- // two-thought purpose reads as a short paragraph instead of one blob.
277
524
  if (showPurpose && component.purpose) {
278
525
  lines.push(line(<div style={{ height: 6 }} />, 'purpose-gap'));
279
- component.purpose.split("\n").forEach((purposeLine, i) =>
526
+ component.purpose.split('\n').forEach((purposeLine, i) =>
280
527
  lines.push(
281
528
  line(
282
529
  <span style={{ ...purposeStyle, display: 'block' }} key={`purpose${i}`}>
@@ -45,6 +45,7 @@ import { SubsystemComponentNode, SubsystemEdge, SUBSYSTEM_CALLBACKS } from './no
45
45
  import { SubsystemFileTree } from './SubsystemFileTree';
46
46
  import { GraphLayoutCover } from './GraphLayoutCover';
47
47
  import { ComponentDeclaration } from './ComponentDeclaration';
48
+ import type { ComponentVerificationState } from './ComponentDeclaration';
48
49
  import { FileDrawer } from './FileDrawer';
49
50
  import { EdgeLegendModal, MECHANISM_DESCRIPTIONS } from './EdgeLegendModal';
50
51
  import { buildRepoGroups, repoAvatarUrl, type RepoGroup } from './paths';
@@ -88,6 +89,10 @@ export interface SubsystemComponentGraphProps {
88
89
  * path). The tree is derived from the components' `file` values.
89
90
  */
90
91
  onFileSelect?: (file: string) => void;
92
+ /** Verify the selected component against graphify (declaration panel). */
93
+ onVerifyComponent?: (componentId: string) => void;
94
+ /** Live verification status for the selected component. */
95
+ componentVerification?: ComponentVerificationState | null;
91
96
  }
92
97
 
93
98
  const nodeTypes: NodeTypes = {
@@ -117,7 +122,7 @@ interface InnerProps extends SubsystemComponentGraphProps {
117
122
  measured: { w: number; h: number } | null;
118
123
  }
119
124
 
120
- function Inner({ components, edges, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, title, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, renderFileView, renderFileViewer, onFileSelect }: InnerProps) {
125
+ function Inner({ components, edges, onSelect, onEdgeSelect, measured: _measured, maxNodeWidth, showEdgeLabels, title, description, canvasOverlay, sidebarExtra, sidebarAfterDescription, renderFileView, renderFileViewer, onFileSelect, onVerifyComponent, componentVerification }: InnerProps) {
121
126
  const { theme } = useTheme();
122
127
  const { fitView } = useReactFlow();
123
128
  const viewport = useViewport();
@@ -699,6 +704,8 @@ function Inner({ components, edges, onSelect, onEdgeSelect, measured: _measured,
699
704
  component={selected}
700
705
  onOpenFile={onTreeSelectFile}
701
706
  onRelatedSelect={resolveRelatedComponent}
707
+ onVerify={onVerifyComponent}
708
+ verification={componentVerification}
702
709
  />
703
710
  </div>
704
711
  )}