@bpmnkit/plugins 0.0.16 → 0.0.17

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,20 @@
1
+ import type { CanvasPlugin } from "@bpmnkit/canvas";
2
+ import type { BpmnDefinitions } from "@bpmnkit/core";
3
+ export interface PatternAdvisorOptions {
4
+ /** Container element to mount the advisor panel into. */
5
+ container?: HTMLElement;
6
+ /**
7
+ * Called when a fix is applied. Use this to re-serialize and reload the
8
+ * modified definitions (e.g. serialize to XML and call `canvas.load(xml)`).
9
+ */
10
+ onApplyFix?: (defs: BpmnDefinitions, description: string) => void;
11
+ }
12
+ export interface PatternAdvisorPlugin extends CanvasPlugin {
13
+ readonly name: "pattern-advisor";
14
+ /** The side-panel element. Mount it in your dock or sidebar. */
15
+ readonly panel: HTMLElement;
16
+ /** Mount the panel into a container element. */
17
+ mount(container: HTMLElement): void;
18
+ }
19
+ export declare function createPatternAdvisorPlugin(options?: PatternAdvisorOptions): PatternAdvisorPlugin;
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,233 @@
1
+ import { optimize } from "@bpmnkit/core";
2
+ import { injectPatternAdvisorStyles } from "./css.js";
3
+ // ── Plugin factory ──────────────────────────────────────────────────────────
4
+ export function createPatternAdvisorPlugin(options) {
5
+ let canvasApi = null;
6
+ let currentDefs = null;
7
+ let findings = [];
8
+ const dismissed = new Set(); // finding IDs dismissed by user
9
+ const unsubs = [];
10
+ // ── Panel DOM ───────────────────────────────────────────────────────────
11
+ const panelEl = document.createElement("div");
12
+ panelEl.className = "bpmnkit-pa-panel";
13
+ const headerEl = document.createElement("div");
14
+ headerEl.className = "bpmnkit-pa-header";
15
+ const titleEl = document.createElement("span");
16
+ titleEl.className = "bpmnkit-pa-title";
17
+ titleEl.textContent = "Pattern Advisor";
18
+ const countsEl = document.createElement("div");
19
+ countsEl.className = "bpmnkit-pa-counts";
20
+ headerEl.appendChild(titleEl);
21
+ headerEl.appendChild(countsEl);
22
+ const bodyEl = document.createElement("div");
23
+ bodyEl.className = "bpmnkit-pa-body";
24
+ panelEl.appendChild(headerEl);
25
+ panelEl.appendChild(bodyEl);
26
+ // ── Helpers ─────────────────────────────────────────────────────────────
27
+ function clearEl(el) {
28
+ while (el.firstChild !== null)
29
+ el.removeChild(el.firstChild);
30
+ }
31
+ function makeBadge(severity) {
32
+ const badge = document.createElement("span");
33
+ badge.className = `bpmnkit-pa-badge bpmnkit-pa-badge-${severity}`;
34
+ badge.textContent = severity.toUpperCase();
35
+ return badge;
36
+ }
37
+ function makeSeverityTag(severity) {
38
+ const tag = document.createElement("span");
39
+ tag.className = `bpmnkit-pa-severity bpmnkit-pa-severity-${severity}`;
40
+ tag.textContent = severity;
41
+ return tag;
42
+ }
43
+ // ── Canvas badge management ─────────────────────────────────────────────
44
+ const BADGE_CLASSES = [
45
+ "bpmnkit-pa-error-ring",
46
+ "bpmnkit-pa-warning-ring",
47
+ "bpmnkit-pa-info-ring",
48
+ ];
49
+ function clearCanvasBadges() {
50
+ const vp = canvasApi?.viewportEl;
51
+ if (vp === undefined)
52
+ return;
53
+ for (const cls of BADGE_CLASSES) {
54
+ for (const el of vp.querySelectorAll(`.${cls}`)) {
55
+ el.classList.remove(cls);
56
+ }
57
+ }
58
+ }
59
+ function applyCanvasBadges(activeFindings) {
60
+ const vp = canvasApi?.viewportEl;
61
+ if (vp === undefined)
62
+ return;
63
+ clearCanvasBadges();
64
+ // Track the worst severity per element
65
+ const worstSeverity = new Map();
66
+ for (const f of activeFindings) {
67
+ for (const id of f.elementIds) {
68
+ const current = worstSeverity.get(id);
69
+ if (current === undefined ||
70
+ (f.severity === "error" && current !== "error") ||
71
+ (f.severity === "warning" && current === "info")) {
72
+ worstSeverity.set(id, f.severity);
73
+ }
74
+ }
75
+ }
76
+ for (const [elementId, severity] of worstSeverity) {
77
+ const el = vp.querySelector(`[data-bpmnkit-id="${elementId}"]`);
78
+ if (el !== null) {
79
+ el.classList.add(`bpmnkit-pa-${severity}-ring`);
80
+ }
81
+ }
82
+ }
83
+ // ── Rendering ───────────────────────────────────────────────────────────
84
+ function renderPanel() {
85
+ clearEl(countsEl);
86
+ clearEl(bodyEl);
87
+ const active = findings.filter((f) => !dismissed.has(f.id));
88
+ if (active.length === 0) {
89
+ const empty = document.createElement("div");
90
+ empty.className = "bpmnkit-pa-empty";
91
+ empty.textContent =
92
+ currentDefs === null
93
+ ? "Open a process to see pattern suggestions."
94
+ : "No pattern issues found. The process looks good.";
95
+ bodyEl.appendChild(empty);
96
+ clearEl(countsEl);
97
+ return;
98
+ }
99
+ // Summary badges
100
+ const errors = active.filter((f) => f.severity === "error").length;
101
+ const warnings = active.filter((f) => f.severity === "warning").length;
102
+ const infos = active.filter((f) => f.severity === "info").length;
103
+ if (errors > 0) {
104
+ const b = makeBadge("error");
105
+ b.textContent = String(errors);
106
+ countsEl.appendChild(b);
107
+ }
108
+ if (warnings > 0) {
109
+ const b = makeBadge("warning");
110
+ b.textContent = String(warnings);
111
+ countsEl.appendChild(b);
112
+ }
113
+ if (infos > 0) {
114
+ const b = makeBadge("info");
115
+ b.textContent = String(infos);
116
+ countsEl.appendChild(b);
117
+ }
118
+ // Group findings by first elementId (or processId if empty)
119
+ const groups = new Map();
120
+ for (const f of active) {
121
+ const groupKey = f.elementIds[0] ?? f.processId;
122
+ const arr = groups.get(groupKey) ?? [];
123
+ arr.push(f);
124
+ groups.set(groupKey, arr);
125
+ }
126
+ // Sort groups: errors first, then warnings, then info
127
+ const severityOrder = { error: 0, warning: 1, info: 2 };
128
+ const sortedGroups = [...groups.entries()].sort(([, a], [, b]) => {
129
+ const aMin = Math.min(...a.map((f) => severityOrder[f.severity]));
130
+ const bMin = Math.min(...b.map((f) => severityOrder[f.severity]));
131
+ return aMin - bMin;
132
+ });
133
+ for (const [groupKey, groupFindings] of sortedGroups) {
134
+ const groupEl = document.createElement("div");
135
+ groupEl.className = "bpmnkit-pa-group";
136
+ const groupHeader = document.createElement("div");
137
+ groupHeader.className = "bpmnkit-pa-group-header";
138
+ groupHeader.textContent = groupKey;
139
+ groupEl.appendChild(groupHeader);
140
+ // Sort within group: errors first
141
+ const sorted = [...groupFindings].sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
142
+ for (const finding of sorted) {
143
+ const findingEl = document.createElement("div");
144
+ findingEl.className = "bpmnkit-pa-finding";
145
+ findingEl.appendChild(makeSeverityTag(finding.severity));
146
+ const bodyDiv = document.createElement("div");
147
+ bodyDiv.className = "bpmnkit-pa-finding-body";
148
+ const msgEl = document.createElement("div");
149
+ msgEl.className = "bpmnkit-pa-finding-msg";
150
+ msgEl.textContent = finding.message;
151
+ const sugEl = document.createElement("div");
152
+ sugEl.className = "bpmnkit-pa-finding-sug";
153
+ sugEl.textContent = finding.suggestion;
154
+ bodyDiv.appendChild(msgEl);
155
+ bodyDiv.appendChild(sugEl);
156
+ // Action buttons
157
+ const actionsEl = document.createElement("div");
158
+ actionsEl.className = "bpmnkit-pa-finding-actions";
159
+ if (finding.applyFix !== undefined && currentDefs !== null) {
160
+ const fixBtn = document.createElement("button");
161
+ fixBtn.className = "bpmnkit-pa-btn bpmnkit-pa-btn-fix";
162
+ fixBtn.textContent = "Apply Fix";
163
+ const defs = currentDefs;
164
+ const fix = finding.applyFix;
165
+ fixBtn.addEventListener("click", () => {
166
+ const result = fix(defs);
167
+ options?.onApplyFix?.(defs, result.description);
168
+ });
169
+ actionsEl.appendChild(fixBtn);
170
+ }
171
+ const dismissBtn = document.createElement("button");
172
+ dismissBtn.className = "bpmnkit-pa-btn";
173
+ dismissBtn.textContent = "Dismiss";
174
+ dismissBtn.addEventListener("click", () => {
175
+ dismissed.add(finding.id);
176
+ renderPanel();
177
+ applyCanvasBadges(findings.filter((f) => !dismissed.has(f.id)));
178
+ });
179
+ actionsEl.appendChild(dismissBtn);
180
+ bodyDiv.appendChild(actionsEl);
181
+ findingEl.appendChild(bodyDiv);
182
+ groupEl.appendChild(findingEl);
183
+ }
184
+ bodyEl.appendChild(groupEl);
185
+ }
186
+ }
187
+ // ── Analysis ────────────────────────────────────────────────────────────
188
+ function runAnalysis(defs) {
189
+ currentDefs = defs;
190
+ const report = optimize(defs, { categories: ["pattern"] });
191
+ findings = report.findings;
192
+ renderPanel();
193
+ applyCanvasBadges(findings.filter((f) => !dismissed.has(f.id)));
194
+ }
195
+ function clearAnalysis() {
196
+ currentDefs = null;
197
+ findings = [];
198
+ clearCanvasBadges();
199
+ renderPanel();
200
+ }
201
+ // ── CanvasPlugin ────────────────────────────────────────────────────────
202
+ return {
203
+ name: "pattern-advisor",
204
+ panel: panelEl,
205
+ mount(container) {
206
+ container.appendChild(panelEl);
207
+ },
208
+ install(api) {
209
+ canvasApi = api;
210
+ injectPatternAdvisorStyles();
211
+ renderPanel();
212
+ if (options?.container !== undefined) {
213
+ options.container.appendChild(panelEl);
214
+ }
215
+ const onAny = api.on;
216
+ unsubs.push(api.on("diagram:load", (defs) => {
217
+ runAnalysis(defs);
218
+ }), api.on("diagram:clear", () => {
219
+ clearAnalysis();
220
+ }), onAny("diagram:change", (defs) => {
221
+ runAnalysis(defs);
222
+ }));
223
+ },
224
+ uninstall() {
225
+ for (const off of unsubs)
226
+ off();
227
+ clearCanvasBadges();
228
+ panelEl.remove();
229
+ canvasApi = null;
230
+ },
231
+ };
232
+ }
233
+ //# sourceMappingURL=index.js.map
@@ -40,6 +40,20 @@ const CSS = `
40
40
  position: relative;
41
41
  }
42
42
 
43
+ .bpmnkit-runner-chaos-label {
44
+ display: flex;
45
+ align-items: center;
46
+ gap: 4px;
47
+ font-size: 12px;
48
+ color: var(--bpmnkit-fg-muted, rgba(255,255,255,0.55));
49
+ cursor: pointer;
50
+ user-select: none;
51
+ padding: 0 4px;
52
+ }
53
+ .bpmnkit-runner-chaos-label:has(.bpmnkit-runner-chaos-checkbox:checked) {
54
+ color: var(--bpmnkit-warn, #f59e0b);
55
+ }
56
+
43
57
  .bpmnkit-runner-btn {
44
58
  display: flex;
45
59
  align-items: center;
@@ -289,6 +303,45 @@ const CSS = `
289
303
  }
