@xxrandr/conecta-diagram 0.0.0-stage → 0.1.4

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 (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +319 -2
  3. package/dist/chunk-3QGSCPWL.cjs +663 -0
  4. package/dist/chunk-43VYPB4U.js +2136 -0
  5. package/dist/chunk-6ADTOXBT.cjs +626 -0
  6. package/dist/chunk-B2RLLFGC.js +0 -0
  7. package/dist/chunk-GXOBW2BK.js +290 -0
  8. package/dist/chunk-HZER5BPR.js +1122 -0
  9. package/dist/chunk-IT5H6WO2.js +626 -0
  10. package/dist/chunk-LKOWNTPR.cjs +1 -0
  11. package/dist/chunk-Q62JPMJH.js +663 -0
  12. package/dist/chunk-QI4NNFRQ.cjs +290 -0
  13. package/dist/chunk-VD32JNDO.cjs +2136 -0
  14. package/dist/chunk-ZMSQTBVW.cjs +1122 -0
  15. package/dist/core/index.cjs +56 -0
  16. package/dist/core/index.d.cts +65 -0
  17. package/dist/core/index.d.ts +65 -0
  18. package/dist/core/index.js +56 -0
  19. package/dist/d3/index.cjs +8 -0
  20. package/dist/d3/index.d.cts +46 -0
  21. package/dist/d3/index.d.ts +46 -0
  22. package/dist/d3/index.js +8 -0
  23. package/dist/index.cjs +71 -0
  24. package/dist/index.d.cts +5 -0
  25. package/dist/index.d.ts +5 -0
  26. package/dist/index.js +71 -0
  27. package/dist/photon.css +339 -0
  28. package/dist/runner-display-BkBJpBY-.d.ts +151 -0
  29. package/dist/runner-display-DHHtavSX.d.cts +151 -0
  30. package/dist/store-CtOInpPu.d.cts +242 -0
  31. package/dist/store-CtOInpPu.d.ts +242 -0
  32. package/dist/svg/index.cjs +15 -0
  33. package/dist/svg/index.d.cts +172 -0
  34. package/dist/svg/index.d.ts +172 -0
  35. package/dist/svg/index.js +15 -0
  36. package/docs/devtools-refactor.md +93 -0
  37. package/docs/java-runtime-integration.md +268 -0
  38. package/docs/photon-template.html +547 -0
  39. package/docs/presentation-architecture.md +30 -0
  40. package/docs/runner-display.md +109 -0
  41. package/package.json +130 -4
@@ -0,0 +1,547 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="light">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Workflow Nodes — Firefox Profiler palette</title>
7
+
8
+ <script>
9
+ /* Set theme before first paint to avoid a flash of the wrong theme. */
10
+ try {
11
+ var saved = localStorage.getItem("wf-theme");
12
+ var dark = window.matchMedia("(prefers-color-scheme: dark)").matches;
13
+ document.documentElement.dataset.theme = saved || (dark ? "dark" : "light");
14
+ } catch (_) {}
15
+ </script>
16
+
17
+ <style>
18
+ /* ------------------------------------------------------------
19
+ Palette — based on Firefox Photon colors (the same family
20
+ the Firefox Profiler uses: blue-60 #0060df, grey scale,
21
+ dark-mode accent #0a84ff).
22
+ ------------------------------------------------------------ */
23
+ :root {
24
+ color-scheme: light;
25
+
26
+ --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto,
27
+ Helvetica, Arial, sans-serif;
28
+ --font-mono: ui-monospace, "SF Mono", "Fira Code", Menlo, Consolas, monospace;
29
+
30
+ --app-bg: #ededf0; /* grey-20 */
31
+ --canvas-bg: #f9f9fa; /* grey-10 */
32
+ --dot: #d7d7db; /* grey-30 */
33
+
34
+ --surface: #ffffff;
35
+ --border: #d7d7db; /* grey-30 */
36
+ --border-strong: #b1b1b3; /* grey-40 */
37
+
38
+ --text: #0c0c0d; /* grey-90 */
39
+ --text-secondary: #4a4a4f; /* grey-60 */
40
+ --text-muted: #737373; /* grey-50 */
41
+
42
+ --icon-bg: #ededf0;
43
+ --chip-bg: #ededf0;
44
+
45
+ --accent: #0060df; /* blue-60 */
46
+ --selection-glow: rgba(0, 96, 223, 0.16);
47
+
48
+ --shadow-card: 0 1px 2px rgba(12, 12, 13, 0.06);
49
+ --shadow-card-hover: 0 2px 8px rgba(12, 12, 13, 0.10);
50
+ }
51
+
52
+ html[data-theme="dark"] {
53
+ color-scheme: dark;
54
+
55
+ --app-bg: #0c0c0d;
56
+ --canvas-bg: #141414;
57
+ --dot: #2a2a2e;
58
+
59
+ --surface: #1c1c1f;
60
+ --border: #35353a;
61
+ --border-strong: #4a4a4f;
62
+
63
+ --text: #f9f9fa;
64
+ --text-secondary: #d7d7db;
65
+ --text-muted: #9a9aa2;
66
+
67
+ --icon-bg: #2a2a2e;
68
+ --chip-bg: #2a2a2e;
69
+
70
+ --accent: #0a84ff; /* blue-50 / dark-mode blue */
71
+ --selection-glow: rgba(10, 132, 255, 0.24);
72
+
73
+ --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.40);
74
+ --shadow-card-hover: 0 2px 10px rgba(0, 0, 0, 0.50);
75
+ }
76
+
77
+ * { box-sizing: border-box; }
78
+
79
+ body {
80
+ margin: 0;
81
+ min-height: 100vh;
82
+ padding: 20px;
83
+ background: var(--app-bg);
84
+ color: var(--text);
85
+ font-family: var(--font-sans);
86
+ -webkit-font-smoothing: antialiased;
87
+ transition: background-color .15s ease, color .15s ease;
88
+ }
89
+
90
+ /* ---------------- Toolbar ---------------- */
91
+
92
+ .toolbar {
93
+ max-width: 980px;
94
+ margin: 0 auto 10px;
95
+ display: flex;
96
+ align-items: center;
97
+ justify-content: space-between;
98
+ gap: 12px;
99
+ }
100
+
101
+ .toolbar-title {
102
+ font-size: 11px;
103
+ font-weight: 600;
104
+ letter-spacing: .08em;
105
+ text-transform: uppercase;
106
+ color: var(--text-muted);
107
+ }
108
+
109
+ .theme-toggle {
110
+ display: inline-flex;
111
+ align-items: center;
112
+ gap: 6px;
113
+ padding: 5px 10px;
114
+ border: 1px solid var(--border);
115
+ border-radius: 4px;
116
+ background: var(--surface);
117
+ color: var(--text-secondary);
118
+ font: inherit;
119
+ font-size: 12px;
120
+ font-weight: 500;
121
+ line-height: 18px;
122
+ cursor: pointer;
123
+ transition: background-color .12s ease, border-color .12s ease, color .12s ease;
124
+ }
125
+
126
+ .theme-toggle:hover {
127
+ background: var(--chip-bg);
128
+ border-color: var(--border-strong);
129
+ color: var(--text);
130
+ }
131
+
132
+ .theme-toggle:focus-visible {
133
+ outline: 2px solid var(--accent);
134
+ outline-offset: 2px;
135
+ }
136
+
137
+ .icon {
138
+ display: block;
139
+ width: 13px;
140
+ height: 13px;
141
+ fill: none;
142
+ stroke: currentColor;
143
+ stroke-width: 1.5;
144
+ stroke-linecap: round;
145
+ stroke-linejoin: round;
146
+ }
147
+
148
+ .icon-sun { display: none; }
149
+ html[data-theme="dark"] .icon-sun { display: block; }
150
+ html[data-theme="dark"] .icon-moon { display: none; }
151
+
152
+ /* ---------------- Canvas ---------------- */
153
+
154
+ .canvas {
155
+ max-width: 980px;
156
+ min-height: 460px;
157
+ margin: 0 auto;
158
+ padding: 40px 28px;
159
+ display: flex;
160
+ flex-wrap: wrap;
161
+ align-items: center;
162
+ justify-content: center;
163
+ gap: 28px;
164
+ background-color: var(--canvas-bg);
165
+ background-image: radial-gradient(var(--dot) 1px, transparent 1px);
166
+ background-size: 16px 16px;
167
+ border: 1px solid var(--border);
168
+ border-radius: 8px;
169
+ }
170
+
171
+ /* ---------------- Nodes ---------------- */
172
+
173
+ .flow-node {
174
+ position: relative;
175
+ width: 300px;
176
+ padding: 14px 16px 12px;
177
+ overflow: hidden;
178
+ border: 1px solid var(--border);
179
+ border-radius: 8px;
180
+ background: var(--surface);
181
+ box-shadow: var(--shadow-card);
182
+ cursor: pointer;
183
+ transition: border-color .12s ease, box-shadow .12s ease,
184
+ background-color .12s ease;
185
+ }
186
+
187
+ /* Category stripe, like a profiler track color */
188
+ .flow-node::before {
189
+ content: "";
190
+ position: absolute;
191
+ inset: 0 auto 0 0;
192
+ width: 3px;
193
+ background: var(--node-accent);
194
+ opacity: .9;
195
+ }
196
+
197
+ .flow-node:hover {
198
+ border-color: var(--border-strong);
199
+ box-shadow: var(--shadow-card-hover);
200
+ }
201
+
202
+ .flow-node.selected {
203
+ border-color: var(--accent);
204
+ box-shadow: 0 0 0 3px var(--selection-glow), var(--shadow-card-hover);
205
+ }
206
+
207
+ .flow-node:focus-visible {
208
+ outline: 2px solid var(--accent);
209
+ outline-offset: 2px;
210
+ }
211
+
212
+ /* Per-kind accent colors */
213
+ .flow-node[data-kind="process"] {
214
+ --node-accent: #0060df;
215
+ --node-accent-soft: rgba(0, 96, 223, 0.10);
216
+ }
217
+ .flow-node[data-kind="service"] {
218
+ --node-accent: #008eaa;
219
+ --node-accent-soft: rgba(0, 142, 170, 0.12);
220
+ }
221
+ html[data-theme="dark"] .flow-node[data-kind="process"] {
222
+ --node-accent: #0a84ff;
223
+ --node-accent-soft: rgba(10, 132, 255, 0.16);
224
+ }
225
+ html[data-theme="dark"] .flow-node[data-kind="service"] {
226
+ --node-accent: #00c2d1;
227
+ --node-accent-soft: rgba(0, 194, 209, 0.16);
228
+ }
229
+
230
+ .node-header {
231
+ display: flex;
232
+ align-items: center;
233
+ gap: 10px;
234
+ }
235
+
236
+ .node-icon {
237
+ display: grid;
238
+ place-items: center;
239
+ width: 32px;
240
+ height: 32px;
241
+ flex: 0 0 32px;
242
+ border-radius: 6px;
243
+ background: var(--icon-bg);
244
+ color: var(--node-accent);
245
+ transition: background-color .12s ease;
246
+ }
247
+
248
+ .flow-node.selected .node-icon {
249
+ background: var(--node-accent-soft);
250
+ }
251
+
252
+ .node-icon svg {
253
+ width: 18px;
254
+ height: 18px;
255
+ fill: none;
256
+ stroke: currentColor;
257
+ stroke-width: 1.7;
258
+ stroke-linecap: round;
259
+ stroke-linejoin: round;
260
+ }
261
+
262
+ .node-title {
263
+ font-size: 13.5px;
264
+ font-weight: 600;
265
+ line-height: 18px;
266
+ letter-spacing: -.01em;
267
+ }
268
+
269
+ .node-subtitle {
270
+ margin-top: 1px;
271
+ font-size: 11px;
272
+ line-height: 15px;
273
+ color: var(--text-muted);
274
+ }
275
+
276
+ /* ---------------- Signal chips ---------------- */
277
+
278
+ .signals {
279
+ display: flex;
280
+ flex-wrap: wrap;
281
+ gap: 6px;
282
+ margin: 12px 0 10px;
283
+ }
284
+
285
+ .signal {
286
+ display: inline-flex;
287
+ align-items: center;
288
+ gap: 5px;
289
+ padding: 2px 8px 2px 6px;
290
+ border-radius: 20px;
291
+ background: var(--chip-bg);
292
+ color: var(--text-secondary);
293
+ font-family: var(--font-mono);
294
+ font-size: 10px;
295
+ line-height: 16px;
296
+ white-space: nowrap;
297
+ }
298
+
299
+ .signal svg {
300
+ width: 12px;
301
+ height: 12px;
302
+ flex: 0 0 12px;
303
+ fill: none;
304
+ stroke: var(--text-muted);
305
+ stroke-width: 1.7;
306
+ stroke-linecap: round;
307
+ stroke-linejoin: round;
308
+ }
309
+
310
+ /* ---------------- Preview row ---------------- */
311
+
312
+ .action-preview {
313
+ display: flex;
314
+ align-items: center;
315
+ gap: 6px;
316
+ margin: 0 0 12px;
317
+ }
318
+
319
+ .preview-label {
320
+ font-size: 9.5px;
321
+ font-weight: 600;
322
+ letter-spacing: .08em;
323
+ text-transform: uppercase;
324
+ color: var(--text-muted);
325
+ }
326
+
327
+ .preview-value {
328
+ padding: 1px 5px;
329
+ border-radius: 3px;
330
+ background: var(--chip-bg);
331
+ color: var(--text-secondary);
332
+ font-family: var(--font-mono);
333
+ font-size: 10.5px;
334
+ line-height: 16px;
335
+ }
336
+
337
+ .node-divider {
338
+ height: 1px;
339
+ margin: 0 0 9px;
340
+ background: var(--border);
341
+ }
342
+
343
+ /* ---------------- Footer ---------------- */
344
+
345
+ .node-footer {
346
+ display: flex;
347
+ align-items: center;
348
+ justify-content: space-between;
349
+ gap: 10px;
350
+ }
351
+
352
+ .execution {
353
+ display: inline-flex;
354
+ align-items: center;
355
+ gap: 6px;
356
+ font-size: 11px;
357
+ font-weight: 600;
358
+ color: var(--text-secondary);
359
+ }
360
+
361
+ .status-dot {
362
+ width: 6px;
363
+ height: 6px;
364
+ border-radius: 50%;
365
+ background: var(--text-muted);
366
+ }
367
+
368
+ .action-type {
369
+ font-family: var(--font-mono);
370
+ font-size: 10px;
371
+ color: var(--text-muted);
372
+ }
373
+
374
+ /* ---------------- Responsive ---------------- */
375
+
376
+ @media (max-width: 700px) {
377
+ body { padding: 12px; }
378
+ .canvas { padding: 28px 14px; gap: 20px; }
379
+ .flow-node { width: 100%; max-width: 340px; }
380
+ }
381
+ </style>
382
+ </head>
383
+
384
+ <body>
385
+ <div class="toolbar">
386
+ <span class="toolbar-title">Workflow nodes</span>
387
+
388
+ <button class="theme-toggle" id="themeToggle" type="button"
389
+ aria-pressed="false" title="Toggle color theme">
390
+ <svg class="icon icon-moon" viewBox="0 0 16 16" aria-hidden="true">
391
+ <path d="M13.5 9.6A5.75 5.75 0 0 1 6.4 2.5a5.75 5.75 0 1 0 7.1 7.1Z"/>
392
+ </svg>
393
+ <svg class="icon icon-sun" viewBox="0 0 16 16" aria-hidden="true">
394
+ <circle cx="8" cy="8" r="3"/>
395
+ <path d="M8 1.4v1.9M8 12.7v1.9M1.4 8h1.9M12.7 8h1.9M3.35 3.35l1.35 1.35M11.3 11.3l1.35 1.35M12.65 3.35 11.3 4.7M4.7 11.3l-1.35 1.35"/>
396
+ </svg>
397
+ <span id="themeLabel">Dark mode</span>
398
+ </button>
399
+ </div>
400
+
401
+ <main class="canvas">
402
+ <!-- Selected node -->
403
+ <article class="flow-node selected" data-kind="process" tabindex="0"
404
+ role="button" aria-pressed="true">
405
+ <header class="node-header">
406
+ <div class="node-icon">
407
+ <svg viewBox="0 0 24 24" aria-hidden="true">
408
+ <path d="m8 7-5 5 5 5m8-10 5 5-5 5m-3-13-2 16"/>
409
+ </svg>
410
+ </div>
411
+ <div>
412
+ <div class="node-title">Normalize order</div>
413
+ <div class="node-subtitle">Python · Process</div>
414
+ </div>
415
+ </header>
416
+
417
+ <div class="signals">
418
+ <div class="signal">
419
+ <svg viewBox="0 0 24 24" aria-hidden="true">
420
+ <path d="M3 12h14m-5-5 5 5-5 5m6-14h3v18h-3"/>
421
+ </svg>
422
+ order.received
423
+ </div>
424
+ <div class="signal">
425
+ <svg viewBox="0 0 24 24" aria-hidden="true">
426
+ <path d="M3 12h14m-5-5 5 5-5 5M6 3H3v18h3"/>
427
+ </svg>
428
+ order.normalized
429
+ </div>
430
+ </div>
431
+
432
+ <div class="action-preview">
433
+ <span class="preview-label">Profile</span>
434
+ <code class="preview-value">python-normalize</code>
435
+ </div>
436
+
437
+ <div class="node-divider"></div>
438
+
439
+ <div class="node-footer">
440
+ <span class="execution">
441
+ <span class="status-dot"></span>
442
+ Idle
443
+ </span>
444
+ <code class="action-type">process.exec</code>
445
+ </div>
446
+ </article>
447
+
448
+ <!-- Unselected node -->
449
+ <article class="flow-node" data-kind="service" tabindex="0"
450
+ role="button" aria-pressed="false">
451
+ <header class="node-header">
452
+ <div class="node-icon">
453
+ <svg viewBox="0 0 24 24" aria-hidden="true">
454
+ <path d="M4 5h16v14H4zM8 9h8M8 13h5"/>
455
+ </svg>
456
+ </div>
457
+ <div>
458
+ <div class="node-title">Validate payment</div>
459
+ <div class="node-subtitle">Payment · Service</div>
460
+ </div>
461
+ </header>
462
+
463
+ <div class="signals">
464
+ <div class="signal">
465
+ <svg viewBox="0 0 24 24" aria-hidden="true">
466
+ <path d="M3 12h14m-5-5 5 5-5 5m6-14h3v18h-3"/>
467
+ </svg>
468
+ payment.created
469
+ </div>
470
+ <div class="signal">
471
+ <svg viewBox="0 0 24 24" aria-hidden="true">
472
+ <path d="M3 12h14m-5-5 5 5-5 5M6 3H3v18h3"/>
473
+ </svg>
474
+ payment.verified
475
+ </div>
476
+ </div>
477
+
478
+ <div class="action-preview">
479
+ <span class="preview-label">Provider</span>
480
+ <code class="preview-value">payment-gateway</code>
481
+ </div>
482
+
483
+ <div class="node-divider"></div>
484
+
485
+ <div class="node-footer">
486
+ <span class="execution">
487
+ <span class="status-dot"></span>
488
+ Idle
489
+ </span>
490
+ <code class="action-type">payment.verify</code>
491
+ </div>
492
+ </article>
493
+ </main>
494
+
495
+ <script>
496
+ const root = document.documentElement;
497
+ const toggle = document.getElementById("themeToggle");
498
+ const themeLabel = document.getElementById("themeLabel");
499
+
500
+ function applyTheme(theme) {
501
+ root.dataset.theme = theme;
502
+ const dark = theme === "dark";
503
+ toggle.setAttribute("aria-pressed", String(dark));
504
+ themeLabel.textContent = dark ? "Light mode" : "Dark mode";
505
+ }
506
+
507
+ applyTheme(root.dataset.theme === "dark" ? "dark" : "light");
508
+
509
+ toggle.addEventListener("click", () => {
510
+ const next = root.dataset.theme === "dark" ? "light" : "dark";
511
+ applyTheme(next);
512
+ try { localStorage.setItem("wf-theme", next); } catch (_) {}
513
+ });
514
+
515
+ /* ----- Node selection ----- */
516
+
517
+ const nodes = [...document.querySelectorAll(".flow-node")];
518
+
519
+ function selectNode(node) {
520
+ for (const item of nodes) {
521
+ const selected = item === node;
522
+ item.classList.toggle("selected", selected);
523
+ item.setAttribute("aria-pressed", String(selected));
524
+ }
525
+ }
526
+
527
+ nodes.forEach((node, index) => {
528
+ node.addEventListener("click", () => selectNode(node));
529
+
530
+ node.addEventListener("keydown", (event) => {
531
+ if (event.key === "Enter" || event.key === " ") {
532
+ event.preventDefault();
533
+ selectNode(node);
534
+ return;
535
+ }
536
+
537
+ // Let the arrow keys move focus between nodes.
538
+ if (event.key === "ArrowRight" || event.key === "ArrowLeft") {
539
+ event.preventDefault();
540
+ const direction = event.key === "ArrowRight" ? 1 : -1;
541
+ nodes[(index + direction + nodes.length) % nodes.length].focus();
542
+ }
543
+ });
544
+ });
545
+ </script>
546
+ </body>
547
+ </html>
@@ -0,0 +1,30 @@
1
+ # Presentation architecture
2
+
3
+ The current presentation is defined by **[the Photon + DevTools refactor](devtools-refactor.md)**. The old fixed DETAILS inspector and bottom console shell have been deleted; do not build on those obsolete surfaces.
4
+
5
+ ## Library boundary
6
+
7
+ - `src/svg/photon.css`: shared template tokens and component classes; import `@xxrandr/conecta-diagram/photon.css` explicitly.
8
+ - `src/svg/PhotonCard.ts`: safe HTML/SVG template structure; all text is inserted with DOM APIs, never interpolated into executable HTML.
9
+ - `src/svg/SvgFlowEditor.ts`: keyed SVG groups, selection, connections, camera and native D3 movement. It owns no application panel or runtime transport.
10
+ - `src/svg/icons.ts` / `NodeTooltip.ts`: small local SVG glyphs and instance-owned, theme-token tool previews. Destroy removes listeners, timers and tooltip elements.
11
+ - `src/core/globals.ts`: typed diagram globals and creation-ordered item projections.
12
+ - `src/core/performance.ts`: immutable backend metric observations, including integer cost. No UI-derived costs.
13
+ - `src/d3/box-collision.ts`: shape-aware placement, separate from persistent plan/execution state.
14
+
15
+ ## Host UI boundary
16
+
17
+ - `examples/demo.js`: the single authoritative store/replay owner and command dispatcher.
18
+ - `examples/devtools/shell.js`: stable tab registration and the one DevTools top bar.
19
+ - `manager.js`, `window.js`: docking and same-origin real-window command/snapshot synchronization.
20
+ - `resize.js`: pointer/keyboard resize mechanics and `wf-panel-sizes` persistence.
21
+ - `tabs/assemble.js`: the only node/global editing surface; right pane is exactly `In construction`.
22
+ - `tabs/trace.js`: relocation adapter for the existing `ExecutionConsole` list/detail logic and original `ExecutionJournal` model.
23
+ - `tabs/performance.js`: shared-ruler Parent process / Network / Memory / Cost top graph; no lower trace strip.
24
+ - `examples/styles.css`: app chrome expressed entirely in the shared Photon tokens.
25
+
26
+ ## Display guarantees
27
+
28
+ Nothing executes from a card, tooltip, editor, trace row, graph, layout tick or detached window. The sample replay remains explicitly simulated. The complete retained sample trace remains searchable, pageable, inspectable and exportable; success/finality/uint64 counters and independent file-chunk finality are not coerced away.
29
+
30
+ Receipt timing is distinct from backend execution time. The UI does not invent missing logs, costs, checksums, binary data or credentials. Real backend transport, durable recovery, collaborative document editing and per-concurrent-activation inspectors remain separate work. No formal accessibility certification or exhaustive cross-browser performance claim is made.
@@ -0,0 +1,109 @@
1
+ # Runner operation and signal display
2
+
3
+ The viewer can now describe the command families from `maximus.runner.commands` and display observed results. **It is not a protobuf client, command sender, Python interpreter, HTTP client, SQL engine or file-transfer implementation.** Nothing is executed by clicking, drawing, dragging, rendering, replaying, or receiving an observation.
4
+
5
+ ## Action previews
6
+
7
+ Task and decision nodes may carry `action: TaskAction`. Its open-string `type` matches a runner command type. `body.kind` selects a small, sanitized presentation model, not the full protobuf body. The type and known body family must agree; unknown extension types use `generic`. A decision may also carry `decision: { description, expression: { language, source } }`. Expression/code source is text only; it never replaces executable graph conditions or evaluates a real decision in the viewer.
8
+
9
+ | Runner family | Display fields |
10
+ | ------------------------------------------------------- | ------------------------------------------------------------------------------- |
11
+ | `api.request` / `api` | Method, redacted URL, logical connection id, optional sanitized body preview |
12
+ | `database.*` / `database` | Backend, logical datasource, operation, SQL/procedure/document preview |
13
+ | `file.*` / `file` | Operation, path/destination, transfer id and resume policy |
14
+ | `process.exec` / `process` | Configured executable profile, arguments, optional code-language/source preview |
15
+ | `ssh.exec` / `ssh` | Logical connection id and command preview |
16
+ | `sftp.*` / `sftp` | Operation, connection id and local/remote/destination paths |
17
+ | `mail.*` / `mail` | Operation, connection id, subject and sanitized recipients |
18
+ | `messaging.*` / `messaging` | Operation, connection id and topic/queue |
19
+ | `event.*` / `event` | Operation, topic and optional watched path |
20
+ | `command.cancel` / `cancel` | Target command id and reason; this does not cancel anything |
21
+ | `command.ping`, `custom`, vendor extensions / `generic` | Logical name and sanitized textual preview |
22
+
23
+ Python has **no separate typed body** in the supplied protocol. Show it as a `process.exec` configured profile with optional Python source for explanation; do not invent a supported `python.execute` capability or assume a source preview is executable stdin.
24
+
25
+ ```ts
26
+ store.updateNode("process", (node) => ({
27
+ ...node,
28
+ action: {
29
+ type: "process.exec",
30
+ body: {
31
+ kind: "process",
32
+ profile: "python-report",
33
+ code: {
34
+ language: "python",
35
+ source: "print('illustrative preview only')",
36
+ },
37
+ },
38
+ },
39
+ signals: { inputs: ["report.requested"], outputs: ["report.ready"] },
40
+ }));
41
+ ```
42
+
43
+ Photon task cards show tool identity, IN/OUT chips, a preview row and observed status; hover offers language/tool detail. Decision cards show a condition preview and the **observed** selected branch. Taken edges are highlighted; unselected decision outputs are dashed. Node/global definitions are edited only through DevTools Assemble; command correlation, exact progress, transfers and signal observations are inspected in Trace. All user content is text, never executable HTML.
44
+
45
+ `capabilities` in editor options or `editor.setCapabilities(commandTypes)` is an advertised command-type list supplied by the host. A declared protobuf body or command name is not evidence that a runner supports it. Without a catalog, support is unverified; the supplied implementation's `database.transaction` and `database.metadata` are marked unsupported by default. With a catalog, absence means unsupported. Sample capabilities in the demo are explicitly fixtures, not a discovery request.
46
+
47
+ No credentials, private connection details, full binary bodies or unrestricted result payloads belong in the diagram. The host must redact source, SQL, paths, recipients and payload previews before handing them to this library. API URL presentation strips embedded credentials, query values and fragments, but that is not a universal secret scanner. Raw planning JSON is still application data; do not populate it with secrets.
48
+
49
+ ## Observed results
50
+
51
+ `RunnerDisplay` is a bounded in-memory presentation projection. It correlates command ids to existing task/decision nodes and accepts `RunnerResultPreview` DTOs. The host/backend maps generated protobuf messages to these redacted DTOs. It has no networking, persistence, dispatch or worker code.
52
+
53
+ ```ts
54
+ import { RunnerDisplay } from "@xxrandr/conecta-diagram/core";
55
+
56
+ const display = new RunnerDisplay(store.getSnapshot());
57
+ display.track("command-42", "process"); // Correlation only, not command dispatch
58
+ display.applyResult({
59
+ commandId: "command-42",
60
+ status: "STREAMING",
61
+ sequence: "9007199254740993",
62
+ success: false,
63
+ final: false,
64
+ summary: "Rows received",
65
+ progress: { current: "100", total: "200", unit: "rows" },
66
+ });
67
+ editor.setExecution(display.getOverlay());
68
+ ```
69
+
70
+ - Status names follow the supplied enum: `ACCEPTED`, `RUNNING`, `STREAMING`, `SUCCEEDED`, `FAILED`, `CANCELLED`, `TIMED_OUT`, `REJECTED`. Numeric enum values 1–8 are also accepted. Unspecified/legacy messages require an explicit host policy; the projection does not guess a terminal state.
71
+ - `success=false` on an intermediate result is **not a failure**. Terminal status, `final=true`, and success must agree. Success is true only for `SUCCEEDED` terminal results. All terminal outcomes display distinctly.
72
+ - Sequences are uint64 **decimal strings**, compared using `BigInt`, never lossy JavaScript numbers. They are per command, not the Java/SSE run-event cursor. A first result may start at 0 or 1; later values must increase. Gaps are not synthesized or interpreted as command completion.
73
+ - Older/identical results are ignored. Conflicting content at the latest sequence and results after a final result are rejected. A retry uses a new command id. Re-registering an old id never replaces a newer binding. Up to 10000 commands are retained per projection; create a fresh projection for a new run/window.
74
+ - `progress` contains exact decimal counters and a unit (`bytes`, `rows`, `items`). `transfer` optionally carries file-chunk `{id, offset, chunkSequence, size, totalSize?, final}` metadata. These are independent of the command sequence/finality: a final file chunk does not itself imply a final command result. Raw `data` bytes are not stored/displayed. No chunks are sent, resumed or checksummed by the viewer.
75
+ - A node may have several tracked command ids; the most recently registered id is the current card summary. This is not a complete concurrent-activation inspector. Old results cannot replace that current summary; host-provided `ExecutionOverlay` can supply an explicit aggregate instead.
76
+ - `RunnerDisplay` captures the plan at construction. Recreate it when changing runs/revisions. Snapshots are frozen; live rendering never changes the plan.
77
+
78
+ ## Signals and decisions
79
+
80
+ ```ts
81
+ display.observeSignal({
82
+ id: "event-104",
83
+ nodeId: "approval",
84
+ name: "approval.received",
85
+ direction: "in",
86
+ summary: "Redacted approval received outside the diagram",
87
+ });
88
+ display.observeDecision({
89
+ id: "event-105",
90
+ nodeId: "decision",
91
+ edgeId: "approved",
92
+ reason: "Observed server result: approved == true",
93
+ });
94
+ editor.setExecution(display.getOverlay());
95
+ ```
96
+
97
+ Decision observations must reference a real output of that decision. Signals indicate observed ingress/egress; they do not match subscriptions, emit business events, collect input, or trigger execution. The bounded signal/observation-id tail defaults to 200. Replay deduplication outside that tail belongs to the host's durable cursor/reducer, not this visual cache. Signal names in the plan are display descriptors, not the complete future Java binding schema.
98
+
99
+ `editor.setExecution()` accepts an `ExecutionOverlay` directly and still accepts old `SimulationSnapshot` values through an internal compatibility projection. The core `FlowSimulator` remains useful for local control-flow exploration, but it does not execute action previews or reproduce every production runner behavior.
100
+
101
+ ## Example replay and development
102
+
103
+ Run `npm run dev`, then open `http://127.0.0.1:4173/examples/index.html`. The command builds before serving, disables browser caching, and watches/reloads library source changes. This avoids using an old `dist/` implementation with newer demo buttons. Static serving after `npm run build` remains supported.
104
+
105
+ Placement and animations start natively. Drag/arrow movements are local view pins, including in read-only views. No placement saving, storage or undo entries are involved. `position` remains an initial layout seed in existing documents; this viewer never persists its adjustments.
106
+
107
+ The replay controls advance **sample observations**, not operations. Approved/declined scenarios show external approval signals and selected branches; failure/timeout scenarios show terminal transfer results. The mock event sequence includes Python output, HTTP status, streamed database row counts, transfer offsets/chunk progress, mail notification and inbound/outbound signals. Other families and unsupported operations are available as addable preview presets. There are no real runner connections or human-response forms in this example.
108
+
109
+ Play / Stop / Reset now live only in the dockable DevTools top bar and call the original replay handlers. Trace reuses the original list/detail controller; Performance inspection shows only the profiler-style top graph, with backend-supplied integer cost (explicitly labeled fixtures in the demo). See [DevTools refactor](devtools-refactor.md) for the real-window sync mechanism and persisted pane/theme keys.