@memberjunction/ng-task-graph-editor 6.1.0-edge.1 → 6.1.0-edge.3

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 (59) 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 +142 -7
  12. package/dist/lib/task-graph-canvas-adapter.d.ts.map +1 -1
  13. package/dist/lib/task-graph-canvas-adapter.js +319 -44
  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 +144 -5
  24. package/dist/lib/task-graph-editor.component.d.ts.map +1 -1
  25. package/dist/lib/task-graph-editor.component.js +321 -26
  26. package/dist/lib/task-graph-editor.component.js.map +1 -1
  27. package/dist/lib/task-graph-editor.module.d.ts +10 -5
  28. package/dist/lib/task-graph-editor.module.d.ts.map +1 -1
  29. package/dist/lib/task-graph-editor.module.js +53 -5
  30. package/dist/lib/task-graph-editor.module.js.map +1 -1
  31. package/dist/lib/task-graph-properties-panel.component.d.ts +34 -8
  32. package/dist/lib/task-graph-properties-panel.component.d.ts.map +1 -1
  33. package/dist/lib/task-graph-properties-panel.component.js +164 -45
  34. package/dist/lib/task-graph-properties-panel.component.js.map +1 -1
  35. package/dist/lib/task-graph-run-prefs.d.ts +34 -0
  36. package/dist/lib/task-graph-run-prefs.d.ts.map +1 -0
  37. package/dist/lib/task-graph-run-prefs.js +67 -0
  38. package/dist/lib/task-graph-run-prefs.js.map +1 -0
  39. package/dist/lib/task-graph-run-view.component.d.ts +290 -0
  40. package/dist/lib/task-graph-run-view.component.d.ts.map +1 -0
  41. package/dist/lib/task-graph-run-view.component.js +826 -0
  42. package/dist/lib/task-graph-run-view.component.js.map +1 -0
  43. package/dist/lib/task-graph-runtime-source.d.ts +7 -1
  44. package/dist/lib/task-graph-runtime-source.d.ts.map +1 -1
  45. package/dist/lib/task-graph-runtime-source.js +14 -2
  46. package/dist/lib/task-graph-runtime-source.js.map +1 -1
  47. package/dist/lib/task-graph-variables.component.d.ts +23 -0
  48. package/dist/lib/task-graph-variables.component.d.ts.map +1 -0
  49. package/dist/lib/task-graph-variables.component.js +192 -0
  50. package/dist/lib/task-graph-variables.component.js.map +1 -0
  51. package/dist/lib/workflow-debug-host.d.ts +23 -0
  52. package/dist/lib/workflow-debug-host.d.ts.map +1 -0
  53. package/dist/lib/workflow-debug-host.js +101 -0
  54. package/dist/lib/workflow-debug-host.js.map +1 -0
  55. package/dist/public-api.d.ts +8 -0
  56. package/dist/public-api.d.ts.map +1 -1
  57. package/dist/public-api.js +8 -0
  58. package/dist/public-api.js.map +1 -1
  59. package/package.json +11 -9
