@memberjunction/ng-task-graph-editor 6.1.0-edge.2 → 6.1.0-edge.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 (55) hide show
  1. package/LICENSE +180 -4
  2. package/README.md +197 -0
  3. package/dist/lib/debug-variables.d.ts +27 -0
  4. package/dist/lib/debug-variables.d.ts.map +1 -0
  5. package/dist/lib/debug-variables.js +96 -0
  6. package/dist/lib/debug-variables.js.map +1 -0
  7. package/dist/lib/pane-split.d.ts +11 -0
  8. package/dist/lib/pane-split.d.ts.map +1 -0
  9. package/dist/lib/pane-split.js +36 -0
  10. package/dist/lib/pane-split.js.map +1 -0
  11. package/dist/lib/task-graph-canvas-adapter.d.ts +41 -3
  12. package/dist/lib/task-graph-canvas-adapter.d.ts.map +1 -1
  13. package/dist/lib/task-graph-canvas-adapter.js +175 -36
  14. package/dist/lib/task-graph-canvas-adapter.js.map +1 -1
  15. package/dist/lib/task-graph-debug-toolbar.component.d.ts +27 -0
  16. package/dist/lib/task-graph-debug-toolbar.component.d.ts.map +1 -0
  17. package/dist/lib/task-graph-debug-toolbar.component.js +153 -0
  18. package/dist/lib/task-graph-debug-toolbar.component.js.map +1 -0
  19. package/dist/lib/task-graph-debugger.component.d.ts +96 -0
  20. package/dist/lib/task-graph-debugger.component.d.ts.map +1 -0
  21. package/dist/lib/task-graph-debugger.component.js +371 -0
  22. package/dist/lib/task-graph-debugger.component.js.map +1 -0
  23. package/dist/lib/task-graph-editor.component.d.ts +40 -3
  24. package/dist/lib/task-graph-editor.component.d.ts.map +1 -1
  25. package/dist/lib/task-graph-editor.component.js +83 -10
  26. package/dist/lib/task-graph-editor.component.js.map +1 -1
  27. package/dist/lib/task-graph-editor.module.d.ts +9 -5
  28. package/dist/lib/task-graph-editor.module.d.ts.map +1 -1
  29. package/dist/lib/task-graph-editor.module.js +52 -5
  30. package/dist/lib/task-graph-editor.module.js.map +1 -1
  31. package/dist/lib/task-graph-run-prefs.d.ts +34 -0
  32. package/dist/lib/task-graph-run-prefs.d.ts.map +1 -0
  33. package/dist/lib/task-graph-run-prefs.js +67 -0
  34. package/dist/lib/task-graph-run-prefs.js.map +1 -0
  35. package/dist/lib/task-graph-run-view.component.d.ts +180 -3
  36. package/dist/lib/task-graph-run-view.component.d.ts.map +1 -1
  37. package/dist/lib/task-graph-run-view.component.js +560 -23
  38. package/dist/lib/task-graph-run-view.component.js.map +1 -1
  39. package/dist/lib/task-graph-runtime-source.d.ts +7 -1
  40. package/dist/lib/task-graph-runtime-source.d.ts.map +1 -1
  41. package/dist/lib/task-graph-runtime-source.js +14 -2
  42. package/dist/lib/task-graph-runtime-source.js.map +1 -1
  43. package/dist/lib/task-graph-variables.component.d.ts +23 -0
  44. package/dist/lib/task-graph-variables.component.d.ts.map +1 -0
  45. package/dist/lib/task-graph-variables.component.js +192 -0
  46. package/dist/lib/task-graph-variables.component.js.map +1 -0
  47. package/dist/lib/workflow-debug-host.d.ts +23 -0
  48. package/dist/lib/workflow-debug-host.d.ts.map +1 -0
  49. package/dist/lib/workflow-debug-host.js +101 -0
  50. package/dist/lib/workflow-debug-host.js.map +1 -0
  51. package/dist/public-api.d.ts +7 -0
  52. package/dist/public-api.d.ts.map +1 -1
  53. package/dist/public-api.js +7 -0
  54. package/dist/public-api.js.map +1 -1
  55. package/package.json +11 -10
@@ -16,14 +16,20 @@
16
16
  *
17
17
  * @module @memberjunction/ng-task-graph-editor
18
18
  */
19
- import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, } from '@angular/core';
19
+ import { ChangeDetectionStrategy, Component, EventEmitter, HostListener, Input, Output, } from '@angular/core';
20
20
  import { BaseAngularComponent } from '@memberjunction/ng-base-types';
21
21
  import { LogError, RunView } from '@memberjunction/core';
22
+ import { UUIDsEqual } from '@memberjunction/global';
23
+ import { UserInfoEngine } from '@memberjunction/core-entities';
24
+ import { ToPaneSizePair } from './pane-split';
25
+ import { TaskGraphRunPrefsStore } from './task-graph-run-prefs';
22
26
  import { GraphLayoutBounds, LayoutGraphNodes, ProjectTaskRowsToSpec, } from '@memberjunction/ai-core-plus';
23
- import { BuildRuntimeStatus } from './task-graph-runtime-source';
27
+ import { BuildRuntimeStatus, IsRuntimeSettled, NormalizeRuntimeState } from './task-graph-runtime-source';
24
28
  import * as i0 from "@angular/core";
25
29
  import * as i1 from "@memberjunction/ng-ui-components";
26
- import * as i2 from "./task-graph-editor.component";
30
+ import * as i2 from "angular-split";
31
+ import * as i3 from "./task-graph-variables.component";
32
+ import * as i4 from "./task-graph-editor.component";
27
33
  function TaskGraphRunViewComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
28
34
  i0.ɵɵelement(0, "mj-alert", 0);
29
35
  } if (rf & 2) {
@@ -39,7 +45,7 @@ function TaskGraphRunViewComponent_Conditional_2_Template(rf, ctx) { if (rf & 1)
39
45
  i0.ɵɵelement(0, "mj-empty-state", 2);
40
46
  } }
41
47
  function TaskGraphRunViewComponent_Conditional_3_Conditional_4_Template(rf, ctx) { if (rf & 1) {
42
- i0.ɵɵelementStart(0, "span", 6);
48
+ i0.ɵɵelementStart(0, "span", 7);
43
49
  i0.ɵɵtext(1);
44
50
  i0.ɵɵelementEnd();
45
51
  } if (rf & 2) {
@@ -47,26 +53,114 @@ function TaskGraphRunViewComponent_Conditional_3_Conditional_4_Template(rf, ctx)
47
53
  i0.ɵɵadvance();
48
54
  i0.ɵɵtextInterpolate1("", ctx_r0.SkippedCount, " not taken");
49
55
  } }
