@zenwave360/vistas-eventflow 0.1.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 (42) hide show
  1. package/FLOWVIEWMODEL_EXAMPLES.md +896 -0
  2. package/LICENSE +21 -0
  3. package/README.md +151 -0
  4. package/dist/EventFlowVisualizer.d.ts +25 -0
  5. package/dist/EventFlowVisualizer.d.ts.map +1 -0
  6. package/dist/ServiceMapVisualizer.d.ts +14 -0
  7. package/dist/ServiceMapVisualizer.d.ts.map +1 -0
  8. package/dist/components/CausationEdge.d.ts +8 -0
  9. package/dist/components/CausationEdge.d.ts.map +1 -0
  10. package/dist/components/CommandNode.d.ts +10 -0
  11. package/dist/components/CommandNode.d.ts.map +1 -0
  12. package/dist/components/EndNode.d.ts +10 -0
  13. package/dist/components/EndNode.d.ts.map +1 -0
  14. package/dist/components/EventNode.d.ts +10 -0
  15. package/dist/components/EventNode.d.ts.map +1 -0
  16. package/dist/components/OutcomeNode.d.ts +7 -0
  17. package/dist/components/OutcomeNode.d.ts.map +1 -0
  18. package/dist/components/PolicyNode.d.ts +16 -0
  19. package/dist/components/PolicyNode.d.ts.map +1 -0
  20. package/dist/components/ServiceGroupNode.d.ts +10 -0
  21. package/dist/components/ServiceGroupNode.d.ts.map +1 -0
  22. package/dist/components/ServiceMapNode.d.ts +8 -0
  23. package/dist/components/ServiceMapNode.d.ts.map +1 -0
  24. package/dist/components/StartNode.d.ts +10 -0
  25. package/dist/components/StartNode.d.ts.map +1 -0
  26. package/dist/components/SystemGroupNode.d.ts +7 -0
  27. package/dist/components/SystemGroupNode.d.ts.map +1 -0
  28. package/dist/index.d.ts +29 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +645 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/labelFormatting.d.ts +6 -0
  33. package/dist/labelFormatting.d.ts.map +1 -0
  34. package/dist/styles.css +1 -0
  35. package/dist/types/FlowViewModel-updated.d.ts +114 -0
  36. package/dist/types/FlowViewModel-updated.d.ts.map +1 -0
  37. package/dist/types/FlowViewModel.d.ts +124 -0
  38. package/dist/types/FlowViewModel.d.ts.map +1 -0
  39. package/dist/types/ServiceViewModel.d.ts +64 -0
  40. package/dist/types/ServiceViewModel.d.ts.map +1 -0
  41. package/event-flow-architecture-and-contracts.md +1067 -0
  42. package/package.json +54 -0