290
304
  .bpmnkit-runner-play-pane--hidden { display: none !important; }
291
305
 
306
+ /* ── Timeline scrubber ───────────────────────────────────────────────────── */
307
+ .bpmnkit-runner-scrubber-row {
308
+ display: flex;
309
+ align-items: center;
310
+ gap: 6px;
311
+ padding: 6px 10px;
312
+ border-bottom: 1px solid rgba(255,255,255,0.06);
313
+ font-size: 11px;
314
+ }
315
+ .bpmnkit-runner-scrubber {
316
+ flex: 1;
317
+ height: 4px;
318
+ accent-color: var(--bpmnkit-accent, #6b9df7);
319
+ cursor: pointer;
320
+ }
321
+ .bpmnkit-runner-scrubber-index {
322
+ color: rgba(255,255,255,0.4);
323
+ white-space: nowrap;
324
+ min-width: 100px;
325
+ text-align: right;
326
+ }
327
+ .bpmnkit-runner-scrubber-live,
328
+ .bpmnkit-runner-scrubber-replay {
329
+ background: none;
330
+ border: 1px solid rgba(255,255,255,0.2);
331
+ border-radius: 4px;
332
+ color: rgba(255,255,255,0.7);
333
+ font-size: 10px;
334
+ padding: 2px 6px;
335
+ cursor: pointer;
336
+ white-space: nowrap;
337
+ }
338
+ .bpmnkit-runner-scrubber-live:hover,
339
+ .bpmnkit-runner-scrubber-replay:hover { border-color: var(--bpmnkit-accent, #6b9df7); color: var(--bpmnkit-accent, #6b9df7); }
340
+ [data-bpmnkit-hud-theme="light"] .bpmnkit-runner-scrubber-row { border-color: rgba(0,0,0,0.08); }
341
+ [data-bpmnkit-hud-theme="light"] .bpmnkit-runner-scrubber-index { color: rgba(0,0,0,0.4); }
342
+ [data-bpmnkit-hud-theme="light"] .bpmnkit-runner-scrubber-live,
343
+ [data-bpmnkit-hud-theme="light"] .bpmnkit-runner-scrubber-replay { border-color: rgba(0,0,0,0.2); color: rgba(0,0,0,0.6); }
344
+
292
345
  .bpmnkit-runner-play-empty {
293
346
  color: rgba(255,255,255,0.25);
294
347
  text-align: center;
@@ -453,6 +506,81 @@ const CSS = `
453
506
  color: rgba(0,0,0,0.4);
454
507
  }
455
508
  [data-bpmnkit-hud-theme="light"] .bpmnkit-runner-play-ivar-add:hover { border-color: var(--bpmnkit-accent, #1a56db); color: var(--bpmnkit-accent, #1a56db); }
509
+
510
+ /* ── Tests tab ───────────────────────────────────────────────────────────── */
511
+ .bpmnkit-runner-tests-header {
512
+ display: flex;
513
+ gap: 6px;
514
+ align-items: center;
515
+ padding-bottom: 8px;
516
+ border-bottom: 1px solid rgba(255,255,255,0.06);
517
+ margin-bottom: 8px;
518
+ }
519
+ .bpmnkit-runner-tests-run-all, .bpmnkit-runner-tests-add { font-size: 11px; padding: 3px 8px; }
520
+ .bpmnkit-runner-tests-row {
521
+ display: flex;
522
+ align-items: center;
523
+ gap: 6px;
524
+ padding: 4px 0;
525
+ border-bottom: 1px solid rgba(255,255,255,0.04);
526
+ }
527
+ .bpmnkit-runner-tests-pass .bpmnkit-runner-tests-status { color: var(--bpmnkit-success, #22c55e); }
528
+ .bpmnkit-runner-tests-fail .bpmnkit-runner-tests-status { color: var(--bpmnkit-danger, #f87171); }
529
+ .bpmnkit-runner-tests-status { font-size: 14px; width: 16px; text-align: center; }
530
+ .bpmnkit-runner-tests-name {
531
+ flex: 1;
532
+ background: none;
533
+ border: none;
534
+ border-bottom: 1px solid rgba(255,255,255,0.1);
535
+ color: inherit;
536
+ font-size: 12px;
537
+ padding: 2px 4px;
538
+ }
539
+ .bpmnkit-runner-tests-name:focus { outline: none; border-color: var(--bpmnkit-accent, #6b9df7); }
540
+ .bpmnkit-runner-tests-run-one, .bpmnkit-runner-tests-del {
541
+ background: none;
542
+ border: none;
543
+ color: rgba(255,255,255,0.4);
544
+ cursor: pointer;
545
+ padding: 2px 4px;
546
+ font-size: 12px;
547
+ }
548
+ .bpmnkit-runner-tests-run-one:hover { color: var(--bpmnkit-accent, #6b9df7); }
549
+ .bpmnkit-runner-tests-del:hover { color: var(--bpmnkit-danger, #f87171); }
550
+ .bpmnkit-runner-tests-diff {
551
+ padding: 4px 0 4px 22px;
552
+ font-size: 11px;
553
+ color: rgba(255,255,255,0.5);
554
+ }
555
+ .bpmnkit-runner-tests-diff-row { padding: 1px 0; }
556
+ .bpmnkit-runner-tests-diff-error { color: var(--bpmnkit-danger, #f87171); }
557
+ [data-bpmnkit-hud-theme="light"] .bpmnkit-runner-tests-header { border-color: rgba(0,0,0,0.08); }
558
+ [data-bpmnkit-hud-theme="light"] .bpmnkit-runner-tests-row { border-color: rgba(0,0,0,0.04); }
559
+ [data-bpmnkit-hud-theme="light"] .bpmnkit-runner-tests-name { border-color: rgba(0,0,0,0.15); }
560
+ [data-bpmnkit-hud-theme="light"] .bpmnkit-runner-tests-run-one,
561
+ [data-bpmnkit-hud-theme="light"] .bpmnkit-runner-tests-del { color: rgba(0,0,0,0.3); }
562
+ [data-bpmnkit-hud-theme="light"] .bpmnkit-runner-tests-diff { color: rgba(0,0,0,0.4); }
563
+
564
+ /* ── Tests tab extra buttons ─────────────────────────────────────────────── */
565
+ .bpmnkit-runner-tests-gen, .bpmnkit-runner-tests-chaos-import { font-size: 11px; padding: 3px 8px; }
566
+ .bpmnkit-runner-tests-chaos-import {
567
+ color: var(--bpmnkit-warn, #f59e0b);
568
+ border-color: var(--bpmnkit-warn, #f59e0b);
569
+ }
570
+ .bpmnkit-runner-tests-chaos-import:hover {
571
+ background: rgba(245,158,11,0.12);
572
+ }
573
+
574
+ /* ── Chaos run summary banner ─────────────────────────────────────────────── */
575
+ .bpmnkit-runner-chaos-summary {
576
+ font-size: 11px;
577
+ padding: 6px 10px;
578
+ margin-bottom: 8px;
579
+ border-radius: 5px;
580
+ background: rgba(245,158,11,0.12);
581
+ border: 1px solid var(--bpmnkit-warn, #f59e0b);
582
+ color: var(--bpmnkit-warn, #f59e0b);
583
+ }
456
584
  `;
457
585
  export function injectProcessRunnerStyles() {
458
586
  if (typeof document === "undefined")
@@ -26,6 +26,38 @@ interface TokenHighlightLike {
26
26
  setError(elementId: string): void;
27
27
  };
28
28
  }
29
+ /** Minimal scenario interface — avoids hard dep on @bpmnkit/engine. */
30
+ export interface ScenarioLike {
31
+ id: string;
32
+ name: string;
33
+ processId?: string;
34
+ inputs?: Record<string, unknown>;
35
+ mocks?: Record<string, {
36
+ outputs?: Record<string, unknown>;
37
+ error?: string;
38
+ }>;
39
+ expect?: {
40
+ path?: string[];
41
+ variables?: Record<string, unknown>;
42
+ };
43
+ }
44
+ export interface ScenarioResultLike {
45
+ scenarioId: string;
46
+ scenarioName: string;
47
+ passed: boolean;
48
+ visitedElements: string[];
49
+ finalVariables: Record<string, unknown>;
50
+ errors: Array<{
51
+ elementId?: string;
52
+ message: string;
53
+ }>;
54
+ failures: Array<{
55
+ field: string;
56
+ expected: unknown;
57
+ actual: unknown;
58
+ }>;
59
+ durationMs: number;
60
+ }
29
61
  export interface ProcessRunnerOptions {
30
62
  /** The engine instance used to deploy and execute processes. */
31
63
  engine: EngineLike;
@@ -46,6 +78,26 @@ export interface ProcessRunnerOptions {
46
78
  onExitPlayMode?: () => void;
47
79
  /** Returns the current project ID, used to scope input variable persistence. */
48
80
  getProjectId?: () => string | null;
81
+ /**
82
+ * Optional scenario runner callback. When provided, the Tests tab is active.
83
+ * Inject `runScenario` from `@bpmnkit/engine` here to avoid a hard dep.
84
+ */
85
+ runScenario?: (scenario: ScenarioLike) => Promise<ScenarioResultLike>;
86
+ /**
87
+ * Called on diagram load to check for a companion .bpmn.tests.json sidecar.
88
+ * Return parsed scenarios, or null if no sidecar exists.
89
+ */
90
+ loadSidecarScenarios?: () => Promise<ScenarioLike[] | null>;
91
+ /**
92
+ * Optional AI scenario generator. When provided, a "Generate" button appears
93
+ * in the Tests tab. Should return draft scenarios to add to the list.
94
+ */
95
+ generateScenarios?: () => Promise<ScenarioLike[]>;
96
+ /**
97
+ * Returns the Zeebe job type string for a given element ID.
98
+ * Used to map chaos injections to scenario mocks for export.
99
+ */
100
+ getJobType?: (elementId: string) => string | null;
49
101
  }
50
102
  export declare function createProcessRunnerPlugin(options: ProcessRunnerOptions): CanvasPlugin & {
51
103
  toolbar: HTMLDivElement;