56
+ function TaskGraphRunViewComponent_Conditional_3_Conditional_5_Template(rf, ctx) { if (rf & 1) {
57
+ i0.ɵɵelementStart(0, "span", 8);
58
+ i0.ɵɵtext(1, "Finished");
59
+ i0.ɵɵelementEnd();
60
+ } }
61
+ function TaskGraphRunViewComponent_Conditional_3_Conditional_6_Conditional_1_Template(rf, ctx) { if (rf & 1) {
62
+ i0.ɵɵtext(0);
63
+ } if (rf & 2) {
64
+ const ctx_r0 = i0.ɵɵnextContext(3);
65
+ i0.ɵɵtextInterpolate1(" Waiting on ", ctx_r0.WaitingStepName, " ");
66
+ } }
67
+ function TaskGraphRunViewComponent_Conditional_3_Conditional_6_Conditional_2_Template(rf, ctx) { if (rf & 1) {
68
+ i0.ɵɵtext(0, " Waiting to start \u2014 Continue or Step ");
69
+ } }
70
+ function TaskGraphRunViewComponent_Conditional_3_Conditional_6_Template(rf, ctx) { if (rf & 1) {
71
+ i0.ɵɵelementStart(0, "span", 9);
72
+ i0.ɵɵconditionalCreate(1, TaskGraphRunViewComponent_Conditional_3_Conditional_6_Conditional_1_Template, 1, 1)(2, TaskGraphRunViewComponent_Conditional_3_Conditional_6_Conditional_2_Template, 1, 0);
73
+ i0.ɵɵelementEnd();
74
+ } if (rf & 2) {
75
+ const ctx_r0 = i0.ɵɵnextContext(2);
76
+ i0.ɵɵadvance();
77
+ i0.ɵɵconditional(ctx_r0.PausedAtTaskID ? 1 : 2);
78
+ } }
79
+ function TaskGraphRunViewComponent_Conditional_3_Conditional_7_Template(rf, ctx) { if (rf & 1) {
80
+ i0.ɵɵelementStart(0, "span", 10);
81
+ i0.ɵɵtext(1);
82
+ i0.ɵɵelementEnd();
83
+ } if (rf & 2) {
84
+ const ctx_r0 = i0.ɵɵnextContext(2);
85
+ i0.ɵɵadvance();
86
+ i0.ɵɵtextInterpolate1("", ctx_r0.RunningCount, " running");
87
+ } }
88
+ function TaskGraphRunViewComponent_Conditional_3_Conditional_8_Template(rf, ctx) { if (rf & 1) {
89
+ const _r3 = i0.ɵɵgetCurrentView();
90
+ i0.ɵɵelementStart(0, "button", 23);
91
+ i0.ɵɵlistener("click", function TaskGraphRunViewComponent_Conditional_3_Conditional_8_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r3); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.OnToggleSelectedBreakpoint()); });
92
+ i0.ɵɵelement(1, "i", 24);
93
+ i0.ɵɵelementEnd();
94
+ i0.ɵɵelementStart(2, "span", 25);
95
+ i0.ɵɵtext(3);
96
+ i0.ɵɵelementEnd();
97
+ } if (rf & 2) {
98
+ const ctx_r0 = i0.ɵɵnextContext(2);
99
+ i0.ɵɵclassProp("run-view-bp-on", ctx_r0.HasSelectedBreakpoint);
100
+ i0.ɵɵproperty("title", ctx_r0.HasSelectedBreakpoint ? "Remove breakpoint" : "Break before this step");
101
+ i0.ɵɵattribute("aria-pressed", ctx_r0.HasSelectedBreakpoint)("aria-label", ctx_r0.HasSelectedBreakpoint ? "Remove breakpoint from " + ctx_r0.SelectedTaskName : "Add breakpoint on " + ctx_r0.SelectedTaskName);
102
+ i0.ɵɵadvance();
103
+ i0.ɵɵclassProp("fa-solid", ctx_r0.HasSelectedBreakpoint)("fa-regular", !ctx_r0.HasSelectedBreakpoint);
104
+ i0.ɵɵadvance(2);
105
+ i0.ɵɵtextInterpolate(ctx_r0.SelectedTaskName);
106
+ } }
107
+ function TaskGraphRunViewComponent_Conditional_3_Conditional_10_Template(rf, ctx) { if (rf & 1) {
108
+ const _r4 = i0.ɵɵgetCurrentView();
109
+ i0.ɵɵelementStart(0, "button", 26);
110
+ i0.ɵɵlistener("click", function TaskGraphRunViewComponent_Conditional_3_Conditional_10_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r4); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.OnToggleInvocation()); });
111
+ i0.ɵɵelement(1, "i", 27);
112
+ i0.ɵɵelementStart(2, "span");
113
+ i0.ɵɵtext(3, "Data");
114
+ i0.ɵɵelementEnd()();
115
+ } }
50
116
  function TaskGraphRunViewComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
51
117
  const _r2 = i0.ɵɵgetCurrentView();
52
- i0.ɵɵelementStart(0, "div", 3)(1, "div", 4)(2, "span", 5);
118
+ i0.ɵɵelementStart(0, "div", 4)(1, "div", 5)(2, "span", 6);
53
119
  i0.ɵɵtext(3);
54
120
  i0.ɵɵelementEnd();
55
- i0.ɵɵconditionalCreate(4, TaskGraphRunViewComponent_Conditional_3_Conditional_4_Template, 2, 1, "span", 6);
121
+ i0.ɵɵconditionalCreate(4, TaskGraphRunViewComponent_Conditional_3_Conditional_4_Template, 2, 1, "span", 7);
122
+ i0.ɵɵconditionalCreate(5, TaskGraphRunViewComponent_Conditional_3_Conditional_5_Template, 2, 0, "span", 8)(6, TaskGraphRunViewComponent_Conditional_3_Conditional_6_Template, 3, 1, "span", 9)(7, TaskGraphRunViewComponent_Conditional_3_Conditional_7_Template, 2, 1, "span", 10);
123
+ i0.ɵɵconditionalCreate(8, TaskGraphRunViewComponent_Conditional_3_Conditional_8_Template, 4, 10);
124
+ i0.ɵɵelementEnd();
125
+ i0.ɵɵelementStart(9, "div", 11);
126
+ i0.ɵɵconditionalCreate(10, TaskGraphRunViewComponent_Conditional_3_Conditional_10_Template, 4, 0, "button", 12);
127
+ i0.ɵɵelementStart(11, "as-split", 13);
128
+ i0.ɵɵlistener("dragEnd", function TaskGraphRunViewComponent_Conditional_3_Template_as_split_dragEnd_11_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnInvocationSplitDragEnd($event.sizes)); });
129
+ i0.ɵɵelementStart(12, "as-split-area", 14)(13, "div", 15)(14, "div", 16)(15, "span");
130
+ i0.ɵɵtext(16, "Data");
56
131
  i0.ɵɵelementEnd();
57
- i0.ɵɵelementStart(5, "div", 7)(6, "mj-task-graph-editor", 8);
58
- i0.ɵɵlistener("SelectionChanged", function TaskGraphRunViewComponent_Conditional_3_Template_mj_task_graph_editor_SelectionChanged_6_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnSelectionChanged($event)); });
59
- i0.ɵɵelementEnd()()();
132
+ i0.ɵɵelementStart(17, "button", 17);
133
+ i0.ɵɵlistener("click", function TaskGraphRunViewComponent_Conditional_3_Template_button_click_17_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnToggleInvocation()); });
134
+ i0.ɵɵelement(18, "i", 18);
135
+ i0.ɵɵelementEnd()();
136
+ i0.ɵɵelement(19, "mj-task-graph-variables", 19);
137
+ i0.ɵɵelementEnd()();
138
+ i0.ɵɵelementStart(20, "as-split-area", 20)(21, "div", 21)(22, "mj-task-graph-editor", 22);
139
+ i0.ɵɵlistener("SelectionChanged", function TaskGraphRunViewComponent_Conditional_3_Template_mj_task_graph_editor_SelectionChanged_22_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnSelectionChanged($event)); })("ConnectionSelected", function TaskGraphRunViewComponent_Conditional_3_Template_mj_task_graph_editor_ConnectionSelected_22_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnConnectionSelected($event)); })("LegendToggled", function TaskGraphRunViewComponent_Conditional_3_Template_mj_task_graph_editor_LegendToggled_22_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.LegendToggled.emit($event)); })("BeforeContextMenu", function TaskGraphRunViewComponent_Conditional_3_Template_mj_task_graph_editor_BeforeContextMenu_22_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnBeforeContextMenu($event)); })("AfterContextMenuAction", function TaskGraphRunViewComponent_Conditional_3_Template_mj_task_graph_editor_AfterContextMenuAction_22_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnAfterContextMenuAction($event)); });
140
+ i0.ɵɵelementEnd()()()()()();
60
141
  } if (rf & 2) {
61
142
  const ctx_r0 = i0.ɵɵnextContext();
143
+ i0.ɵɵstyleProp("height", ctx_r0.Height);
62
144
  i0.ɵɵadvance(3);
63
145
  i0.ɵɵtextInterpolate2("", ctx_r0.CompletedCount, " of ", ctx_r0.TotalCount, " complete");
64
146
  i0.ɵɵadvance();
65
147
  i0.ɵɵconditional(ctx_r0.SkippedCount > 0 ? 4 : -1);
66
148
  i0.ɵɵadvance();
67
- i0.ɵɵstyleProp("height", ctx_r0.Height);
149
+ i0.ɵɵconditional(ctx_r0.IsSettled ? 5 : ctx_r0.GraphPaused ? 6 : ctx_r0.RunningCount > 0 ? 7 : -1);
150
+ i0.ɵɵadvance(3);
151
+ i0.ɵɵconditional(ctx_r0.AllowBreakpointEditing && ctx_r0.SelectedTaskID ? 8 : -1);
152
+ i0.ɵɵadvance(2);
153
+ i0.ɵɵconditional(ctx_r0.ShowVariables && !ctx_r0.Prefs.InvocationOpen ? 10 : -1);
154
+ i0.ɵɵadvance();
155
+ i0.ɵɵproperty("gutterSize", 8);
68
156
  i0.ɵɵadvance();
69
- i0.ɵɵproperty("Spec", ctx_r0.Spec)("RuntimeStatus", ctx_r0.RuntimeStatus)("NodePositions", ctx_r0.Positions)("ReadOnly", true)("ShowToolbar", false)("ShowPalette", false)("ShowProperties", false)("ShowMinimap", false)("ShowStatusBar", false);
157
+ i0.ɵɵproperty("size", ctx_r0.InvocationPaneSize)("minSize", 14)("visible", ctx_r0.InvocationPaneOpen);
158
+ i0.ɵɵadvance(7);
159
+ i0.ɵɵproperty("InputPayload", ctx_r0.SelectedTask == null ? null : ctx_r0.SelectedTask.InputPayload)("OutputPayload", ctx_r0.SelectedTask == null ? null : ctx_r0.SelectedTask.OutputPayload)("Invocation", ctx_r0.Invocation);
160
+ i0.ɵɵadvance();
161
+ i0.ɵɵproperty("size", ctx_r0.CanvasPaneSize)("minSize", 40);
162
+ i0.ɵɵadvance(2);
163
+ i0.ɵɵproperty("Spec", ctx_r0.Spec)("RuntimeStatus", ctx_r0.RuntimeStatus)("NodePositions", ctx_r0.Positions)("DebugOverlay", ctx_r0.Overlay)("ReadOnly", true)("ShowToolbar", ctx_r0.ShowToolbar)("ToolbarVisibility", ctx_r0.ToolbarVisibility)("ToolbarAlign", ctx_r0.ToolbarAlign)("ShowPalette", false)("ShowProperties", false)("ShowMinimap", false)("ShowStatusBar", false)("ShowLegend", ctx_r0.ShowLegend);
70
164
  } }