@@ -0,0 +1,1067 @@
1
+ # ZFL Event Flow Renderer - Architecture and Contracts
2
+
3
+ ## 1. Purpose
4
+
5
+ The ZFL Event Flow renderer is the visualization layer for event-flow diagrams in the ZFL ecosystem.
6
+
7
+ Its job is to take a pre-built `FlowViewModel` JSON document and render it as an interactive graph in a browser or embedded webview. It is intentionally downstream from parsing and semantic analysis.
8
+
9
+ In practical terms, the renderer exists to let a host such as the IntelliJ plugin:
10
+
11
+ - display event-flow topology
12
+ - let users inspect nodes and edges
13
+ - let users click graph elements and map them back to source locations
14
+ - optionally let users drag nodes and persist updated layout metadata
15
+
16
+ The renderer is not the source of truth for ZFL semantics. The source of truth remains the parser/language side that produces the graph model.
17
+
18
+ ## 2. Scope of responsibility
19
+
20
+ ### What the renderer owns
21
+
22
+ - Rendering a `FlowViewModel` into a node/edge graph
23
+ - Visual mapping of ZFL event-flow concepts to graph shapes and colors
24
+ - Local interaction inside the canvas
25
+ - Emitting UI-level interaction callbacks
26
+ - Returning updated node positions when drag-based editing is enabled
27
+ - Tolerating partially laid-out models by skipping nodes without layout coordinates
28
+
29
+ ### What the renderer does not own
30
+
31
+ - Parsing `.zfl` source text
32
+ - Resolving imports, models, or semantic correctness
33
+ - Building the event-flow graph from source code
34
+ - Computing graph layout
35
+ - Source navigation policy
36
+ - Host-side message transport
37
+ - Persistence of layout or selection state
38
+
39
+ ### Boundary with parser
40
+
41
+ The parser owns source-to-model transformation. It decides what nodes and edges exist, their semantic type, labels, IDs, and source references.
42
+
43
+ The renderer assumes that parsing is already complete and consumes JSON only.
44
+
45
+ ### Boundary with language server
46
+
47
+ The language server owns project awareness, file resolution, diagnostics, symbol/source navigation, and any server-side generation of `FlowViewModel`.
48
+
49
+ The renderer does not call the language server directly. A host must fetch the model from the language server and pass it into the renderer.
50
+
51
+ ### Boundary with IntelliJ plugin
52
+
53
+ The IntelliJ plugin owns embedding, message transport, model loading, host commands, selection synchronization with other IntelliJ UI, error presentation outside the canvas, and persistence.
54
+
55
+ The renderer owns only the in-webview graph UI.
56
+
57
+ ## 3. Technology stack
58
+
59
+ ### Core stack
60
+
61
+ - React 18
62
+ - TypeScript 5
63
+ - React Flow 11
64
+ - Vite 7 for dev/build
65
+
66
+ ### Rendering model
67
+
68
+ The renderer is a React component named `EventFlowVisualizer`.
69
+
70
+ It receives a full `FlowViewModel` object as props and transforms it into React Flow nodes and edges. Rendering is declarative: whenever the host replaces `flowViewModel`, the graph is re-derived from that new model.
71
+
72
+ There is no internal automatic layout engine. The renderer expects absolute node positions and dimensions to already be present when the host wants a visible graph.
73
+
74
+ ### Embedding assumptions
75
+
76
+ The current implementation is a React component library, not a standalone protocol-driven web app.
77
+
78
+ That means:
79
+
80
+ - the implemented public contract today is the component prop/callback API
81
+ - a host-facing message bridge must be supplied by the embedding layer
82
+ - the IntelliJ plugin should treat the renderer as a UI component wrapped by a small adapter
83
+
84
+ ### Browser/webview assumptions
85
+
86
+ - Runs in a DOM environment
87
+ - Requires standard browser APIs used by React, React DOM, and React Flow
88
+ - Assumes pointer/mouse interaction is available
89
+ - Assumes the host gives the root container a real width and height
90
+ - Assumes modern JavaScript support roughly aligned with `ES2020`
91
+
92
+ For JCEF/IntelliJ, the host should provide a container that fills the available panel space and avoid mounting the renderer into a zero-height root.
93
+
94
+ ## 4. Input contract
95
+
96
+ ### Canonical top-level shape
97
+
98
+ The renderer consumes a `FlowViewModel` object with this shape:
99
+
100
+ ```json
101
+ {
102
+ "schema": "zfl.eventflow.view@1",
103
+ "nodes": [],
104
+ "edges": [],
105
+ "layout": null,
106
+ "bounds": null,
107
+ "systemGroups": null
108
+ }
109
+ ```
110
+
111
+ ### Top-level fields
112
+
113
+ - `schema: string`
114
+ Must currently be `zfl.eventflow.view@1`.
115
+ - `nodes: FlowNode[]`
116
+ Required. May be empty.
117
+ - `edges: FlowEdge[]`
118
+ Required. May be empty.
119
+ - `layout: LayoutMetadata | null`
120
+ Optional in practice, but expected for a fully laid-out graph.
121
+ - `bounds: FlowBounds | null`
122
+ Optional in practice. Used as metadata only.
123
+ - `systemGroups: FlowSystemGroupView[] | null`
124
+ Present in the schema but ignored by the current renderer.
125
+
126
+ ### Node fields
127
+
128
+ Each node has this shape:
129
+
130
+ ```json
131
+ {
132
+ "id": "command:renewSubscription",
133
+ "type": "COMMAND",
134
+ "label": "renewSubscription",
135
+ "system": "Subscription",
136
+ "service": "SubscriptionService",
137
+ "sourceRef": {
138
+ "file": "subscription-flow.zfl",
139
+ "line": 3,
140
+ "column": 1
141
+ },
142
+ "position": {
143
+ "x": 220,
144
+ "y": 160
145
+ },
146
+ "dimensions": {
147
+ "width": 180,
148
+ "height": 64
149
+ }
150
+ }
151
+ ```
152
+
153
+ Required node fields:
154
+
155
+ - `id: string`
156
+ Unique in the graph. Edges reference this ID.
157
+ - `type: "START" | "COMMAND" | "EVENT" | "POLICY" | "END"`
158
+ - `label: string`
159
+ - `system: string | null`
160
+ - `service: string | null`
161
+ - `sourceRef: SourceRef`
162
+ - `position: { x: number, y: number } | null`
163
+ - `dimensions: { width: number, height: number } | null`
164
+
165
+ ### Edge fields
166
+
167
+ Each edge has this shape:
168
+
169
+ ```json
170
+ {
171
+ "id": "e4",
172
+ "source": "policy:SubscriptionRenewed:chargePayment",
173
+ "target": "command:chargePayment",
174
+ "type": "TRIGGER",
175
+ "label": null,
176
+ "sourceRef": null
177
+ }
178
+ ```
179
+
180
+ Required edge fields:
181
+
182
+ - `id: string`
183
+ - `source: string`
184
+ Must match a node ID.
185
+ - `target: string`
186
+ Must match a node ID.
187
+ - `type: "CAUSATION" | "TRIGGER" | "CONDITIONAL" | "ERROR"`
188
+ - `label: string | null`
189
+ - `sourceRef: SourceRef | null`
190
+
191
+ ### Node type/category expectations
192
+
193
+ The current visual mapping is fixed:
194
+
195
+ - `START`: start node styling
196
+ - `COMMAND`: command styling
197
+ - `EVENT`: event styling
198
+ - `POLICY`: policy styling
199
+ - `END`: end-state styling
200
+
201
+ Unknown node types are not part of the contract. Hosts should not send them.
202
+
203
+ ### Styling/category expectations
204
+
205
+ The renderer derives styling from semantic types, not from ad hoc style fields in the JSON.
206
+
207
+ Current expectations:
208
+
209
+ - node shape and color come from `node.type`
210
+ - edge color and dash pattern come from `edge.type`
211
+ - `edge.label` is rendered if non-null
212
+ - `system` and `service` are used as tooltip/title metadata, not swimlanes
213
+ - policy labels may be visually split into `when ... do ...` segments if they match that text pattern
214
+ - labels may be humanized for display only; this does not change the underlying JSON
215
+
216
+ There is currently no contract for per-node custom colors, icons, CSS classes, or arbitrary metadata blobs.
217
+
218
+ ### Required source metadata
219
+
220
+ `sourceRef` is required on every node and should be treated as required for integration.
221
+
222
+ Shape:
223
+
224
+ ```json
225
+ {
226
+ "file": "subscription-flow.zfl",
227
+ "line": 7,
228
+ "column": 1
229
+ }
230
+ ```
231
+
232
+ Semantics:
233
+
234
+ - `file` is a host-resolvable path or file identifier
235
+ - `line` is 1-based
236
+ - `column` is 1-based
237
+
238
+ Edge `sourceRef` may be `null`.
239
+
240
+ ### Optional layout metadata
241
+
242
+ Top-level `layout`:
243
+
244
+ ```json
245
+ {
246
+ "engine": "elk-layered",
247
+ "direction": "LR",
248
+ "rankSpacing": 180,
249
+ "nodeSpacing": 80
250
+ }
251
+ ```
252
+
253
+ Top-level `bounds`:
254
+
255
+ ```json
256
+ {
257
+ "x": 0,
258
+ "y": 0,
259
+ "width": 2200,
260
+ "height": 420
261
+ }
262
+ ```
263
+
264
+ Current behavior:
265
+
266
+ - if a node is missing `position` or `dimensions`, that node is skipped
267
+ - edges whose source or target node is skipped are also skipped
268
+ - `systemGroups` is ignored
269
+
270
+ ### Example input JSON
271
+
272
+ ```json
273
+ {
274
+ "schema": "zfl.eventflow.view@1",
275
+ "nodes": [
276
+ {
277
+ "id": "event:CustomerRequestsSubscriptionRenewal",
278
+ "type": "START",
279
+ "label": "CustomerRequestsSubscriptionRenewal",
280
+ "system": null,
281
+ "service": null,
282
+ "sourceRef": {
283
+ "file": "subscription-flow.zfl",
284
+ "line": 1,
285
+ "column": 1
286
+ },
287
+ "position": {
288
+ "x": 40,
289
+ "y": 140
290
+ },
291
+ "dimensions": {
292
+ "width": 100,
293
+ "height": 100
294
+ }
295
+ },
296
+ {
297
+ "id": "command:renewSubscription",
298
+ "type": "COMMAND",
299
+ "label": "renewSubscription",
300
+ "system": "Subscription",
301
+ "service": "SubscriptionService",
302
+ "sourceRef": {
303
+ "file": "subscription-flow.zfl",
304
+ "line": 3,
305
+ "column": 1
306
+ },
307
+ "position": {
308
+ "x": 220,
309
+ "y": 160
310
+ },
311
+ "dimensions": {
312
+ "width": 180,
313
+ "height": 64
314
+ }
315
+ }
316
+ ],
317
+ "edges": [
318
+ {
319
+ "id": "e1",
320
+ "source": "event:CustomerRequestsSubscriptionRenewal",
321
+ "target": "command:renewSubscription",
322
+ "type": "TRIGGER",
323
+ "label": null,
324
+ "sourceRef": null
325
+ }
326
+ ],
327
+ "layout": {
328
+ "engine": "elk-layered",
329
+ "direction": "LR",
330
+ "rankSpacing": 180,
331
+ "nodeSpacing": 80
332
+ },
333
+ "bounds": {
334
+ "x": 0,
335
+ "y": 0,
336
+ "width": 1200,
337
+ "height": 400
338
+ },
339
+ "systemGroups": null
340
+ }
341
+ ```
342
+
343
+ ## 5. Event/output contract
344
+
345
+ ### Important distinction
346
+
347
+ Current implementation:
348
+
349
+ - implemented callbacks are `onNodeClick`, `onSelectionChange`, and `onFlowViewModelChange`
350
+ - there is no built-in `window.postMessage` bridge
351
+ - there is no implemented `webviewReady`, `viewportChanged`, `contextMenuRequested`, or `errorReported` event
352
+
353
+ Recommended host contract:
354
+
355
+ For IntelliJ integration, use a thin wrapper around `EventFlowVisualizer` and normalize its callbacks into explicit host events. The event definitions below are the recommended bridge contract for the embedding host even where the base renderer does not emit them yet.
356
+
357
+ ### Event envelope
358
+
359
+ Renderer-to-host events should use a stable envelope:
360
+
361
+ ```json
362
+ {
363
+ "channel": "zfl.eventflow",
364
+ "version": 1,
365
+ "type": "nodeSelected",
366
+ "requestId": null,
367
+ "timestamp": "2026-03-14T12:00:00.000Z",
368
+ "payload": {}
369
+ }
370
+ ```
371
+
372
+ Required envelope fields:
373
+
374
+ - `channel: "zfl.eventflow"`
375
+ - `version: 1`
376
+ - `type: string`
377
+ - `payload: object`
378
+
379
+ Optional envelope fields:
380
+
381
+ - `requestId: string | null`
382
+ - `timestamp: string`
383
+
384
+ ### `webviewReady`
385
+
386
+ Status: recommended bridge event, not implemented by the base component.
387
+
388
+ Trigger:
389
+
390
+ - emitted once after the wrapper has mounted and is ready to receive host messages
391
+
392
+ Payload required fields:
393
+
394
+ - `rendererVersion: string`
395
+ - `supportedIncomingMessages: string[]`
396
+ - `supportedOutgoingEvents: string[]`
397
+
398
+ Payload optional fields:
399
+
400
+ - `capabilities: object`
401
+
402
+ Example:
403
+
404
+ ```json
405
+ {
406
+ "channel": "zfl.eventflow",
407
+ "version": 1,
408
+ "type": "webviewReady",
409
+ "payload": {
410
+ "rendererVersion": "1.0.0",
411
+ "supportedIncomingMessages": [
412
+ "loadModel",
413
+ "refreshModel",
414
+ "selectNodes",
415
+ "highlightNodes",
416
+ "fitToNodes",
417
+ "clearSelection",
418
+ "setTheme"
419
+ ],
420
+ "supportedOutgoingEvents": [
421
+ "webviewReady",
422
+ "nodeSelected",
423
+ "selectionChanged",
424
+ "layoutChanged",
425
+ "errorReported"
426
+ ],
427
+ "capabilities": {
428
+ "dragLayoutEditing": true,
429
+ "viewportCommands": false,
430
+ "contextMenu": false
431
+ }
432
+ }
433
+ }
434
+ ```
435
+
436
+ ### `nodeSelected`
437
+
438
+ Status: can be produced today by adapting `onNodeClick`.
439
+
440
+ Trigger:
441
+
442
+ - user clicks a node
443
+
444
+ Payload required fields:
445
+
446
+ - `nodeId: string`
447
+ - `nodeType: string`
448
+ - `sourceRef: SourceRef`
449
+
450
+ Payload optional fields:
451
+
452
+ - `label: string`
453
+ - `system: string | null`
454
+ - `service: string | null`
455
+
456
+ Example:
457
+
458
+ ```json
459
+ {
460
+ "channel": "zfl.eventflow",
461
+ "version": 1,
462
+ "type": "nodeSelected",
463
+ "payload": {
464
+ "nodeId": "command:renewSubscription",
465
+ "nodeType": "COMMAND",
466
+ "label": "renewSubscription",
467
+ "system": "Subscription",
468
+ "service": "SubscriptionService",
469
+ "sourceRef": {
470
+ "file": "subscription-flow.zfl",
471
+ "line": 3,
472
+ "column": 1
473
+ }
474
+ }
475
+ }
476
+ ```
477
+
478
+ ### `selectionChanged`
479
+
480
+ Status: can be produced today by adapting `onSelectionChange`.
481
+
482
+ Trigger:
483
+
484
+ - user changes selected nodes or edges in the canvas
485
+
486
+ Payload required fields:
487
+
488
+ - `selectedNodeIds: string[]`
489
+ - `selectedEdgeIds: string[]`
490
+
491
+ Payload optional fields:
492
+
493
+ - `primaryNodeId: string | null`
494
+
495
+ Example:
496
+
497
+ ```json
498
+ {
499
+ "channel": "zfl.eventflow",
500
+ "version": 1,
501
+ "type": "selectionChanged",
502
+ "payload": {
503
+ "selectedNodeIds": [
504
+ "command:renewSubscription"
505
+ ],
506
+ "selectedEdgeIds": [],
507
+ "primaryNodeId": "command:renewSubscription"
508
+ }
509
+ }
510
+ ```
511
+
512
+ ### `viewportChanged`
513
+
514
+ Status: not implemented by the base component.
515
+
516
+ Trigger:
517
+
518
+ - recommended for future use when the wrapper starts listening to React Flow viewport changes
519
+
520
+ Payload required fields:
521
+
522
+ - `x: number`
523
+ - `y: number`
524
+ - `zoom: number`
525
+
526
+ Payload optional fields:
527
+
528
+ - `reason: "user" | "programmatic"`
529
+
530
+ Example:
531
+
532
+ ```json
533
+ {
534
+ "channel": "zfl.eventflow",
535
+ "version": 1,
536
+ "type": "viewportChanged",
537
+ "payload": {
538
+ "x": 120,
539
+ "y": 40,
540
+ "zoom": 0.9,
541
+ "reason": "user"
542
+ }
543
+ }
544
+ ```
545
+
546
+ ### `contextMenuRequested`
547
+
548
+ Status: not implemented by the base component.
549
+
550
+ Trigger:
551
+
552
+ - recommended future event on right-click or equivalent context gesture
553
+
554
+ Payload required fields:
555
+
556
+ - `targetType: "node" | "edge" | "canvas"`
557
+ - `clientX: number`
558
+ - `clientY: number`
559
+
560
+ Payload optional fields:
561
+
562
+ - `nodeId: string`
563
+ - `edgeId: string`
564
+ - `sourceRef: SourceRef`
565
+
566
+ Example:
567
+
568
+ ```json
569
+ {
570
+ "channel": "zfl.eventflow",
571
+ "version": 1,
572
+ "type": "contextMenuRequested",
573
+ "payload": {
574
+ "targetType": "node",
575
+ "clientX": 522,
576
+ "clientY": 311,
577
+ "nodeId": "event:SubscriptionRenewed",
578
+ "sourceRef": {
579
+ "file": "subscription-flow.zfl",
580
+ "line": 5,
581
+ "column": 1
582
+ }
583
+ }
584
+ }
585
+ ```
586
+
587
+ ### `layoutChanged`
588
+
589
+ Status: can be produced today by adapting `onFlowViewModelChange`.
590
+
591
+ Trigger:
592
+
593
+ - user drags one or more nodes and the renderer emits an updated `FlowViewModel`
594
+
595
+ Payload required fields:
596
+
597
+ - `flowViewModel: FlowViewModel`
598
+
599
+ Payload optional fields:
600
+
601
+ - `changedNodeIds: string[]`
602
+
603
+ Example:
604
+
605
+ ```json
606
+ {
607
+ "channel": "zfl.eventflow",
608
+ "version": 1,
609
+ "type": "layoutChanged",
610
+ "payload": {
611
+ "changedNodeIds": [
612
+ "command:renewSubscription"
613
+ ],
614
+ "flowViewModel": {
615
+ "schema": "zfl.eventflow.view@1",
616
+ "nodes": [],
617
+ "edges": [],
618
+ "layout": {
619
+ "engine": "elk-layered",
620
+ "direction": "LR",
621
+ "rankSpacing": 180,
622
+ "nodeSpacing": 80
623
+ },
624
+ "bounds": {
625
+ "x": 0,
626
+ "y": 0,
627
+ "width": 1200,
628
+ "height": 400
629
+ },
630
+ "systemGroups": null
631
+ }
632
+ }
633
+ }
634
+ ```
635
+
636
+ ### `errorReported`
637
+
638
+ Status: recommended bridge event, not implemented by the base component.
639
+
640
+ Trigger:
641
+
642
+ - wrapper cannot parse host message
643
+ - host sends unsupported command
644
+ - wrapper detects invalid model
645
+
646
+ Payload required fields:
647
+
648
+ - `code: string`
649
+ - `message: string`
650
+
651
+ Payload optional fields:
652
+
653
+ - `details: object`
654
+ - `recoverable: boolean`
655
+
656
+ Example:
657
+
658
+ ```json
659
+ {
660
+ "channel": "zfl.eventflow",
661
+ "version": 1,
662
+ "type": "errorReported",
663
+ "payload": {
664
+ "code": "INVALID_GRAPH",
665
+ "message": "Graph payload is missing required top-level field 'nodes'.",
666
+ "recoverable": true,
667
+ "details": {
668
+ "field": "nodes"
669
+ }
670
+ }
671
+ }
672
+ ```
673
+
674
+ ## 6. Host integration model
675
+
676
+ The IntelliJ plugin should wrap the renderer with a very small adapter component and treat it as a controlled view.
677
+
678
+ Recommended interaction model:
679
+
680
+ 1. Host creates the webview and waits for `webviewReady`.
681
+ 2. Host sends `loadModel` with a full `FlowViewModel`.
682
+ 3. Renderer displays the graph from that model.
683
+ 4. User interactions inside the graph are emitted back to the host.
684
+ 5. If the underlying source changes, the host sends `refreshModel` with a new full `FlowViewModel`.
685
+ 6. If the host wants selection/highlight synchronization, it sends explicit commands such as `selectNodes` or `highlightNodes`.
686
+ 7. If drag layout editing is enabled and the user moves nodes, the renderer emits `layoutChanged`; the host decides whether to persist or ignore it.
687
+
688
+ Important: the current base component already supports steps 2, 4, and 7 through React props/callbacks. Steps involving message passing require a wrapper.
689
+
690
+ ## 7. Message bridge assumptions
691
+
692
+ ### Recommended protocol
693
+
694
+ Use a single JSON message envelope in both directions:
695
+
696
+ ```json
697
+ {
698
+ "channel": "zfl.eventflow",
699
+ "version": 1,
700
+ "type": "loadModel",
701
+ "requestId": "req-1",
702
+ "payload": {}
703
+ }
704
+ ```
705
+
706
+ ### Host -> renderer messages
707
+
708
+ #### `loadModel`
709
+
710
+ Use when the webview is first populated.
711
+
712
+ ```json
713
+ {
714
+ "channel": "zfl.eventflow",
715
+ "version": 1,
716
+ "type": "loadModel",
717
+ "requestId": "req-1",
718
+ "payload": {
719
+ "flowViewModel": {
720
+ "schema": "zfl.eventflow.view@1",
721
+ "nodes": [],
722
+ "edges": [],
723
+ "layout": null,
724
+ "bounds": null,
725
+ "systemGroups": null
726
+ },
727
+ "theme": "light",
728
+ "editable": true,
729
+ "displaySettings": {
730
+ "humanizeLabels": true
731
+ }
732
+ }
733
+ }
734
+ ```
735
+
736
+ #### `refreshModel`
737
+
738
+ Use when the host has a replacement graph after reparse/reanalysis.
739
+
740
+ ```json
741
+ {
742
+ "channel": "zfl.eventflow",
743
+ "version": 1,
744
+ "type": "refreshModel",
745
+ "payload": {
746
+ "flowViewModel": {
747
+ "schema": "zfl.eventflow.view@1",
748
+ "nodes": [],
749
+ "edges": [],
750
+ "layout": null,
751
+ "bounds": null,
752
+ "systemGroups": null
753
+ }
754
+ }
755
+ }
756
+ ```
757
+
758
+ #### `selectNodes`
759
+
760
+ Recommended command for controlled selection.
761
+
762
+ ```json
763
+ {
764
+ "channel": "zfl.eventflow",
765
+ "version": 1,
766
+ "type": "selectNodes",
767
+ "payload": {
768
+ "nodeIds": [
769
+ "command:renewSubscription"
770
+ ],
771
+ "replace": true,
772
+ "fitView": false
773
+ }
774
+ }
775
+ ```
776
+
777
+ #### `highlightNodes`
778
+
779
+ Recommended command for transient emphasis distinct from selection.
780
+
781
+ ```json
782
+ {
783
+ "channel": "zfl.eventflow",
784
+ "version": 1,
785
+ "type": "highlightNodes",
786
+ "payload": {
787
+ "nodeIds": [
788
+ "event:SubscriptionRenewed",
789
+ "policy:SubscriptionRenewed:chargePayment"
790
+ ],
791
+ "mode": "transient"
792
+ }
793
+ }
794
+ ```
795
+
796
+ #### `fitToNodes`
797
+
798
+ Recommended future command.
799
+
800
+ ```json
801
+ {
802
+ "channel": "zfl.eventflow",
803
+ "version": 1,
804
+ "type": "fitToNodes",
805
+ "payload": {
806
+ "nodeIds": [
807
+ "command:renewSubscription"
808
+ ],
809
+ "padding": 0.2,
810
+ "maxZoom": 1.2
811
+ }
812
+ }
813
+ ```
814
+
815
+ #### `clearSelection`
816
+
817
+ ```json
818
+ {
819
+ "channel": "zfl.eventflow",
820
+ "version": 1,
821
+ "type": "clearSelection",
822
+ "payload": {}
823
+ }
824
+ ```
825
+
826
+ #### `setTheme`
827
+
828
+ ```json
829
+ {
830
+ "channel": "zfl.eventflow",
831
+ "version": 1,
832
+ "type": "setTheme",
833
+ "payload": {
834
+ "theme": "dark"
835
+ }
836
+ }
837
+ ```
838
+
839
+ ### Renderer -> host messages
840
+
841
+ Recommended outgoing messages:
842
+
843
+ - `webviewReady`
844
+ - `nodeSelected`
845
+ - `selectionChanged`
846
+ - `viewportChanged`
847
+ - `layoutChanged`
848
+ - `contextMenuRequested`
849
+ - `errorReported`
850
+
851
+ Example:
852
+
853
+ ```json
854
+ {
855
+ "channel": "zfl.eventflow",
856
+ "version": 1,
857
+ "type": "selectionChanged",
858
+ "payload": {
859
+ "selectedNodeIds": [
860
+ "event:SubscriptionRenewed"
861
+ ],
862
+ "selectedEdgeIds": []
863
+ }
864
+ }
865
+ ```
866
+
867
+ ## 8. Selection and highlighting model
868
+
869
+ ### Current behavior
870
+
871
+ The base component supports:
872
+
873
+ - user click on a node
874
+ - user-driven multi-selection inside React Flow
875
+ - callback emission when selection changes
876
+
877
+ The base component does not currently expose imperative APIs for:
878
+
879
+ - programmatic node selection
880
+ - programmatic highlighting
881
+ - programmatic fit-to-selection
882
+ - external tree-to-graph synchronization
883
+
884
+ ### Recommended host model
885
+
886
+ Use two distinct concepts:
887
+
888
+ - `selection`
889
+ Persistent, host-owned state representing the item currently selected in graph or tree UI.
890
+ - `highlight`
891
+ Transient emphasis for hover, search result preview, or cross-panel focus.
892
+
893
+ Recommended rules:
894
+
895
+ - selecting a node in the tree sends `selectNodes`
896
+ - selecting a node in the graph emits `nodeSelected` and `selectionChanged`
897
+ - the host updates the rest of the IntelliJ UI from those events
898
+ - highlighting should not replace selection unless explicitly requested
899
+ - `fitToNodes` should be host-triggered, not automatic on every selection change
900
+
901
+ Recommended command semantics:
902
+
903
+ - `highlightNodes`
904
+ Visually emphasize nodes without changing selection
905
+ - `selectNodes`
906
+ Replace or extend current selection
907
+ - `fitToNodes`
908
+ Center/zoom to a node set
909
+ - `clearSelection`
910
+ Remove current graph selection
911
+
912
+ Because these commands are not implemented in the base component yet, the IntelliJ integration should treat them as wrapper-level features.
913
+
914
+ ## 9. Error and empty-state handling
915
+
916
+ ### Graph is missing
917
+
918
+ If no graph model is available, the host should render or instruct the wrapper to render an empty placeholder state such as:
919
+
920
+ - "No event flow available"
921
+ - "Open a ZFL flow file to visualize"
922
+
923
+ The base component expects a `flowViewModel` prop and does not itself own the "missing model" state. The demo page handles this outside the component.
924
+
925
+ ### Graph is invalid
926
+
927
+ The host or wrapper should validate basic structure before rendering:
928
+
929
+ - `schema` present
930
+ - `nodes` array present
931
+ - `edges` array present
932
+ - node IDs unique
933
+ - edge endpoints reference existing nodes
934
+
935
+ Current renderer behavior on invalid-but-parseable data:
936
+
937
+ - nodes without `position` or `dimensions` are skipped
938
+ - edges with missing visible endpoints are skipped
939
+ - some classes of invalid input may still fail earlier in the host if required fields are absent
940
+
941
+ Recommended behavior:
942
+
943
+ - emit `errorReported`
944
+ - keep the previous valid graph if possible
945
+ - show a visible invalid-state message rather than a blank pane with no explanation
946
+
947
+ ### Graph is empty
948
+
949
+ An empty but valid graph is:
950
+
951
+ ```json
952
+ {
953
+ "schema": "zfl.eventflow.view@1",
954
+ "nodes": [],
955
+ "edges": [],
956
+ "layout": null,
957
+ "bounds": null,
958
+ "systemGroups": null
959
+ }
960
+ ```
961
+
962
+ Recommended host behavior:
963
+
964
+ - render an explicit "empty flow" message
965
+ - do not treat empty as an error
966
+
967
+ ## 10. Webview embedding guidance for IntelliJ
968
+
969
+ For IntelliJ JCEF embedding:
970
+
971
+ - mount the renderer into a root element with explicit width and height
972
+ - avoid reloading the whole webview for every selection change; send messages instead
973
+ - prefer full-model replacement on refresh over incremental graph patching
974
+ - keep message payloads pure JSON
975
+ - do not rely on filesystem access from inside the webview
976
+ - keep source navigation in the IntelliJ host, not inside the renderer
977
+
978
+ Practical constraints:
979
+
980
+ - React Flow needs measurable layout space
981
+ - webview theme changes should be forwarded explicitly
982
+ - focus handling may differ from a normal browser tab, so keyboard shortcuts should stay host-owned unless intentionally added
983
+ - large graphs should be sent after model generation is complete, not streamed node-by-node
984
+
985
+ Recommended IntelliJ-side split:
986
+
987
+ - plugin: fetch model, validate, persist, navigate source, own selection state
988
+ - webview wrapper: translate plugin messages to React props
989
+ - renderer: draw graph and report UI interactions
990
+
991
+ ## 11. Current capabilities vs planned capabilities
992
+
993
+ ### Current behavior in this repository
994
+
995
+ - Renders `FlowViewModel` using React Flow
996
+ - Supports node types `START`, `COMMAND`, `EVENT`, `POLICY`, `END`
997
+ - Supports edge types `CAUSATION`, `TRIGGER`, `CONDITIONAL`, `ERROR`
998
+ - Supports light/dark theme prop
999
+ - Supports display-only label humanization
1000
+ - Emits node click callbacks
1001
+ - Emits selection-change callbacks
1002
+ - Emits updated `FlowViewModel` after drag stop when editing is enabled
1003
+ - Skips nodes lacking `position` or `dimensions`
1004
+ - Skips dangling edges
1005
+ - Ignores `systemGroups`
1006
+ - Has no built-in message bridge
1007
+ - Has no built-in programmatic selection/highlight API
1008
+ - Has no built-in viewport event API
1009
+
1010
+ ### Planned or recommended for host integration
1011
+
1012
+ - Standard host/webview JSON message protocol
1013
+ - `webviewReady` handshake
1014
+ - Programmatic selection commands
1015
+ - Programmatic highlight commands
1016
+ - Fit-to-selection commands
1017
+ - Viewport synchronization events
1018
+ - Structured error reporting
1019
+ - Context menu events
1020
+ - Optional richer empty/invalid states inside the wrapper
1021
+
1022
+ ## 12. Versioning and compatibility notes
1023
+
1024
+ ### Model versioning
1025
+
1026
+ The graph payload already contains a schema marker:
1027
+
1028
+ - `zfl.eventflow.view@1`
1029
+
1030
+ Hosts should branch on schema version before assuming field compatibility.
1031
+
1032
+ ### Bridge versioning
1033
+
1034
+ The recommended message bridge should include:
1035
+
1036
+ - `channel`
1037
+ - `version`
1038
+ - `type`
1039
+
1040
+ Use `version: 1` for the first stable IntelliJ bridge contract.
1041
+
1042
+ ### Safe evolution rules
1043
+
1044
+ - add new message types without changing existing ones
1045
+ - add optional payload fields freely
1046
+ - do not rename existing fields in place
1047
+ - do not change selection or event semantics without bumping bridge version
1048
+ - if a new graph schema is introduced, support old schema in the host adapter during a migration period
1049
+
1050
+ Recommended compatibility stance:
1051
+
1052
+ - renderer should ignore unknown incoming optional fields
1053
+ - host should ignore unknown outgoing event fields
1054
+ - unsupported incoming message types should yield `errorReported`, not silent failure
1055
+
1056
+ ## 13. Open questions / ambiguities
1057
+
1058
+ - Should the IntelliJ integration standardize on full-model replacement only, or also support incremental updates later?
1059
+ - Should `sourceRef.file` be an absolute path, workspace-relative path, URI, or logical document ID?
1060
+ - Should graph selection be single-select only from IntelliJ tree interactions, even though React Flow supports multi-selection?
1061
+ - Should host-triggered highlighting be a separate visual state from selection, or can selection styling serve both?
1062
+ - Should drag-based layout editing be enabled in IntelliJ by default, or remain an opt-in editing mode?
1063
+ - Should `layoutChanged` be emitted on every drag move, or only on drag stop? Current base behavior is drag stop only.
1064
+ - Should the wrapper preserve the previous valid graph when a refresh payload is invalid?
1065
+ - Is `systemGroups` intentionally deferred, or should swimlane/group rendering become part of the supported contract?
1066
+ - Does the IntelliJ plugin need edge-click events in addition to node-click events?
1067
+ - Should viewport persistence be part of the first integration version, or deferred until selection and navigation are stable?