@@ -0,0 +1,826 @@
1
+ /**
2
+ * @fileoverview Watching a graph that is running, on the canvas its author drew on.
3
+ *
4
+ * **Why this is a component and not three.** A run needs rendering in at least three places — inside
5
+ * an Agent Run, inside the test harness, and on a standalone workflow-runs screen — and every one of
6
+ * them wants the identical thing: the graph, with live status, laid out legibly, with a node you can
7
+ * click. Three implementations would be three chances to render the same run differently, and the
8
+ * one that matters most (is that branch grey because it was *not taken*, or because it *broke*?) is
9
+ * exactly the distinction a second implementation gets wrong.
10
+ *
11
+ * **What it owns and what it doesn't.** It owns data acquisition — reading `MJ: Tasks` and
12
+ * `MJ: Task Dependencies` through the host's provider — and geometry. It does not navigate: a
13
+ * widgets-layer component cannot know whether it sits inside Explorer, so selecting a node emits an
14
+ * event and the host decides what "open the agent run" means. That boundary is the whole reason
15
+ * this can live beside the editor rather than in Explorer.
16
+ *
17
+ * @module @memberjunction/ng-task-graph-editor
18
+ */
19
+ import { ChangeDetectionStrategy, Component, EventEmitter, HostListener, Input, Output, } from '@angular/core';
20
+ import { BaseAngularComponent } from '@memberjunction/ng-base-types';
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';
26
+ import { GraphLayoutBounds, LayoutGraphNodes, ProjectTaskRowsToSpec, } from '@memberjunction/ai-core-plus';
27
+ import { BuildRuntimeStatus, IsRuntimeSettled, NormalizeRuntimeState } from './task-graph-runtime-source';
28
+ import * as i0 from "@angular/core";
29
+ import * as i1 from "@memberjunction/ng-ui-components";
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";
33
+ function TaskGraphRunViewComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
34
+ i0.ɵɵelement(0, "mj-alert", 0);
35
+ } if (rf & 2) {
36
+ const ctx_r0 = i0.ɵɵnextContext();
37
+ i0.ɵɵproperty("Message", ctx_r0.ErrorMessage);
38
+ } }
39
+ function TaskGraphRunViewComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
40
+ i0.ɵɵelementStart(0, "div", 1);
41
+ i0.ɵɵtext(1, "Loading the workflow\u2019s steps\u2026");
42
+ i0.ɵɵelementEnd();
43
+ } }
44
+ function TaskGraphRunViewComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
45
+ i0.ɵɵelement(0, "mj-empty-state", 2);
46
+ } }
47
+ function TaskGraphRunViewComponent_Conditional_3_Conditional_4_Template(rf, ctx) { if (rf & 1) {
48
+ i0.ɵɵelementStart(0, "span", 7);
49
+ i0.ɵɵtext(1);
50
+ i0.ɵɵelementEnd();
51
+ } if (rf & 2) {
52
+ const ctx_r0 = i0.ɵɵnextContext(2);
53
+ i0.ɵɵadvance();
54
+ i0.ɵɵtextInterpolate1("", ctx_r0.SkippedCount, " not taken");
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
+ } }
116
+ function TaskGraphRunViewComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
117
+ const _r2 = i0.ɵɵgetCurrentView();
118
+ i0.ɵɵelementStart(0, "div", 4)(1, "div", 5)(2, "span", 6);
119
+ i0.ɵɵtext(3);
120
+ i0.ɵɵelementEnd();
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");
131
+ 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()()()()()();
141
+ } if (rf & 2) {
142
+ const ctx_r0 = i0.ɵɵnextContext();
143
+ i0.ɵɵstyleProp("height", ctx_r0.Height);
144
+ i0.ɵɵadvance(3);
145
+ i0.ɵɵtextInterpolate2("", ctx_r0.CompletedCount, " of ", ctx_r0.TotalCount, " complete");
146
+ i0.ɵɵadvance();
147
+ i0.ɵɵconditional(ctx_r0.SkippedCount > 0 ? 4 : -1);
148
+ i0.ɵɵadvance();
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);
156
+ i0.ɵɵadvance();
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);
164
+ } }
165
+ /** Statuses at which a graph has stopped moving, so polling can stop with it. */
166
+ const SETTLED = new Set(['Complete', 'Failed', 'Cancelled', 'Skipped']);
167
+ export class TaskGraphRunViewComponent extends BaseAngularComponent {
168
+ cdr;
169
+ /**
170
+ * The graph to watch — the parent `MJ: Tasks` row that represents it.
171
+ *
172
+ * Setter-based rather than `ngOnChanges`, matching the editor beside it: the reaction is
173
+ * explicit and costs nothing on unrelated change-detection passes.
174
+ */
175
+ set ParentTaskID(value) {
176
+ if (value === this.parentTaskID)
177
+ return;
178
+ this.parentTaskID = value;
179
+ this.settledEmitted = false;
180
+ void this.load();
181
+ }
182
+ get ParentTaskID() {
183
+ return this.parentTaskID;
184
+ }
185
+ /** Name shown on the compiled graph. Purely cosmetic; the rows carry the truth. */
186
+ WorkflowName = 'Workflow';
187
+ /**
188
+ * Keep re-reading while the graph is still moving.
189
+ *
190
+ * Off by default: most hosts show a finished run, and a component that polls by default would
191
+ * have every historical run quietly issuing queries forever.
192
+ */
193
+ LiveUpdates = false;
194
+ /** How often to re-read while live. Seconds, not milliseconds — this is a human-scale view. */
195
+ PollIntervalSeconds = 3;
196
+ /** Height of the canvas. Hosts embed this in very different amounts of space. */
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;
297
+ NodeSelected = new EventEmitter();
298
+ /** The legend was toggled from the toolbar, so a host can remember the choice. */
299
+ LegendToggled = new EventEmitter();
300
+ /** Emitted once, when every step has reached a terminal status. */
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
+ });
328
+ Spec = null;
329
+ RuntimeStatus = null;
330
+ Positions = new Map();
331
+ IsLoading = false;
332
+ ErrorMessage = null;
333
+ /** Rows by id, so a selection event can hand the host the whole task rather than an id. */
334
+ taskByID = new Map();
335
+ parentTaskID = null;
336
+ replayAt = null;
337
+ pollTimer = null;
338
+ frameReloadTimer = null;
339
+ destroyed = false;
340
+ constructor(cdr) {
341
+ super();
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();
373
+ }
374
+ ngOnDestroy() {
375
+ this.destroyed = true;
376
+ this.stopPolling();
377
+ if (this.frameReloadTimer) {
378
+ clearTimeout(this.frameReloadTimer);
379
+ this.frameReloadTimer = null;
380
+ }
381
+ }
382
+ /** Steps in a terminal state, for the host's summary line. Uses the same map the canvas paints. */
383
+ get CompletedCount() {
384
+ return this.countRuntime('Complete');
385
+ }
386
+ get TotalCount() {
387
+ return this.Spec?.tasks.length ?? this.taskByID.size;
388
+ }
389
+ get SkippedCount() {
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));
399
+ }
400
+ /**
401
+ * A node was selected on the canvas.
402
+ *
403
+ * The projected `tempId` IS the task id, so the row lookup is exact — and handing the host the
404
+ * whole task rather than an id spares every host from re-reading a row this component already
405
+ * holds.
406
+ */
407
+ OnSelectionChanged(args) {
408
+ const tempId = args?.Task?.tempId;
409
+ if (!tempId)
410
+ return;
411
+ this.SelectedTaskID = tempId;
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();
547
+ }
548
+ /**
549
+ * Reads the graph and projects it onto the canvas.
550
+ *
551
+ * `BypassCache` throughout: a run view exists to show what is true *now*, and the whole point of
552
+ * the poll is to see a status that changed a second ago. A cached read would render a stale graph
553
+ * that looks authoritative.
554
+ */
555
+ async load() {
556
+ this.stopPolling();
557
+ if (!this.parentTaskID) {
558
+ this.Spec = null;
559
+ this.cdr.markForCheck();
560
+ return;
561
+ }
562
+ this.IsLoading = this.Spec === null; // only show the spinner on first load, not on every poll
563
+ this.ErrorMessage = null;
564
+ try {
565
+ const rv = RunView.FromMetadataProvider(this.ProviderToUse);
566
+ const [tasks, deps] = await rv.RunViews([
567
+ {
568
+ EntityName: 'MJ: Tasks',
569
+ ExtraFilter: `ParentID='${this.parentTaskID}'`,
570
+ // Started work in the order it ran, unstarted last, persist order as the
571
+ // tiebreak — the same rule GetAgentRunTree uses, so a graph reads identically
572
+ // wherever it is shown.
573
+ //
574
+ // `__mj_CreatedAt ASC` alone was persist order, which is the COMPILER's walk
575
+ // order, not the author's numbering and not what actually happened: a settled
576
+ // graph listed its steps in an order unrelated to their execution, and a skipped
577
+ // step could sit above work that ran.
578
+ OrderBy: 'CASE WHEN StartedAt IS NULL THEN 1 ELSE 0 END, StartedAt, __mj_CreatedAt',
579
+ ResultType: 'entity_object',
580
+ BypassCache: true,
581
+ },
582
+ {
583
+ EntityName: 'MJ: Task Dependencies',
584
+ ExtraFilter: `TaskID IN (SELECT ID FROM __mj.Task WHERE ParentID='${this.parentTaskID}')`,
585
+ ResultType: 'entity_object',
586
+ BypassCache: true,
587
+ },
588
+ ]);
589
+ if (this.destroyed)
590
+ return;
591
+ if (!tasks.Success) {
592
+ this.fail(`The workflow's steps could not be loaded: ${tasks.ErrorMessage}`);
593
+ return;
594
+ }
595
+ const taskRows = (tasks.Results ?? []);
596
+ const depRows = (deps.Success ? (deps.Results ?? []) : []);
597
+ this.project(taskRows, depRows);
598
+ this.rebuildOverlay();
599
+ this.emitSettledOnce();
600
+ if (this.LiveUpdates && !this.isSettled(taskRows))
601
+ this.schedulePoll();
602
+ }
603
+ catch (e) {
604
+ this.fail(e instanceof Error ? e.message : String(e));
605
+ }
606
+ finally {
607
+ this.IsLoading = false;
608
+ this.cdr.markForCheck();
609
+ }
610
+ }
611
+ /** Rows → spec + runtime + geometry. */
612
+ project(taskRows, depRows) {
613
+ this.taskByID = new Map(taskRows.map((t) => [t.ID, t]));
614
+ const projection = ProjectTaskRowsToSpec(this.WorkflowName, taskRows, depRows);
615
+ this.Spec = projection.Spec;
616
+ // Correlate by ID, not by name: a projected node's tempId IS the task id, which is the one
617
+ // case where an id match is exact. Name correlation exists for specs whose tempIds were
618
+ // never real, and would be needlessly lossy here.
619
+ const knownIDs = new Set(taskRows.map((t) => t.ID));
620
+ this.RuntimeStatus = this.replayAt
621
+ ? this.statusAt(this.replayAt)
622
+ : BuildRuntimeStatus(taskRows, new Map(), knownIDs);
623
+ this.Positions = this.resolvePositions(projection.AuthoredPositions, taskRows, depRows);
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
+ }
651
+ /**
652
+ * The author's arrangement where there is one, a computed layout everywhere else.
653
+ *
654
+ * Mixing the two per-node rather than choosing one for the whole graph: a workflow whose author
655
+ * positioned some steps and left others where they fell should keep the positions they chose.
656
+ * A wholly unpositioned graph — the normal case for anything an agent emitted — is laid out
657
+ * entirely by the algorithm.
658
+ */
659
+ resolvePositions(authored, taskRows, depRows) {
660
+ const missing = taskRows.filter((t) => !authored.has(t.ID)).map((t) => t.ID);
661
+ if (missing.length === 0)
662
+ return authored;
663
+ const edges = depRows.map((d) => ({ From: d.DependsOnTaskID, To: d.TaskID }));
664
+ const computed = LayoutGraphNodes(taskRows.map((t) => t.ID), edges, { Direction: 'LR' });
665
+ const resolved = new Map(computed);
666
+ for (const [id, position] of authored)
667
+ resolved.set(id, position);
668
+ return resolved;
669
+ }
670
+ isSettled(taskRows) {
671
+ return taskRows.length > 0 && taskRows.every((t) => SETTLED.has(t.Status));
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
+ }
734
+ schedulePoll() {
735
+ this.pollTimer = setTimeout(() => void this.load(), Math.max(1, this.PollIntervalSeconds) * 1000);
736
+ }
737
+ stopPolling() {
738
+ if (this.pollTimer) {
739
+ clearTimeout(this.pollTimer);
740
+ this.pollTimer = null;
741
+ }
742
+ }
743
+ fail(message) {
744
+ LogError(`[TaskGraphRunView] ${message}`);
745
+ this.ErrorMessage = message;
746
+ this.cdr.markForCheck();
747
+ }
748
+ /** Canvas extent, so a host can size its container to the graph rather than guessing. */
749
+ get GraphBounds() {
750
+ const box = GraphLayoutBounds(this.Positions);
751
+ return { Width: box.Width, Height: box.Height };
752
+ }
753
+ static ɵfac = function TaskGraphRunViewComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || TaskGraphRunViewComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
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);
758
+ } if (rf & 2) {
759
+ i0.ɵɵconditional(ctx.ErrorMessage ? 0 : ctx.IsLoading ? 1 : !ctx.Spec || ctx.Spec.tasks.length === 0 ? 2 : 3);
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 });
761
+ }
762
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TaskGraphRunViewComponent, [{
763
+ type: Component,
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"] }]
765
+ }], () => [{ type: i0.ChangeDetectorRef }], { ParentTaskID: [{
766
+ type: Input
767
+ }], WorkflowName: [{
768
+ type: Input
769
+ }], LiveUpdates: [{
770
+ type: Input
771
+ }], PollIntervalSeconds: [{
772
+ type: Input
773
+ }], Height: [{
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
803
+ }], NodeSelected: [{
804
+ type: Output
805
+ }], LegendToggled: [{
806
+ type: Output
807
+ }], Settled: [{
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']]
818
+ }] }); })();
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
+ }
826
+ //# sourceMappingURL=task-graph-run-view.component.js.map