71
165
  /** Statuses at which a graph has stopped moving, so polling can stop with it. */
72
166
  const SETTLED = new Set(['Complete', 'Failed', 'Cancelled', 'Skipped']);
@@ -82,6 +176,7 @@ export class TaskGraphRunViewComponent extends BaseAngularComponent {
82
176
  if (value === this.parentTaskID)
83
177
  return;
84
178
  this.parentTaskID = value;
179
+ this.settledEmitted = false;
85
180
  void this.load();
86
181
  }
87
182
  get ParentTaskID() {
@@ -100,9 +195,136 @@ export class TaskGraphRunViewComponent extends BaseAngularComponent {
100
195
  PollIntervalSeconds = 3;
101
196
  /** Height of the canvas. Hosts embed this in very different amounts of space. */
102
197
  Height = '320px';
198
+ /**
199
+ * Whether the canvas legend shows. **Off by default here**, unlike the editor.
200
+ *
201
+ * The legend explains the authoring vocabulary — what a conditional edge means, what a duplicate
202
+ * default looks like. That is what someone drawing a graph needs. A run view answers a different
203
+ * question, "what happened", which the legend helps with not at all while covering a corner of
204
+ * the canvas the graph is usually occupying.
205
+ */
206
+ ShowLegend = false;
207
+ /**
208
+ * Whether the canvas toolbar rides above the graph.
209
+ *
210
+ * Debug and other run hosts leave this on and set `ToolbarVisibility` to `minimized` so the
211
+ * graph is the picture, not a floating tool strip. The chip in the corner restores the bar.
212
+ */
213
+ ShowToolbar = true;
214
+ ToolbarVisibility = 'minimized';
215
+ ToolbarAlign = 'left';
216
+ /**
217
+ * The latest live dispatcher frame, handed in by the host.
218
+ *
219
+ * The host owns the subscription (`GraphQLDataProvider.TaskGraphFrames` or anything shaped like
220
+ * it) and binds each arrival here; the widget folds it into the overlay. Frames are advisory —
221
+ * a task frame patches the node's status in place for sub-second feedback, and the frames that
222
+ * imply CASCADES (a skip, a block, settlement) trigger a debounced row reload, because the
223
+ * dispatcher's propagation writes rows this component cannot infer from one frame. Rows remain
224
+ * the truth; the existing poll (if enabled) remains the safety net.
225
+ */
226
+ set LiveFrame(frame) {
227
+ if (!frame)
228
+ return;
229
+ this.foldFrame(frame);
230
+ }
231
+ /**
232
+ * Replay: render the run as it stood at this moment, reconstructed from each step's
233
+ * `StartedAt`/`CompletedAt`. Null (the default) renders the present. A step that had not
234
+ * started reads Pending; one mid-flight reads In Progress; one already finished reads its
235
+ * final status — the same three answers the rows would have given a viewer at that time.
236
+ */
237
+ set ReplayAt(value) {
238
+ if (value?.getTime() === this.replayAt?.getTime())
239
+ return;
240
+ this.replayAt = value;
241
+ this.applyReplay();
242
+ }
243
+ get ReplayAt() {
244
+ return this.replayAt;
245
+ }
246
+ /**
247
+ * Armed breakpoint task IDs, from the parent row's `$.debug.breakpoints`. Empty by default so
248
+ * embeds (agent-run timeline, test harness) never grow debugger chrome they did not ask for.
249
+ */
250
+ set Breakpoints(value) {
251
+ this.breakpoints = value ?? [];
252
+ this.rebuildOverlay();
253
+ }
254
+ get Breakpoints() {
255
+ return this.breakpoints;
256
+ }
257
+ /** The step a breakpoint actually stopped on. */
258
+ set PausedAtTaskID(value) {
259
+ this.pausedAtTaskID = value;
260
+ if (value)
261
+ this.SelectedTaskID = value;
262
+ this.rebuildOverlay();
263
+ }
264
+ get PausedAtTaskID() {
265
+ return this.pausedAtTaskID;
266
+ }
267
+ /**
268
+ * The graph is claim-gated. Combined with `PausedAtTaskID` so start-paused (no specific
269
+ * step yet) can still light the entry node as waiting.
270
+ */
271
+ set GraphPaused(value) {
272
+ this.graphPaused = value;
273
+ this.rebuildOverlay();
274
+ }
275
+ get GraphPaused() {
276
+ return this.graphPaused;
277
+ }
278
+ /** Operator-forced edge verdicts, keyed by `MJ: Task Dependencies` row ID. */
279
+ set EdgeOverrides(value) {
280
+ this.edgeOverrides = value ?? {};
281
+ this.rebuildOverlay();
282
+ }
283
+ get EdgeOverrides() {
284
+ return this.edgeOverrides;
285
+ }
286
+ /**
287
+ * Whether this host may arm/disarm breakpoints from the selected-step control.
288
+ * Off by default — the run view also renders where those controls would be wrong.
289
+ */
290
+ AllowBreakpointEditing = false;
291
+ /** Compact debug key under the summary line. Off unless the host is a debugger. */
292
+ ShowDebugLegend = false;
293
+ /** VS Code VARIABLES pane to the left of the canvas. Off unless the host is debugging. */
294
+ ShowVariables = false;
295
+ /** The parent bag's `data` / `context` roots, for the Invocation scope. */
296
+ Invocation = null;
103
297
  NodeSelected = new EventEmitter();
298
+ /** The legend was toggled from the toolbar, so a host can remember the choice. */
299
+ LegendToggled = new EventEmitter();
104
300
  /** Emitted once, when every step has reached a terminal status. */
105
301
  Settled = new EventEmitter();
302
+ /** A connection was clicked. `EdgeID` is the dependency row id when the projection carried one. */
303
+ ConnectionSelected = new EventEmitter();
304
+ /** Intent only — the host owns `SetBreakpoints`. */
305
+ BreakpointToggled = new EventEmitter();
306
+ /** Intent only — the host owns `OverrideEdge`. */
307
+ EdgeOverrideRequested = new EventEmitter();
308
+ /** What each running step says it is doing, from `NodeProgress` frames. Keyed by task row id. */
309
+ LiveActivity = new Map();
310
+ /** The last step the person selected, so the breakpoint control has a target. */
311
+ SelectedTaskID = null;
312
+ /** Stable overlay object — rebuilt only when debug inputs change, so the editor does not re-project every CD cycle. */
313
+ Overlay = { showConditions: true };
314
+ breakpoints = [];
315
+ pausedAtTaskID = null;
316
+ graphPaused = false;
317
+ edgeOverrides = {};
318
+ /** `Settled` is once-per-parent — a finished poll must not re-fire the host. */
319
+ settledEmitted = false;
320
+ /** Left data pane + split sizes. One JSON bag in `MJ: User Settings`. */
321
+ get Prefs() {
322
+ return this.prefsStore.Value;
323
+ }
324
+ prefsStore = new TaskGraphRunPrefsStore({
325
+ Get: () => undefined,
326
+ Set: () => { },
327
+ });
106
328
  Spec = null;
107
329
  RuntimeStatus = null;
108
330
  Positions = new Map();
@@ -111,25 +333,69 @@ export class TaskGraphRunViewComponent extends BaseAngularComponent {
111
333
  /** Rows by id, so a selection event can hand the host the whole task rather than an id. */
112
334
  taskByID = new Map();
113
335
  parentTaskID = null;
336
+ replayAt = null;
114
337
  pollTimer = null;
338
+ frameReloadTimer = null;
115
339
  destroyed = false;
116
340
  constructor(cdr) {
117
341
  super();
118
342
  this.cdr = cdr;
343
+ try {
344
+ this.prefsStore = new TaskGraphRunPrefsStore({
345
+ Get: (key) => UserInfoEngine.Instance.GetSetting(key),
346
+ Set: (key, value) => UserInfoEngine.Instance.SetSettingDebounced(key, value),
347
+ });
348
+ this.prefsStore.Restore();
349
+ }
350
+ catch {
351
+ // Engine not configured yet (unit tests, pre-bootstrap) — keep the default.
352
+ }
353
+ }
354
+ get InvocationPaneOpen() {
355
+ return this.ShowVariables && this.Prefs.InvocationOpen;
356
+ }
357
+ get InvocationPaneSize() {
358
+ return this.InvocationPaneOpen ? this.Prefs.InvocationSplit[0] : 0;
359
+ }
360
+ get CanvasPaneSize() {
361
+ return this.InvocationPaneOpen ? this.Prefs.InvocationSplit[1] : 100;
362
+ }
363
+ OnToggleInvocation() {
364
+ this.prefsStore.SetInvocationOpen(!this.Prefs.InvocationOpen);
365
+ this.cdr.markForCheck();
366
+ }
367
+ OnInvocationSplitDragEnd(sizes) {
368
+ const pair = ToPaneSizePair(sizes);
369
+ if (!pair)
370
+ return;
371
+ this.prefsStore.SetInvocationSplit(pair);
372
+ this.cdr.markForCheck();
119
373
  }
120
374
  ngOnDestroy() {
121
375
  this.destroyed = true;
122
376
  this.stopPolling();
377
+ if (this.frameReloadTimer) {
378
+ clearTimeout(this.frameReloadTimer);
379
+ this.frameReloadTimer = null;
380
+ }
123
381
  }
124
- /** Steps in a terminal state, for the host's summary line. */
382
+ /** Steps in a terminal state, for the host's summary line. Uses the same map the canvas paints. */
125
383
  get CompletedCount() {
126
- return [...this.taskByID.values()].filter((t) => t.Status === 'Complete').length;
384
+ return this.countRuntime('Complete');
127
385
  }
128
386
  get TotalCount() {
129
- return this.taskByID.size;
387
+ return this.Spec?.tasks.length ?? this.taskByID.size;
130
388
  }
131
389
  get SkippedCount() {
132
- return [...this.taskByID.values()].filter((t) => t.Status === 'Skipped').length;
390
+ return this.countRuntime('Skipped');
391
+ }
392
+ get RunningCount() {
393
+ return this.countRuntime('In Progress');
394
+ }
395
+ get IsSettled() {
396
+ if (!this.Spec || this.Spec.tasks.length === 0)
397
+ return false;
398
+ return IsRuntimeSettled(this.RuntimeStatus ?? {}, this.Spec.tasks.map((t) => t.tempId));
133
399
  }
134
400
  /**
135
401
  * A node was selected on the canvas.
@@ -142,7 +408,142 @@ export class TaskGraphRunViewComponent extends BaseAngularComponent {
142
408
  const tempId = args?.Task?.tempId;
143
409
  if (!tempId)
144
410
  return;
411
+ this.SelectedTaskID = tempId;
145
412
  this.NodeSelected.emit({ TaskID: tempId, Task: this.taskByID.get(tempId) ?? null });
413
+ this.cdr.markForCheck();
414
+ }
415
+ OnConnectionSelected(connection) {
416
+ if (!connection) {
417
+ this.ConnectionSelected.emit({ EdgeID: null, FromTaskID: '', ToTaskID: '' });
418
+ return;
419
+ }
420
+ const data = connection.Data ?? {};
421
+ const edgeID = typeof data['EdgeID'] === 'string' ? data['EdgeID'] : null;
422
+ const from = typeof data['FromTempId'] === 'string' ? data['FromTempId'] : connection.SourceNodeID;
423
+ const to = typeof data['ToTempId'] === 'string' ? data['ToTempId'] : connection.TargetNodeID;
424
+ this.ConnectionSelected.emit({
425
+ EdgeID: edgeID,
426
+ FromTaskID: from,
427
+ ToTaskID: to,
428
+ Condition: connection.Condition,
429
+ });
430
+ }
431
+ countRuntime(state) {
432
+ if (!this.RuntimeStatus) {
433
+ return [...this.taskByID.values()].filter((t) => NormalizeRuntimeState(t.Status) === state).length;
434
+ }
435
+ return Object.values(this.RuntimeStatus).filter((s) => s === state).length;
436
+ }
437
+ get HasSelectedBreakpoint() {
438
+ return !!this.SelectedTaskID && this.hasBreakpoint(this.SelectedTaskID);
439
+ }
440
+ get SelectedTaskName() {
441
+ if (!this.SelectedTaskID)
442
+ return '';
443
+ return this.taskByID.get(this.SelectedTaskID)?.Name ?? 'this step';
444
+ }
445
+ get WaitingStepName() {
446
+ if (this.pausedAtTaskID) {
447
+ return this.taskByID.get(this.pausedAtTaskID)?.Name
448
+ ?? this.Spec?.tasks.find((t) => t.tempId === this.pausedAtTaskID)?.name
449
+ ?? 'this step';
450
+ }
451
+ const entry = this.Spec?.tasks.find((t) => (t.dependsOn?.length ?? 0) === 0);
452
+ return entry?.name ?? 'the first step';
453
+ }
454
+ get SelectedTask() {
455
+ return this.SelectedTaskID ? this.taskByID.get(this.SelectedTaskID) ?? null : null;
456
+ }
457
+ OnToggleSelectedBreakpoint() {
458
+ if (!this.AllowBreakpointEditing || !this.SelectedTaskID)
459
+ return;
460
+ this.BreakpointToggled.emit({
461
+ TaskID: this.SelectedTaskID,
462
+ Enabled: !this.HasSelectedBreakpoint,
463
+ });
464
+ }
465
+ /** Debug attaches here: replace Edit/Remove with breakpoint / path-override items. */
466
+ OnBeforeContextMenu(event) {
467
+ if (!this.AllowBreakpointEditing) {
468
+ event.Cancel = true;
469
+ return;
470
+ }
471
+ if (event.Target === 'node' && event.Node) {
472
+ const on = this.hasBreakpoint(event.Node.ID);
473
+ event.Items = [{
474
+ ID: 'toggle-breakpoint',
475
+ Label: on ? 'Remove Breakpoint' : 'Add Breakpoint',
476
+ Icon: 'fa-circle',
477
+ Shortcut: 'F9',
478
+ }];
479
+ return;
480
+ }
481
+ if (event.Target === 'connection' && event.Connection?.Condition?.trim()) {
482
+ const edgeID = typeof event.Connection.Data?.['EdgeID'] === 'string'
483
+ ? event.Connection.Data['EdgeID']
484
+ : null;
485
+ if (!edgeID) {
486
+ event.Cancel = true;
487
+ return;
488
+ }
489
+ event.Items = [
490
+ { ID: 'force-true', Label: 'Take this path', Icon: 'fa-check' },
491
+ { ID: 'force-false', Label: 'Skip this path', Icon: 'fa-xmark' },
492
+ { ID: 'force-clear', Label: 'Clear override', Icon: 'fa-rotate-left' },
493
+ ];
494
+ return;
495
+ }
496
+ event.Cancel = true;
497
+ }
498
+ OnAfterContextMenuAction(event) {
499
+ if (event.ActionID === 'toggle-breakpoint' && event.Node) {
500
+ this.BreakpointToggled.emit({
501
+ TaskID: event.Node.ID,
502
+ Enabled: !this.hasBreakpoint(event.Node.ID),
503
+ });
504
+ return;
505
+ }
506
+ const edgeID = typeof event.Connection?.Data?.['EdgeID'] === 'string'
507
+ ? event.Connection.Data['EdgeID']
508
+ : null;
509
+ if (!edgeID)
510
+ return;
511
+ if (event.ActionID === 'force-true')
512
+ this.EdgeOverrideRequested.emit({ EdgeID: edgeID, Verdict: 'true' });
513
+ if (event.ActionID === 'force-false')
514
+ this.EdgeOverrideRequested.emit({ EdgeID: edgeID, Verdict: 'false' });
515
+ if (event.ActionID === 'force-clear')
516
+ this.EdgeOverrideRequested.emit({ EdgeID: edgeID, Verdict: null });
517
+ }
518
+ hasBreakpoint(taskID) {
519
+ return this.breakpoints.some((id) => UUIDsEqual(id, taskID));
520
+ }
521
+ OnDebugHotkey(event) {
522
+ if (!this.AllowBreakpointEditing)
523
+ return;
524
+ if (isTypingTarget(event.target))
525
+ return;
526
+ if (event.key === 'F9') {
527
+ event.preventDefault();
528
+ this.OnToggleSelectedBreakpoint();
529
+ }
530
+ }
531
+ rebuildOverlay() {
532
+ this.Overlay = {
533
+ breakpoints: this.breakpoints,
534
+ // A finished graph is not "paused here" — the durable bag can still name the last
535
+ // breakpoint after every step is terminal.
536
+ pausedAtTaskID: this.IsSettled ? null : this.pausedAtTaskID,
537
+ paused: !this.IsSettled && this.graphPaused,
538
+ edgeOverrides: this.edgeOverrides,
539
+ showConditions: true,
540
+ };
541
+ }
542
+ emitSettledOnce() {
543
+ if (this.settledEmitted || !this.IsSettled)
544
+ return;
545
+ this.settledEmitted = true;
546
+ this.Settled.emit();
146
547
  }
147
548
  /**
148
549
  * Reads the graph and projects it onto the canvas.
@@ -194,10 +595,10 @@ export class TaskGraphRunViewComponent extends BaseAngularComponent {
194
595
  const taskRows = (tasks.Results ?? []);
195
596
  const depRows = (deps.Success ? (deps.Results ?? []) : []);
196
597
  this.project(taskRows, depRows);
598
+ this.rebuildOverlay();
599
+ this.emitSettledOnce();
197
600
  if (this.LiveUpdates && !this.isSettled(taskRows))
198
601
  this.schedulePoll();
199
- else if (taskRows.length > 0)
200
- this.Settled.emit();
201
602
  }
202
603
  catch (e) {
203
604
  this.fail(e instanceof Error ? e.message : String(e));
@@ -216,9 +617,37 @@ export class TaskGraphRunViewComponent extends BaseAngularComponent {
216
617
  // case where an id match is exact. Name correlation exists for specs whose tempIds were
217
618
  // never real, and would be needlessly lossy here.
218
619
  const knownIDs = new Set(taskRows.map((t) => t.ID));
219
- this.RuntimeStatus = BuildRuntimeStatus(taskRows, new Map(), knownIDs);
620
+ this.RuntimeStatus = this.replayAt
621
+ ? this.statusAt(this.replayAt)
622
+ : BuildRuntimeStatus(taskRows, new Map(), knownIDs);
220
623
  this.Positions = this.resolvePositions(projection.AuthoredPositions, taskRows, depRows);
221
624
  }
625
+ /** Re-derives the overlay for the current replay position (or the present) from held rows. */
626
+ applyReplay() {
627
+ if (this.taskByID.size === 0)
628
+ return;
629
+ const rows = [...this.taskByID.values()];
630
+ this.RuntimeStatus = this.replayAt
631
+ ? this.statusAt(this.replayAt)
632
+ : BuildRuntimeStatus(rows, new Map(), new Set(rows.map((t) => t.ID)));
633
+ this.cdr.markForCheck();
634
+ }
635
+ /** The overlay as it stood at `moment`, from each row's own timestamps. */
636
+ statusAt(moment) {
637
+ const at = moment.getTime();
638
+ const status = {};
639
+ for (const [id, row] of this.taskByID) {
640
+ const started = row.StartedAt?.getTime?.() ?? (row.StartedAt ? new Date(row.StartedAt).getTime() : null);
641
+ const completed = row.CompletedAt?.getTime?.() ?? (row.CompletedAt ? new Date(row.CompletedAt).getTime() : null);
642
+ if (started == null || started > at)
643
+ status[id] = 'Pending';
644
+ else if (completed == null || completed > at)
645
+ status[id] = 'In Progress';
646
+ else
647
+ status[id] = NormalizeRuntimeState(row.Status);
648
+ }
649
+ return status;
650
+ }
222
651
  /**
223
652
  * The author's arrangement where there is one, a computed layout everywhere else.
224
653
  *
@@ -241,6 +670,67 @@ export class TaskGraphRunViewComponent extends BaseAngularComponent {
241
670
  isSettled(taskRows) {
242
671
  return taskRows.length > 0 && taskRows.every((t) => SETTLED.has(t.Status));
243
672
  }
673
+ /**
674
+ * Folds one live frame into the overlay.
675
+ *
676
+ * Direct status frames patch in place — a new `RuntimeStatus` object, because the canvas is
677
+ * OnPush and mutation would render nothing. Frames whose consequences the dispatcher computes
678
+ * across the whole graph (skips cascade, blocks propagate, settlement rolls up) reload the rows
679
+ * instead: inferring a cascade client-side would be a second implementation of the engine's
680
+ * rules, free to drift.
681
+ */
682
+ foldFrame(frame) {
683
+ switch (frame.kind) {
684
+ case 'TaskStarted':
685
+ case 'TaskCompleted':
686
+ case 'TaskFailed':
687
+ if (frame.taskId && frame.status && this.RuntimeStatus) {
688
+ const row = this.taskByID.get(frame.taskId);
689
+ if (row)
690
+ row.Status = frame.status;
691
+ this.RuntimeStatus = { ...this.RuntimeStatus, [frame.taskId]: NormalizeRuntimeState(frame.status) };
692
+ if (frame.kind !== 'TaskStarted')
693
+ this.LiveActivity.delete(frame.taskId);
694
+ this.rebuildOverlay();
695
+ this.emitSettledOnce();
696
+ this.cdr.markForCheck();
697
+ }
698
+ break;
699
+ case 'NodeProgress':
700
+ if (frame.taskId && frame.progressMessage) {
701
+ this.LiveActivity.set(frame.taskId, { Message: frame.progressMessage, Percent: frame.progressPercent });
702
+ this.cdr.markForCheck();
703
+ }
704
+ break;
705
+ case 'GraphSettled':
706
+ this.scheduleFrameReload();
707
+ // The engine said the graph is done — do not wait for the row reload to tell the host.
708
+ if (!this.settledEmitted) {
709
+ this.settledEmitted = true;
710
+ this.Settled.emit();
711
+ }
712
+ break;
713
+ case 'TaskSkipped':
714
+ case 'TaskBlocked':
715
+ case 'TaskAwaitingHuman':
716
+ case 'GraphResumed':
717
+ this.scheduleFrameReload();
718
+ break;
719
+ default:
720
+ // GateDecision / ClaimChanged / PassCompleted / GraphPaused / BreakpointHit are
721
+ // console-chrome concerns the HOST renders; unknown kinds are a newer server.
722
+ break;
723
+ }
724
+ }
725
+ /** One reload per burst of cascade frames, not one per frame. */
726
+ scheduleFrameReload() {
727
+ if (this.frameReloadTimer)
728
+ return;
729
+ this.frameReloadTimer = setTimeout(() => {
730
+ this.frameReloadTimer = null;
731
+ void this.load();
732
+ }, 250);
733
+ }
244
734
  schedulePoll() {
245
735
  this.pollTimer = setTimeout(() => void this.load(), Math.max(1, this.PollIntervalSeconds) * 1000);
246
736
  }
@@ -261,15 +751,17 @@ export class TaskGraphRunViewComponent extends BaseAngularComponent {
261
751
  return { Width: box.Width, Height: box.Height };
262
752
  }
263
753
  static ɵfac = function TaskGraphRunViewComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || TaskGraphRunViewComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
264
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TaskGraphRunViewComponent, selectors: [["mj-task-graph-run-view"]], inputs: { ParentTaskID: "ParentTaskID", WorkflowName: "WorkflowName", LiveUpdates: "LiveUpdates", PollIntervalSeconds: "PollIntervalSeconds", Height: "Height" }, outputs: { NodeSelected: "NodeSelected", Settled: "Settled" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 4, vars: 1, consts: [["Variant", "error", 3, "Message"], [1, "run-view-loading"], ["Icon", "fa-solid fa-diagram-project", "Title", "No steps yet", "Message", "This workflow has not created any steps."], [1, "run-view"], [1, "run-view-summary"], [1, "run-view-count"], [1, "run-view-skipped"], [1, "run-view-canvas"], [3, "SelectionChanged", "Spec", "RuntimeStatus", "NodePositions", "ReadOnly", "ShowToolbar", "ShowPalette", "ShowProperties", "ShowMinimap", "ShowStatusBar"]], template: function TaskGraphRunViewComponent_Template(rf, ctx) { if (rf & 1) {
265
- i0.ɵɵconditionalCreate(0, TaskGraphRunViewComponent_Conditional_0_Template, 1, 1, "mj-alert", 0)(1, TaskGraphRunViewComponent_Conditional_1_Template, 2, 0, "div", 1)(2, TaskGraphRunViewComponent_Conditional_2_Template, 1, 0, "mj-empty-state", 2)(3, TaskGraphRunViewComponent_Conditional_3_Template, 7, 14, "div", 3);
754
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TaskGraphRunViewComponent, selectors: [["mj-task-graph-run-view"]], hostBindings: function TaskGraphRunViewComponent_HostBindings(rf, ctx) { if (rf & 1) {
755
+ i0.ɵɵlistener("keydown", function TaskGraphRunViewComponent_keydown_HostBindingHandler($event) { return ctx.OnDebugHotkey($event); }, i0.ɵɵresolveDocument);
756
+ } }, inputs: { ParentTaskID: "ParentTaskID", WorkflowName: "WorkflowName", LiveUpdates: "LiveUpdates", PollIntervalSeconds: "PollIntervalSeconds", Height: "Height", ShowLegend: "ShowLegend", ShowToolbar: "ShowToolbar", ToolbarVisibility: "ToolbarVisibility", ToolbarAlign: "ToolbarAlign", LiveFrame: "LiveFrame", ReplayAt: "ReplayAt", Breakpoints: "Breakpoints", PausedAtTaskID: "PausedAtTaskID", GraphPaused: "GraphPaused", EdgeOverrides: "EdgeOverrides", AllowBreakpointEditing: "AllowBreakpointEditing", ShowDebugLegend: "ShowDebugLegend", ShowVariables: "ShowVariables", Invocation: "Invocation" }, outputs: { NodeSelected: "NodeSelected", LegendToggled: "LegendToggled", Settled: "Settled", ConnectionSelected: "ConnectionSelected", BreakpointToggled: "BreakpointToggled", EdgeOverrideRequested: "EdgeOverrideRequested" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 4, vars: 1, consts: [["Variant", "error", 3, "Message"], [1, "run-view-loading"], ["Icon", "fa-solid fa-diagram-project", "Title", "No steps yet", "Message", "This workflow has not created any steps."], [1, "run-view", 3, "height"], [1, "run-view"], [1, "run-view-summary"], [1, "run-view-count"], [1, "run-view-skipped"], [1, "run-view-done"], [1, "run-view-waiting"], [1, "run-view-running"], [1, "run-view-body"], ["type", "button", "title", "Show invocation and output", "aria-label", "Show invocation and output", 1, "run-view-vars-rail"], ["direction", "horizontal", "unit", "percent", 1, "run-view-split", 3, "dragEnd", "gutterSize"], [3, "size", "minSize", "visible"], [1, "run-view-vars"], [1, "run-view-vars-bar"], ["type", "button", "title", "Minimize", "aria-label", "Minimize invocation and output", 1, "run-view-vars-min", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-chevron-left"], [3, "InputPayload", "OutputPayload", "Invocation"], [3, "size", "minSize"], [1, "run-view-canvas"], [3, "SelectionChanged", "ConnectionSelected", "LegendToggled", "BeforeContextMenu", "AfterContextMenuAction", "Spec", "RuntimeStatus", "NodePositions", "DebugOverlay", "ReadOnly", "ShowToolbar", "ToolbarVisibility", "ToolbarAlign", "ShowPalette", "ShowProperties", "ShowMinimap", "ShowStatusBar", "ShowLegend"], ["type", "button", 1, "run-view-bp", 3, "click", "title"], ["aria-hidden", "true", 1, "fa-circle"], [1, "run-view-bp-name"], ["type", "button", "title", "Show invocation and output", "aria-label", "Show invocation and output", 1, "run-view-vars-rail", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-code"]], template: function TaskGraphRunViewComponent_Template(rf, ctx) { if (rf & 1) {
757
+ i0.ɵɵconditionalCreate(0, TaskGraphRunViewComponent_Conditional_0_Template, 1, 1, "mj-alert", 0)(1, TaskGraphRunViewComponent_Conditional_1_Template, 2, 0, "div", 1)(2, TaskGraphRunViewComponent_Conditional_2_Template, 1, 0, "mj-empty-state", 2)(3, TaskGraphRunViewComponent_Conditional_3_Template, 23, 30, "div", 3);
266
758
  } if (rf & 2) {
267
759
  i0.ɵɵconditional(ctx.ErrorMessage ? 0 : ctx.IsLoading ? 1 : !ctx.Spec || ctx.Spec.tasks.length === 0 ? 2 : 3);
268
- } }, dependencies: [i1.MJEmptyStateComponent, i1.MJAlertComponent, i2.TaskGraphEditorComponent], styles: ["[_nghost-%COMP%] { display: block; }\n\n.run-view[_ngcontent-%COMP%] { display: flex; flex-direction: column; gap: var(--mj-space-2); }\n\n.run-view-summary[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n font-size: var(--mj-font-size-sm);\n color: var(--mj-text-secondary);\n}\n\n.run-view-count[_ngcontent-%COMP%] { font-weight: 500; }\n\n\n\n\n.run-view-skipped[_ngcontent-%COMP%] { color: var(--mj-text-tertiary); }\n\n.run-view-canvas[_ngcontent-%COMP%] {\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n overflow: hidden;\n background: var(--mj-bg-surface);\n}\n\n.run-view-loading[_ngcontent-%COMP%] {\n padding: var(--mj-space-4);\n color: var(--mj-text-secondary);\n font-size: var(--mj-font-size-sm);\n}"], changeDetection: 0 });
760
+ } }, dependencies: [i1.MJEmptyStateComponent, i1.MJAlertComponent, i2.SplitComponent, i2.SplitAreaComponent, i3.TaskGraphVariablesComponent, i4.TaskGraphEditorComponent], styles: ["\n\n\n[_nghost-%COMP%] { display: block; height: 100%; min-height: 0; }\n\n.run-view[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: var(--mj-space-2);\n height: 100%;\n min-height: 0;\n}\n\n.run-view-summary[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n font-size: var(--mj-font-size-sm);\n color: var(--mj-text-secondary);\n}\n\n.run-view-count[_ngcontent-%COMP%] { font-weight: 500; }\n\n\n\n\n.run-view-skipped[_ngcontent-%COMP%] { color: var(--mj-text-tertiary); }\n.run-view-running[_ngcontent-%COMP%] { color: var(--mj-brand-primary); font-weight: 600; }\n.run-view-waiting[_ngcontent-%COMP%] { color: var(--mj-brand-primary); font-weight: 700; }\n.run-view-done[_ngcontent-%COMP%] { color: var(--mj-status-success); font-weight: 600; }\n\n.run-view-body[_ngcontent-%COMP%] {\n display: flex;\n flex: 1 1 auto;\n min-height: 0;\n min-width: 0;\n}\n\n.run-view-vars-rail[_ngcontent-%COMP%] {\n flex: 0 0 28px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-start;\n gap: var(--mj-space-2);\n padding: var(--mj-space-3) 0;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n cursor: pointer;\n}\n.run-view-vars-rail[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-hover);\n}\n.run-view-vars-rail[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n writing-mode: vertical-rl;\n transform: rotate(180deg);\n letter-spacing: 0.08em;\n text-transform: uppercase;\n font-size: var(--mj-font-size-xs);\n font-weight: 700;\n}\n.run-view-vars-rail[_ngcontent-%COMP%] i[_ngcontent-%COMP%] { font-size: 11px; }\n\n.run-view-split[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-height: 0;\n min-width: 0;\n}\n\n[_nghost-%COMP%] .run-view-split > .as-split-gutter {\n background: transparent;\n}\n[_nghost-%COMP%] .run-view-split > .as-split-gutter::after {\n content: '';\n display: block;\n margin: auto;\n background: var(--mj-border-default);\n border-radius: 1px;\n}\n[_nghost-%COMP%] .run-view-split.as-horizontal > .as-split-gutter::after {\n width: 3px;\n height: 32px;\n}\n[_nghost-%COMP%] .run-view-split > .as-split-gutter:hover::after {\n background: var(--mj-brand-primary);\n}\n[_nghost-%COMP%] .run-view-split > .as-split-area {\n overflow: hidden;\n}\n\n\n\n\n\n.run-view-canvas[_ngcontent-%COMP%] {\n height: 100%;\n min-height: 0;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n overflow: hidden;\n background: var(--mj-bg-surface);\n}\n\n.run-view-vars[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n min-width: 0;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n overflow: hidden;\n background: var(--mj-bg-surface);\n}\n.run-view-vars-bar[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--mj-space-2);\n padding: 4px 8px;\n border-bottom: 1px solid var(--mj-border-default);\n color: var(--mj-text-secondary);\n font-size: var(--mj-font-size-xs);\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n}\n.run-view-vars-min[_ngcontent-%COMP%] {\n width: 22px;\n height: 22px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: none;\n border-radius: var(--mj-radius-sm);\n background: transparent;\n color: var(--mj-text-muted);\n cursor: pointer;\n}\n.run-view-vars-min[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-hover);\n}\n.run-view-vars[_ngcontent-%COMP%] mj-task-graph-variables[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-height: 0;\n}\n\n.run-view-loading[_ngcontent-%COMP%] {\n padding: var(--mj-space-4);\n color: var(--mj-text-secondary);\n font-size: var(--mj-font-size-sm);\n}\n\n.run-view-bp[_ngcontent-%COMP%] {\n margin-left: auto;\n width: 22px;\n height: 22px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--mj-text-muted);\n cursor: pointer;\n}\n.run-view-bp[_ngcontent-%COMP%]:hover { color: var(--mj-status-error); }\n.run-view-bp-on[_ngcontent-%COMP%], \n.run-view-bp-on[_ngcontent-%COMP%]:hover { color: var(--mj-status-error); }\n.run-view-bp[_ngcontent-%COMP%] i[_ngcontent-%COMP%] { font-size: 10px; }\n.run-view-bp-name[_ngcontent-%COMP%] {\n font-size: var(--mj-font-size-xs);\n color: var(--mj-text-muted);\n}\n\n.run-view-legend[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: var(--mj-space-3);\n font-size: var(--mj-font-size-xs);\n color: var(--mj-text-muted);\n}\n.run-view-legend[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n.run-view-key-break[_ngcontent-%COMP%] { color: var(--mj-status-error); font-size: 8px; }\n.run-view-key-paused[_ngcontent-%COMP%] { color: var(--mj-brand-primary); font-size: 10px; }\n.run-view-key-forced[_ngcontent-%COMP%] { color: var(--mj-status-warning); font-size: 10px; }\n.run-view-key-if[_ngcontent-%COMP%] {\n font-family: var(--mj-font-mono, ui-monospace, monospace);\n font-style: italic;\n}"], changeDetection: 0 });
269
761
  }
