@principal-ai/principal-view-core 0.25.0 → 0.26.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.
@@ -0,0 +1,419 @@
1
+ /**
2
+ * Canvas File Manifest Builder
3
+ *
4
+ * Utilities for building aggregated file manifests from canvases, workflows, and storyboards.
5
+ * These manifests provide structured access to all files associated with OTEL canvases,
6
+ * with bidirectional mappings between files, nodes, and events.
7
+ */
8
+
9
+ import type {
10
+ ExtendedCanvas,
11
+ ExtendedCanvasNode,
12
+ OtelNode,
13
+ OtelEventNode,
14
+ } from '../types/canvas';
15
+ import type { WorkflowTemplate, WorkflowScenario } from '../workflow/types';
16
+ import type {
17
+ CanvasFileManifest,
18
+ WorkflowFileManifest,
19
+ StoryboardFileManifest,
20
+ CanvasFile,
21
+ CanvasFileRole,
22
+ CanvasFileOrigin,
23
+ CanvasType,
24
+ } from './types';
25
+
26
+ // =============================================================================
27
+ // Type Guards
28
+ // =============================================================================
29
+
30
+ function isOtelNode(node: ExtendedCanvasNode): node is OtelNode {
31
+ return node.type.startsWith('otel-');
32
+ }
33
+
34
+ function isOtelEventNode(node: ExtendedCanvasNode): node is OtelEventNode {
35
+ return node.type === 'otel-event';
36
+ }
37
+
38
+ // =============================================================================
39
+ // Node File Extraction
40
+ // =============================================================================
41
+
42
+ interface ExtractedFile {
43
+ path: string;
44
+ role: CanvasFileRole;
45
+ origin: CanvasFileOrigin;
46
+ eventName?: string;
47
+ }
48
+
49
+ function extractNodeFiles(node: ExtendedCanvasNode): ExtractedFile[] {
50
+ const files: ExtractedFile[] = [];
51
+
52
+ // Only process OTEL nodes
53
+ if (!isOtelNode(node) || !node.otel) {
54
+ return files;
55
+ }
56
+
57
+ // Get event name if this is an otel-event node
58
+ let eventName: string | undefined;
59
+ if (isOtelEventNode(node)) {
60
+ eventName = node.event?.name ?? node.eventRef;
61
+ }
62
+
63
+ const origin: CanvasFileOrigin = node.otel.origin ?? 'internal';
64
+
65
+ // Instrumentation files
66
+ if (node.otel.files) {
67
+ for (const path of node.otel.files) {
68
+ files.push({ path, role: 'instrumentation', origin, eventName });
69
+ }
70
+ }
71
+
72
+ // References
73
+ if (node.otel.references) {
74
+ for (const path of node.otel.references) {
75
+ files.push({ path, role: 'reference', origin: 'external', eventName });
76
+ }
77
+ }
78
+
79
+ return files;
80
+ }
81
+
82
+ // =============================================================================
83
+ // Canvas Manifest Builder
84
+ // =============================================================================
85
+
86
+ /**
87
+ * Build a file manifest from a canvas
88
+ *
89
+ * @param canvas - The parsed ExtendedCanvas
90
+ * @param canvasId - Canvas identifier
91
+ * @param canvasPath - Canvas file path
92
+ * @param canvasType - Canvas type (otel, scopes, spans, resources)
93
+ * @returns Aggregated file manifest
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * const manifest = buildCanvasFileManifest(
98
+ * canvas,
99
+ * 'my-canvas',
100
+ * '.principal-views/my-canvas.otel.canvas',
101
+ * 'otel'
102
+ * );
103
+ *
104
+ * // Get all instrumentation files
105
+ * const instrumentationFiles = manifest.byRole.instrumentation;
106
+ *
107
+ * // Find which nodes reference a specific file
108
+ * const nodeIds = manifest.fileToNodes.get('src/api/checkout.ts');
109
+ *
110
+ * // Get all files for a specific node
111
+ * const files = manifest.nodeToFiles.get('node-123');
112
+ * ```
113
+ */
114
+ export function buildCanvasFileManifest(
115
+ canvas: ExtendedCanvas,
116
+ canvasId: string,
117
+ canvasPath: string,
118
+ canvasType: CanvasType = 'otel'
119
+ ): CanvasFileManifest {
120
+ const fileMap = new Map<string, CanvasFile>();
121
+ const fileToNodes = new Map<string, string[]>();
122
+ const nodeToFiles = new Map<string, string[]>();
123
+ const eventToFiles = new Map<string, string[]>();
124
+ const eventNames = new Set<string>();
125
+
126
+ let nodesWithFiles = 0;
127
+ let nodesWithoutFiles = 0;
128
+
129
+ for (const node of canvas.nodes ?? []) {
130
+ const nodeFiles = extractNodeFiles(node);
131
+
132
+ if (nodeFiles.length > 0) {
133
+ nodesWithFiles++;
134
+ nodeToFiles.set(
135
+ node.id,
136
+ nodeFiles.map((f) => f.path)
137
+ );
138
+ } else {
139
+ nodesWithoutFiles++;
140
+ }
141
+
142
+ for (const { path, role, origin, eventName } of nodeFiles) {
143
+ // Track event names
144
+ if (eventName) {
145
+ eventNames.add(eventName);
146
+ const existingEventFiles = eventToFiles.get(eventName) ?? [];
147
+ if (!existingEventFiles.includes(path)) {
148
+ existingEventFiles.push(path);
149
+ eventToFiles.set(eventName, existingEventFiles);
150
+ }
151
+ }
152
+
153
+ // Update fileToNodes mapping
154
+ const existingNodes = fileToNodes.get(path) ?? [];
155
+ if (!existingNodes.includes(node.id)) {
156
+ existingNodes.push(node.id);
157
+ fileToNodes.set(path, existingNodes);
158
+ }
159
+
160
+ // Update or create CanvasFile entry
161
+ const existing = fileMap.get(path);
162
+ if (existing) {
163
+ if (!existing.nodeIds.includes(node.id)) {
164
+ existing.nodeIds.push(node.id);
165
+ }
166
+ if (!existing.nodeTypes.includes(node.type)) {
167
+ existing.nodeTypes.push(node.type);
168
+ }
169
+ if (eventName && !existing.eventNames.includes(eventName)) {
170
+ existing.eventNames.push(eventName);
171
+ }
172
+ // Role priority: instrumentation > root-span > reference
173
+ const rolePriority: Record<CanvasFileRole, number> = {
174
+ instrumentation: 3,
175
+ 'root-span': 2,
176
+ reference: 1,
177
+ };
178
+ if (rolePriority[role] > rolePriority[existing.role]) {
179
+ existing.role = role;
180
+ }
181
+ } else {
182
+ fileMap.set(path, {
183
+ path,
184
+ role,
185
+ origin,
186
+ level: 'canvas-node',
187
+ nodeIds: [node.id],
188
+ nodeTypes: [node.type],
189
+ workflowIds: [],
190
+ eventNames: eventName ? [eventName] : [],
191
+ });
192
+ }
193
+ }
194
+ }
195
+
196
+ const files = Array.from(fileMap.values());
197
+
198
+ // Build byRole lookup
199
+ const byRole: Record<CanvasFileRole, CanvasFile[]> = {
200
+ instrumentation: files.filter((f) => f.role === 'instrumentation'),
201
+ reference: files.filter((f) => f.role === 'reference'),
202
+ 'root-span': files.filter((f) => f.role === 'root-span'),
203
+ };
204
+
205
+ // Build byOrigin lookup
206
+ const byOrigin: Record<CanvasFileOrigin, CanvasFile[]> = {
207
+ internal: files.filter((f) => f.origin === 'internal'),
208
+ external: files.filter((f) => f.origin === 'external'),
209
+ };
210
+
211
+ return {
212
+ canvasId,
213
+ canvasPath,
214
+ canvasType,
215
+ files,
216
+ byRole,
217
+ byOrigin,
218
+ fileToNodes,
219
+ nodeToFiles,
220
+ eventToFiles,
221
+ stats: {
222
+ totalFiles: files.length,
223
+ internalFiles: byOrigin.internal.length,
224
+ externalFiles: byOrigin.external.length,
225
+ instrumentationFiles: byRole.instrumentation.length,
226
+ referenceFiles: byRole.reference.length,
227
+ nodesWithFiles,
228
+ nodesWithoutFiles,
229
+ uniqueEventNames: eventNames.size,
230
+ },
231
+ };
232
+ }
233
+
234
+ // =============================================================================
235
+ // Workflow Manifest Builder
236
+ // =============================================================================
237
+
238
+ /**
239
+ * Get files relevant to a specific scenario
240
+ */
241
+ function getFilesForScenario(
242
+ scenario: WorkflowScenario,
243
+ canvasManifest: CanvasFileManifest,
244
+ workflowFiles: CanvasFile[]
245
+ ): CanvasFile[] {
246
+ const files: CanvasFile[] = [];
247
+
248
+ // Always include workflow-level files (root span)
249
+ files.push(...workflowFiles);
250
+
251
+ // Get event names from scenario template
252
+ const eventNames = Object.keys(scenario.template.events ?? {});
253
+
254
+ // Find files for each event
255
+ for (const eventName of eventNames) {
256
+ const eventFiles = canvasManifest.eventToFiles.get(eventName) ?? [];
257
+ for (const filePath of eventFiles) {
258
+ const canvasFile = canvasManifest.files.find((f) => f.path === filePath);
259
+ if (canvasFile && !files.some((f) => f.path === filePath)) {
260
+ files.push(canvasFile);
261
+ }
262
+ }
263
+ }
264
+
265
+ return files;
266
+ }
267
+
268
+ /**
269
+ * Build a file manifest for a workflow (includes canvas files)
270
+ *
271
+ * @param canvasManifest - The canvas manifest to extend
272
+ * @param workflow - The workflow template
273
+ * @param workflowId - Workflow identifier
274
+ * @param workflowPath - Workflow file path
275
+ * @returns Workflow file manifest with scenario filtering
276
+ *
277
+ * @example
278
+ * ```typescript
279
+ * const workflowManifest = buildWorkflowFileManifest(
280
+ * canvasManifest,
281
+ * workflow,
282
+ * 'checkout-workflow',
283
+ * '.principal-views/checkout/checkout.workflow.json'
284
+ * );
285
+ *
286
+ * // Get files for a specific scenario
287
+ * const scenarioFiles = workflowManifest.byScenario.get('success-scenario');
288
+ * ```
289
+ */
290
+ export function buildWorkflowFileManifest(
291
+ canvasManifest: CanvasFileManifest,
292
+ workflow: WorkflowTemplate,
293
+ workflowId: string,
294
+ workflowPath: string
295
+ ): WorkflowFileManifest {
296
+ // Extract workflow-level files
297
+ const workflowFiles: CanvasFile[] = (workflow.files ?? []).map((path) => ({
298
+ path,
299
+ role: 'root-span' as CanvasFileRole,
300
+ origin: 'internal' as CanvasFileOrigin,
301
+ level: 'workflow' as const,
302
+ nodeIds: [],
303
+ nodeTypes: [],
304
+ workflowIds: [workflowId],
305
+ eventNames: [],
306
+ }));
307
+
308
+ // Merge canvas files with workflow files
309
+ const allFilesMap = new Map<string, CanvasFile>();
310
+
311
+ // Add canvas files first
312
+ for (const file of canvasManifest.files) {
313
+ allFilesMap.set(file.path, { ...file });
314
+ }
315
+
316
+ // Add/merge workflow files
317
+ for (const file of workflowFiles) {
318
+ const existing = allFilesMap.get(file.path);
319
+ if (existing) {
320
+ existing.workflowIds.push(workflowId);
321
+ // Upgrade role if workflow-level
322
+ if (file.role === 'root-span') {
323
+ existing.role = 'root-span';
324
+ existing.level = 'workflow';
325
+ }
326
+ } else {
327
+ allFilesMap.set(file.path, file);
328
+ }
329
+ }
330
+
331
+ const allFiles = Array.from(allFilesMap.values());
332
+
333
+ // Build scenario -> files mapping
334
+ const byScenario = new Map<string, CanvasFile[]>();
335
+ for (const scenario of workflow.scenarios) {
336
+ const scenarioFiles = getFilesForScenario(scenario, canvasManifest, workflowFiles);
337
+ byScenario.set(scenario.id, scenarioFiles);
338
+ }
339
+
340
+ return {
341
+ ...canvasManifest,
342
+ workflowId,
343
+ workflowPath,
344
+ rootSpan: workflow.rootSpan ?? workflow.spanPattern,
345
+ workflowFiles,
346
+ allFiles,
347
+ byScenario,
348
+ };
349
+ }
350
+
351
+ // =============================================================================
352
+ // Storyboard Manifest Builder
353
+ // =============================================================================
354
+
355
+ /**
356
+ * Build a file manifest for an entire storyboard
357
+ *
358
+ * @param canvasManifest - The main canvas manifest
359
+ * @param workflowManifests - Array of workflow manifests
360
+ * @param storyboardId - Storyboard identifier
361
+ * @param storyboardPath - Storyboard folder path
362
+ * @returns Storyboard file manifest aggregating all files
363
+ *
364
+ * @example
365
+ * ```typescript
366
+ * const storyboardManifest = buildStoryboardFileManifest(
367
+ * canvasManifest,
368
+ * workflowManifests,
369
+ * 'checkout-flow',
370
+ * '.principal-views/checkout-flow'
371
+ * );
372
+ *
373
+ * // Get all files across canvas and workflows
374
+ * const allFiles = storyboardManifest.allFiles;
375
+ * ```
376
+ */
377
+ export function buildStoryboardFileManifest(
378
+ canvasManifest: CanvasFileManifest,
379
+ workflowManifests: WorkflowFileManifest[],
380
+ storyboardId: string,
381
+ storyboardPath: string
382
+ ): StoryboardFileManifest {
383
+ // Merge all files across canvas and workflows
384
+ const allFilesMap = new Map<string, CanvasFile>();
385
+
386
+ for (const file of canvasManifest.files) {
387
+ allFilesMap.set(file.path, { ...file });
388
+ }
389
+
390
+ for (const workflow of workflowManifests) {
391
+ for (const file of workflow.workflowFiles) {
392
+ const existing = allFilesMap.get(file.path);
393
+ if (existing) {
394
+ existing.workflowIds.push(...file.workflowIds);
395
+ } else {
396
+ allFilesMap.set(file.path, { ...file });
397
+ }
398
+ }
399
+ }
400
+
401
+ const allFiles = Array.from(allFilesMap.values());
402
+
403
+ // Count scenarios across all workflows
404
+ const scenarioCount = workflowManifests.reduce((sum, w) => sum + w.byScenario.size, 0);
405
+
406
+ return {
407
+ storyboardId,
408
+ storyboardPath,
409
+ canvas: canvasManifest,
410
+ workflows: workflowManifests,
411
+ allFiles,
412
+ stats: {
413
+ ...canvasManifest.stats,
414
+ workflowCount: workflowManifests.length,
415
+ scenarioCount,
416
+ rootSpanFiles: allFiles.filter((f) => f.role === 'root-span').length,
417
+ },
418
+ };
419
+ }
@@ -208,3 +208,179 @@ export interface CanvasDiscoveryResultWithContent {
208
208
  errors: Array<{ path: string; error: string }>;
209
209
  warnings: Array<{ path: string; message: string; type: 'deprecation' }>;
210
210
  }
211
+
212
+ // =============================================================================
213
+ // Canvas File Manifest Types
214
+ // =============================================================================
215
+
216
+ /**
217
+ * Classification of how a file relates to a canvas/workflow
218
+ */
219
+ export type CanvasFileRole =
220
+ | 'instrumentation' // File contains OTEL instrumentation (from otel.files)
221
+ | 'reference' // External reference/documentation (from otel.references)
222
+ | 'root-span'; // Root span instrumentation (from workflow.files)
223
+
224
+ /**
225
+ * Origin of the file - internal to repo or external
226
+ */
227
+ export type CanvasFileOrigin = 'internal' | 'external';
228
+
229
+ /**
230
+ * Source level where the file was defined
231
+ */
232
+ export type CanvasFileLevel = 'canvas-node' | 'workflow';
233
+
234
+ /**
235
+ * A file referenced by a canvas, workflow, or scenario
236
+ */
237
+ export interface CanvasFile {
238
+ /** File path (relative to repo root for internal, URL/package ref for external) */
239
+ path: string;
240
+
241
+ /** How this file relates to the canvas */
242
+ role: CanvasFileRole;
243
+
244
+ /** Whether the file is in this repo or external */
245
+ origin: CanvasFileOrigin;
246
+
247
+ /** Where this file reference was defined */
248
+ level: CanvasFileLevel;
249
+
250
+ /** IDs of nodes that reference this file (for canvas-node level) */
251
+ nodeIds: string[];
252
+
253
+ /** Node types that reference this file */
254
+ nodeTypes: string[];
255
+
256
+ /** Workflow IDs that reference this file (for workflow level) */
257
+ workflowIds: string[];
258
+
259
+ /** Event names associated with this file's nodes */
260
+ eventNames: string[];
261
+ }
262
+
263
+ /**
264
+ * Canvas-level statistics
265
+ */
266
+ export interface CanvasFileStats {
267
+ totalFiles: number;
268
+ internalFiles: number;
269
+ externalFiles: number;
270
+ instrumentationFiles: number;
271
+ referenceFiles: number;
272
+ nodesWithFiles: number;
273
+ nodesWithoutFiles: number;
274
+ uniqueEventNames: number;
275
+ }
276
+
277
+ /**
278
+ * Storyboard-level statistics
279
+ */
280
+ export interface StoryboardFileStats extends CanvasFileStats {
281
+ workflowCount: number;
282
+ scenarioCount: number;
283
+ rootSpanFiles: number;
284
+ }
285
+
286
+ /**
287
+ * File manifest for a single canvas (without workflows)
288
+ */
289
+ export interface CanvasFileManifest {
290
+ /** Canvas ID */
291
+ canvasId: string;
292
+
293
+ /** Canvas path */
294
+ canvasPath: string;
295
+
296
+ /** Canvas type */
297
+ canvasType: CanvasType;
298
+
299
+ /** All files from canvas nodes, deduplicated */
300
+ files: CanvasFile[];
301
+
302
+ /** Quick lookup by role */
303
+ byRole: Record<CanvasFileRole, CanvasFile[]>;
304
+
305
+ /** Quick lookup by origin */
306
+ byOrigin: Record<CanvasFileOrigin, CanvasFile[]>;
307
+
308
+ /** Reverse mapping: file path -> node IDs */
309
+ fileToNodes: Map<string, string[]>;
310
+
311
+ /** Reverse mapping: node ID -> file paths */
312
+ nodeToFiles: Map<string, string[]>;
313
+
314
+ /** Reverse mapping: event name -> file paths */
315
+ eventToFiles: Map<string, string[]>;
316
+
317
+ /** Summary statistics */
318
+ stats: CanvasFileStats;
319
+ }
320
+
321
+ /**
322
+ * File manifest for a workflow (includes canvas + workflow-level files)
323
+ */
324
+ export interface WorkflowFileManifest extends CanvasFileManifest {
325
+ /** Workflow ID */
326
+ workflowId: string;
327
+
328
+ /** Workflow path */
329
+ workflowPath: string;
330
+
331
+ /** Root span name */
332
+ rootSpan?: string;
333
+
334
+ /** Files from workflow.files property */
335
+ workflowFiles: CanvasFile[];
336
+
337
+ /** Merged files (canvas + workflow) */
338
+ allFiles: CanvasFile[];
339
+
340
+ /** Files filtered by scenario (scenario ID -> files) */
341
+ byScenario: Map<string, CanvasFile[]>;
342
+ }
343
+
344
+ /**
345
+ * File manifest for an entire storyboard
346
+ */
347
+ export interface StoryboardFileManifest {
348
+ /** Storyboard ID */
349
+ storyboardId: string;
350
+
351
+ /** Storyboard path */
352
+ storyboardPath: string;
353
+
354
+ /** Main canvas manifest */
355
+ canvas: CanvasFileManifest;
356
+
357
+ /** Workflow manifests */
358
+ workflows: WorkflowFileManifest[];
359
+
360
+ /** All files across canvas and all workflows */
361
+ allFiles: CanvasFile[];
362
+
363
+ /** Summary statistics */
364
+ stats: StoryboardFileStats;
365
+ }
366
+
367
+ /**
368
+ * Discovered canvas with content AND file manifest
369
+ */
370
+ export interface DiscoveredCanvasWithManifest extends DiscoveredCanvasWithContent {
371
+ manifest: CanvasFileManifest;
372
+ }
373
+
374
+ /**
375
+ * Discovered workflow with content AND file manifest
376
+ */
377
+ export interface DiscoveredWorkflowWithManifest extends DiscoveredWorkflowWithContent {
378
+ manifest: WorkflowFileManifest;
379
+ }
380
+
381
+ /**
382
+ * Discovered storyboard with content AND file manifest
383
+ */
384
+ export interface DiscoveredStoryboardWithManifest extends DiscoveredStoryboardWithContent {
385
+ manifest: StoryboardFileManifest;
386
+ }
package/src/index.ts CHANGED
@@ -282,8 +282,28 @@ export type {
282
282
  DiscoveredWorkflowWithContent,
283
283
  DiscoveredStoryboardWithContent,
284
284
  CanvasDiscoveryResultWithContent,
285
+ // File manifest types
286
+ CanvasFileRole,
287
+ CanvasFileOrigin,
288
+ CanvasFileLevel,
289
+ CanvasFile,
290
+ CanvasFileStats,
291
+ StoryboardFileStats,
292
+ CanvasFileManifest,
293
+ WorkflowFileManifest,
294
+ StoryboardFileManifest,
295
+ DiscoveredCanvasWithManifest,
296
+ DiscoveredWorkflowWithManifest,
297
+ DiscoveredStoryboardWithManifest,
285
298
  } from './discovery/types';