270
762
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TaskGraphRunViewComponent, [{
271
763
  type: Component,
272
- args: [{ standalone: false, selector: 'mj-task-graph-run-view', changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (ErrorMessage) {\n <mj-alert Variant=\"error\" [Message]=\"ErrorMessage\"></mj-alert>\n} @else if (IsLoading) {\n <div class=\"run-view-loading\">Loading the workflow&rsquo;s steps&hellip;</div>\n} @else if (!Spec || Spec.tasks.length === 0) {\n <mj-empty-state\n Icon=\"fa-solid fa-diagram-project\"\n Title=\"No steps yet\"\n Message=\"This workflow has not created any steps.\">\n </mj-empty-state>\n} @else {\n <div class=\"run-view\">\n <div class=\"run-view-summary\">\n <span class=\"run-view-count\">{{ CompletedCount }} of {{ TotalCount }} complete</span>\n @if (SkippedCount > 0) {\n <span class=\"run-view-skipped\">{{ SkippedCount }} not taken</span>\n }\n </div>\n\n <div class=\"run-view-canvas\" [style.height]=\"Height\">\n <mj-task-graph-editor\n [Spec]=\"Spec\"\n [RuntimeStatus]=\"RuntimeStatus\"\n [NodePositions]=\"Positions\"\n [ReadOnly]=\"true\"\n [ShowToolbar]=\"false\"\n [ShowPalette]=\"false\"\n [ShowProperties]=\"false\"\n [ShowMinimap]=\"false\"\n [ShowStatusBar]=\"false\"\n (SelectionChanged)=\"OnSelectionChanged($event)\">\n </mj-task-graph-editor>\n </div>\n </div>\n}\n", styles: [":host { display: block; }\n\n.run-view { display: flex; flex-direction: column; gap: var(--mj-space-2); }\n\n.run-view-summary {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n font-size: var(--mj-font-size-sm);\n color: var(--mj-text-secondary);\n}\n\n.run-view-count { font-weight: 500; }\n\n/* Not-taken reads as neutral, never as a failure: a skipped branch is a normal outcome, and\n colouring it like an error sends people hunting for a bug that does not exist. */\n.run-view-skipped { color: var(--mj-text-tertiary); }\n\n.run-view-canvas {\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n overflow: hidden;\n background: var(--mj-bg-surface);\n}\n\n.run-view-loading {\n padding: var(--mj-space-4);\n color: var(--mj-text-secondary);\n font-size: var(--mj-font-size-sm);\n}\n"] }]
764
+ args: [{ standalone: false, selector: 'mj-task-graph-run-view', changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (ErrorMessage) {\n <mj-alert Variant=\"error\" [Message]=\"ErrorMessage\"></mj-alert>\n} @else if (IsLoading) {\n <div class=\"run-view-loading\">Loading the workflow&rsquo;s steps&hellip;</div>\n} @else if (!Spec || Spec.tasks.length === 0) {\n <mj-empty-state\n Icon=\"fa-solid fa-diagram-project\"\n Title=\"No steps yet\"\n Message=\"This workflow has not created any steps.\">\n </mj-empty-state>\n} @else {\n <div class=\"run-view\" [style.height]=\"Height\">\n <div class=\"run-view-summary\">\n <span class=\"run-view-count\">{{ CompletedCount }} of {{ TotalCount }} complete</span>\n @if (SkippedCount > 0) {\n <span class=\"run-view-skipped\">{{ SkippedCount }} not taken</span>\n }\n @if (IsSettled) {\n <span class=\"run-view-done\">Finished</span>\n } @else if (GraphPaused) {\n <span class=\"run-view-waiting\">\n @if (PausedAtTaskID) { Waiting on {{ WaitingStepName }} }\n @else { Waiting to start \u2014 Continue or Step }\n </span>\n } @else if (RunningCount > 0) {\n <span class=\"run-view-running\">{{ RunningCount }} running</span>\n }\n @if (AllowBreakpointEditing && SelectedTaskID) {\n <button\n type=\"button\"\n class=\"run-view-bp\"\n [class.run-view-bp-on]=\"HasSelectedBreakpoint\"\n (click)=\"OnToggleSelectedBreakpoint()\"\n [attr.aria-pressed]=\"HasSelectedBreakpoint\"\n [attr.aria-label]=\"HasSelectedBreakpoint ? 'Remove breakpoint from ' + SelectedTaskName : 'Add breakpoint on ' + SelectedTaskName\"\n [title]=\"HasSelectedBreakpoint ? 'Remove breakpoint' : 'Break before this step'\">\n <i class=\"fa-circle\" [class.fa-solid]=\"HasSelectedBreakpoint\" [class.fa-regular]=\"!HasSelectedBreakpoint\" aria-hidden=\"true\"></i>\n </button>\n <span class=\"run-view-bp-name\">{{ SelectedTaskName }}</span>\n }\n </div>\n\n <div class=\"run-view-body\">\n @if (ShowVariables && !Prefs.InvocationOpen) {\n <button type=\"button\" class=\"run-view-vars-rail\"\n (click)=\"OnToggleInvocation()\"\n title=\"Show invocation and output\"\n aria-label=\"Show invocation and output\">\n <i class=\"fa-solid fa-code\" aria-hidden=\"true\"></i>\n <span>Data</span>\n </button>\n }\n <as-split class=\"run-view-split\" direction=\"horizontal\" unit=\"percent\"\n [gutterSize]=\"8\" (dragEnd)=\"OnInvocationSplitDragEnd($event.sizes)\">\n <as-split-area [size]=\"InvocationPaneSize\" [minSize]=\"14\" [visible]=\"InvocationPaneOpen\">\n <div class=\"run-view-vars\">\n <div class=\"run-view-vars-bar\">\n <span>Data</span>\n <button type=\"button\" class=\"run-view-vars-min\"\n (click)=\"OnToggleInvocation()\"\n title=\"Minimize\"\n aria-label=\"Minimize invocation and output\">\n <i class=\"fa-solid fa-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <mj-task-graph-variables\n [InputPayload]=\"SelectedTask?.InputPayload\"\n [OutputPayload]=\"SelectedTask?.OutputPayload\"\n [Invocation]=\"Invocation\">\n </mj-task-graph-variables>\n </div>\n </as-split-area>\n <as-split-area [size]=\"CanvasPaneSize\" [minSize]=\"40\">\n <div class=\"run-view-canvas\">\n <mj-task-graph-editor\n [Spec]=\"Spec\"\n [RuntimeStatus]=\"RuntimeStatus\"\n [NodePositions]=\"Positions\"\n [DebugOverlay]=\"Overlay\"\n [ReadOnly]=\"true\"\n [ShowToolbar]=\"ShowToolbar\"\n [ToolbarVisibility]=\"ToolbarVisibility\"\n [ToolbarAlign]=\"ToolbarAlign\"\n [ShowPalette]=\"false\"\n [ShowProperties]=\"false\"\n [ShowMinimap]=\"false\"\n [ShowStatusBar]=\"false\"\n [ShowLegend]=\"ShowLegend\"\n (SelectionChanged)=\"OnSelectionChanged($event)\"\n (ConnectionSelected)=\"OnConnectionSelected($event)\"\n (LegendToggled)=\"LegendToggled.emit($event)\"\n (BeforeContextMenu)=\"OnBeforeContextMenu($event)\"\n (AfterContextMenuAction)=\"OnAfterContextMenuAction($event)\">\n </mj-task-graph-editor>\n </div>\n </as-split-area>\n </as-split>\n </div>\n </div>\n}\n", styles: ["/* `height: 100%` here resolves to auto when the host's parent has no definite height, so this is\n safe for both callers: one that gives the widget a box to fill, and one that asks for 320px. */\n:host { display: block; height: 100%; min-height: 0; }\n\n.run-view {\n display: flex;\n flex-direction: column;\n gap: var(--mj-space-2);\n height: 100%;\n min-height: 0;\n}\n\n.run-view-summary {\n display: flex;\n align-items: center;\n gap: var(--mj-space-3);\n font-size: var(--mj-font-size-sm);\n color: var(--mj-text-secondary);\n}\n\n.run-view-count { font-weight: 500; }\n\n/* Not-taken reads as neutral, never as a failure: a skipped branch is a normal outcome, and\n colouring it like an error sends people hunting for a bug that does not exist. */\n.run-view-skipped { color: var(--mj-text-tertiary); }\n.run-view-running { color: var(--mj-brand-primary); font-weight: 600; }\n.run-view-waiting { color: var(--mj-brand-primary); font-weight: 700; }\n.run-view-done { color: var(--mj-status-success); font-weight: 600; }\n\n.run-view-body {\n display: flex;\n flex: 1 1 auto;\n min-height: 0;\n min-width: 0;\n}\n\n.run-view-vars-rail {\n flex: 0 0 28px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-start;\n gap: var(--mj-space-2);\n padding: var(--mj-space-3) 0;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n cursor: pointer;\n}\n.run-view-vars-rail:hover {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-hover);\n}\n.run-view-vars-rail span {\n writing-mode: vertical-rl;\n transform: rotate(180deg);\n letter-spacing: 0.08em;\n text-transform: uppercase;\n font-size: var(--mj-font-size-xs);\n font-weight: 700;\n}\n.run-view-vars-rail i { font-size: 11px; }\n\n.run-view-split {\n flex: 1 1 auto;\n min-height: 0;\n min-width: 0;\n}\n\n:host ::ng-deep .run-view-split > .as-split-gutter {\n background: transparent;\n}\n:host ::ng-deep .run-view-split > .as-split-gutter::after {\n content: '';\n display: block;\n margin: auto;\n background: var(--mj-border-default);\n border-radius: 1px;\n}\n:host ::ng-deep .run-view-split.as-horizontal > .as-split-gutter::after {\n width: 3px;\n height: 32px;\n}\n:host ::ng-deep .run-view-split > .as-split-gutter:hover::after {\n background: var(--mj-brand-primary);\n}\n:host ::ng-deep .run-view-split > .as-split-area {\n overflow: hidden;\n}\n\n/* Takes what the summary line leaves, rather than a percentage of an ancestor that may not have a\n definite height \u2014 which is what left a canvas stopping halfway down its pane with dead space\n below it. */\n.run-view-canvas {\n height: 100%;\n min-height: 0;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n overflow: hidden;\n background: var(--mj-bg-surface);\n}\n\n.run-view-vars {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n min-width: 0;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n overflow: hidden;\n background: var(--mj-bg-surface);\n}\n.run-view-vars-bar {\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--mj-space-2);\n padding: 4px 8px;\n border-bottom: 1px solid var(--mj-border-default);\n color: var(--mj-text-secondary);\n font-size: var(--mj-font-size-xs);\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n}\n.run-view-vars-min {\n width: 22px;\n height: 22px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: none;\n border-radius: var(--mj-radius-sm);\n background: transparent;\n color: var(--mj-text-muted);\n cursor: pointer;\n}\n.run-view-vars-min:hover {\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-hover);\n}\n.run-view-vars mj-task-graph-variables {\n flex: 1 1 auto;\n min-height: 0;\n}\n\n.run-view-loading {\n padding: var(--mj-space-4);\n color: var(--mj-text-secondary);\n font-size: var(--mj-font-size-sm);\n}\n\n.run-view-bp {\n margin-left: auto;\n width: 22px;\n height: 22px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--mj-text-muted);\n cursor: pointer;\n}\n.run-view-bp:hover { color: var(--mj-status-error); }\n.run-view-bp-on,\n.run-view-bp-on:hover { color: var(--mj-status-error); }\n.run-view-bp i { font-size: 10px; }\n.run-view-bp-name {\n font-size: var(--mj-font-size-xs);\n color: var(--mj-text-muted);\n}\n\n.run-view-legend {\n display: flex;\n flex-wrap: wrap;\n gap: var(--mj-space-3);\n font-size: var(--mj-font-size-xs);\n color: var(--mj-text-muted);\n}\n.run-view-legend span {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n.run-view-key-break { color: var(--mj-status-error); font-size: 8px; }\n.run-view-key-paused { color: var(--mj-brand-primary); font-size: 10px; }\n.run-view-key-forced { color: var(--mj-status-warning); font-size: 10px; }\n.run-view-key-if {\n font-family: var(--mj-font-mono, ui-monospace, monospace);\n font-style: italic;\n}\n"] }]
273
765
  }], () => [{ type: i0.ChangeDetectorRef }], { ParentTaskID: [{
274
766
  type: Input
275
767
  }], WorkflowName: [{
@@ -280,10 +772,55 @@ export class TaskGraphRunViewComponent extends BaseAngularComponent {
280
772
  type: Input
281
773
  }], Height: [{
282
774
  type: Input
775
+ }], ShowLegend: [{
776
+ type: Input
777
+ }], ShowToolbar: [{
778
+ type: Input
779
+ }], ToolbarVisibility: [{
780
+ type: Input
781
+ }], ToolbarAlign: [{
782
+ type: Input
783
+ }], LiveFrame: [{
784
+ type: Input
785
+ }], ReplayAt: [{
786
+ type: Input
787
+ }], Breakpoints: [{
788
+ type: Input
789
+ }], PausedAtTaskID: [{
790
+ type: Input
791
+ }], GraphPaused: [{
792
+ type: Input
793
+ }], EdgeOverrides: [{
794
+ type: Input
795
+ }], AllowBreakpointEditing: [{
796
+ type: Input
797
+ }], ShowDebugLegend: [{
798
+ type: Input
799
+ }], ShowVariables: [{
800
+ type: Input
801
+ }], Invocation: [{
802
+ type: Input
283
803
  }], NodeSelected: [{
284
804
  type: Output
805
+ }], LegendToggled: [{
806
+ type: Output
285
807
  }], Settled: [{
286
808
  type: Output
809
+ }], ConnectionSelected: [{
810
+ type: Output
811
+ }], BreakpointToggled: [{
812
+ type: Output
813
+ }], EdgeOverrideRequested: [{
814
+ type: Output
815
+ }], OnDebugHotkey: [{
816
+ type: HostListener,
817
+ args: ['document:keydown', ['$event']]
287
818
  }] }); })();
288
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TaskGraphRunViewComponent, { className: "TaskGraphRunViewComponent", filePath: "src/lib/task-graph-run-view.component.ts", lineNumber: 60 }); })();
819
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TaskGraphRunViewComponent, { className: "TaskGraphRunViewComponent", filePath: "src/lib/task-graph-run-view.component.ts", lineNumber: 108 }); })();
820
+ function isTypingTarget(target) {
821
+ if (!(target instanceof HTMLElement))
822
+ return false;
823
+ const tag = target.tagName;
824
+ return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target.isContentEditable;
825
+ }
289
826
  //# sourceMappingURL=task-graph-run-view.component.js.map