286
299
 
300
+ // Export file manifest builders (browser-safe)
301
+ export {
302
+ buildCanvasFileManifest,
303
+ buildWorkflowFileManifest,
304
+ buildStoryboardFileManifest,
305
+ } from './discovery/CanvasFileManifest';
306
+
287
307
  // Export scopes module (canvas validation + scope utilities)
288
308
  export {
289
309
  ScopesCanvasValidator,
@@ -403,7 +423,6 @@ export type {
403
423
  } from './storyboard/index';
404
424
  export {
405
425
  buildStoryboardContext,
406
- buildNodeSourcesMap,
407
426
  buildEventNodeMap,
408
427
  getNodeEventName,
409
428
  resolveScenarioNodeIds,
@@ -13,6 +13,8 @@ import type {
13
13
  ScenarioReference,
14
14
  StoryboardContextSliceData,
15
15
  } from './types';
16
+ import type { CanvasType } from '../discovery/types';
17
+ import { buildCanvasFileManifest } from '../discovery/CanvasFileManifest';
16
18
 
17
19
  // ============================================================================
18
20
  // Types
@@ -33,6 +35,9 @@ export interface BuildStoryboardContextOptions {
33
35
  /** Storyboard metadata (id, name, path) */
34
36
  storyboard: StoryboardReference;
35
37
 
38
+ /** Canvas type (defaults to 'otel') */
39
+ canvasType?: CanvasType;
40
+
36
41
  /** Optional: Selected workflow template and its path */
37
42
  workflow?: {
38
43
  template: WorkflowTemplate;
@@ -50,37 +55,6 @@ export interface BuildStoryboardContextOptions {
50
55
  // Core Functions
51
56
  // ============================================================================
52
57
 
53
- /**
54
- * Build a map of canvas node ID to reference paths.
55
- * Extracts `pv.references` (or deprecated `pv.sources`) from each node.
56
- *
57
- * @param canvas - The extended canvas with nodes
58
- * @returns Record mapping node IDs to their reference paths
59
- *
60
- * @example
61
- * ```typescript
62
- * const nodeSources = buildNodeSourcesMap(canvas);
63
- * // { 'node-abc': ['src/auth/login.ts'], 'node-xyz': ['@logfire/pydantic-ai'] }
64
- * ```
65
- */
66
- export function buildNodeSourcesMap(canvas: ExtendedCanvas): Record<string, string[]> {
67
- const nodeSources: Record<string, string[]> = {};
68
-
69
- if (!canvas.nodes) {
70
- return nodeSources;
71
- }
72
-
73
- for (const node of canvas.nodes) {
74
- // Prefer references, fall back to deprecated sources
75
- const refs = node.pv?.references ?? node.pv?.sources;
76
- if (refs && refs.length > 0) {
77
- nodeSources[node.id] = [...refs];
78
- }
79
- }
80
-
81
- return nodeSources;
82
- }
83
-
84
58
  /**
85
59
  * Build a map of event name to canvas node IDs.
86
60
  * Uses `pv.event.name` or `pv.eventRef` from each node.
@@ -267,10 +241,10 @@ export function getAllNodeIds(canvas: ExtendedCanvas): string[] {
267
241
  export function buildStoryboardContext(
268
242
  options: BuildStoryboardContextOptions
269
243
  ): StoryboardContextSliceData {
270
- const { canvas, storyboard, workflow, scenario, additionalSupportingFiles = [] } = options;
244
+ const { canvas, storyboard, canvasType = 'otel', workflow, scenario, additionalSupportingFiles = [] } = options;
271
245
 
272
- // Build node sources map from canvas
273
- const nodeSources = buildNodeSourcesMap(canvas);
246
+ // Build canvas file manifest
247
+ const manifest = buildCanvasFileManifest(canvas, storyboard.id, storyboard.path, canvasType);
274
248
 
275
249
  // Build supporting files list
276
250
  const supportingFiles: string[] = [storyboard.path];
@@ -310,7 +284,7 @@ export function buildStoryboardContext(
310
284
  storyboard,
311
285
  workflow: workflowReference,
312
286
  scenario: scenarioReference,
313
- nodeSources,
287
+ manifest,
314
288
  supportingFiles,
315
289
  };
316
290
  }