@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
@@ -24,14 +24,16 @@
24
24
  *
25
25
  * @module @memberjunction/ng-task-graph-editor
26
26
  */
27
- import { Component, EventEmitter, Input, Output } from '@angular/core';
27
+ import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
28
28
  import { BaseAngularComponent } from '@memberjunction/ng-base-types';
29
- import { ValidateTaskGraphSpec, } from '@memberjunction/ai-core-plus';
30
- import { AddDependency, AddTask, GetDependents, NextTempId, RemoveDependency, RemoveTask, SpecToConnections, SpecToNodes, TASK_GRAPH_NODE_TYPES, UpdateTask, WouldCreateCycle, } from './task-graph-canvas-adapter';
29
+ import { ConfigOf, ValidateTaskGraphSpec, } from '@memberjunction/ai-core-plus';
30
+ import { FlowEditorComponent } from '@memberjunction/ng-flow-editor';
31
+ import { AddDependency, AddTask, GetDependents, GetNodeTypeConfig, IsAuthorableNodeType, NewTaskFromNodeType, NextTempId, RemoveDependency, RemoveTask, SpecToConnections, SpecToNodes, TASK_GRAPH_NODE_TYPES, UpdateTask, WouldCreateCycle, } from './task-graph-canvas-adapter';
31
32
  import { AfterDependencyAddedEventArgs, AfterDependencyRemovedEventArgs, AfterTaskAddedEventArgs, AfterTaskRemovedEventArgs, AfterTaskUpdatedEventArgs, AgentOpenRequestedEventArgs, BeforeDependencyAddedEventArgs, BeforeDependencyRemovedEventArgs, BeforeTaskAddedEventArgs, BeforeTaskRemovedEventArgs, BeforeTaskUpdatedEventArgs, RecordOpenRequestedEventArgs, TaskGraphSelectionChangedEventArgs, TaskGraphSpecChangedEventArgs, TaskGraphValidationChangedEventArgs, } from './task-graph-editor-events';
32
33
  import * as i0 from "@angular/core";
33
34
  import * as i1 from "@memberjunction/ng-flow-editor";
34
35
  import * as i2 from "@memberjunction/ng-ui-components";
36
+ import * as i3 from "./task-graph-properties-panel.component";
35
37
  const _forTrack0 = ($index, $item) => $item.Code + ($item.TempId ?? "");
36
38
  function TaskGraphEditorComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
37
39
  i0.ɵɵelement(0, "mj-empty-state", 1);
@@ -39,7 +41,13 @@ function TaskGraphEditorComponent_Conditional_1_Template(rf, ctx) { if (rf & 1)
39
41
  const ctx_r0 = i0.ɵɵnextContext();
40
42
  i0.ɵɵproperty("Message", ctx_r0.EmptyStateMessage);
41
43
  } }
42
- function TaskGraphEditorComponent_Conditional_2_Conditional_0_For_5_Template(rf, ctx) { if (rf & 1) {
44
+ function TaskGraphEditorComponent_Conditional_2_Conditional_0_Template(rf, ctx) { if (rf & 1) {
45
+ i0.ɵɵelement(0, "mj-alert", 2);
46
+ } if (rf & 2) {
47
+ const ctx_r0 = i0.ɵɵnextContext(2);
48
+ i0.ɵɵproperty("Message", ctx_r0.EmptyStateMessage);
49
+ } }
50
+ function TaskGraphEditorComponent_Conditional_2_Conditional_1_For_5_Template(rf, ctx) { if (rf & 1) {
43
51
  i0.ɵɵelementStart(0, "li");
44
52
  i0.ɵɵtext(1);
45
53
  i0.ɵɵelementEnd();
@@ -48,12 +56,12 @@ function TaskGraphEditorComponent_Conditional_2_Conditional_0_For_5_Template(rf,
48
56
  i0.ɵɵadvance();
49
57
  i0.ɵɵtextInterpolate(err_r3.Message);
50
58
  } }
51
- function TaskGraphEditorComponent_Conditional_2_Conditional_0_Template(rf, ctx) { if (rf & 1) {
52
- i0.ɵɵelementStart(0, "mj-alert", 2)(1, "strong");
59
+ function TaskGraphEditorComponent_Conditional_2_Conditional_1_Template(rf, ctx) { if (rf & 1) {
60
+ i0.ɵɵelementStart(0, "mj-alert", 3)(1, "strong");
53
61
  i0.ɵɵtext(2);
54
62
  i0.ɵɵelementEnd();
55
- i0.ɵɵelementStart(3, "ul", 4);
56
- i0.ɵɵrepeaterCreate(4, TaskGraphEditorComponent_Conditional_2_Conditional_0_For_5_Template, 2, 1, "li", null, _forTrack0);
63
+ i0.ɵɵelementStart(3, "ul", 7);
64
+ i0.ɵɵrepeaterCreate(4, TaskGraphEditorComponent_Conditional_2_Conditional_1_For_5_Template, 2, 1, "li", null, _forTrack0);
57
65
  i0.ɵɵelementEnd()();
58
66
  } if (rf & 2) {
59
67
  const ctx_r0 = i0.ɵɵnextContext(2);
@@ -62,17 +70,33 @@ function TaskGraphEditorComponent_Conditional_2_Conditional_0_Template(rf, ctx)
62
70
  i0.ɵɵadvance(2);
63
71
  i0.ɵɵrepeater(ctx_r0.ValidationErrors);
64
72
  } }
73
+ function TaskGraphEditorComponent_Conditional_2_Conditional_4_Template(rf, ctx) { if (rf & 1) {
74
+ const _r4 = i0.ɵɵgetCurrentView();
75
+ i0.ɵɵelementStart(0, "mj-task-graph-properties", 8);
76
+ i0.ɵɵlistener("TaskPropertyChangeRequested", function TaskGraphEditorComponent_Conditional_2_Conditional_4_Template_mj_task_graph_properties_TaskPropertyChangeRequested_0_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.OnTaskPropertyChangeRequested($event)); })("DependencyConditionChangeRequested", function TaskGraphEditorComponent_Conditional_2_Conditional_4_Template_mj_task_graph_properties_DependencyConditionChangeRequested_0_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.OnDependencyConditionChangeRequested($event)); });
77
+ i0.ɵɵelementEnd();
78
+ } if (rf & 2) {
79
+ const ctx_r0 = i0.ɵɵnextContext(2);
80
+ i0.ɵɵproperty("Task", ctx_r0.SelectedTask)("Spec", ctx_r0.Spec)("ReadOnly", ctx_r0.ReadOnly)("AvailableAgentNames", ctx_r0.AvailableAgentNames)("AvailableActionNames", ctx_r0.AvailableActionNames);
81
+ } }
65
82
  function TaskGraphEditorComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
66
83
  const _r2 = i0.ɵɵgetCurrentView();
67
- i0.ɵɵconditionalCreate(0, TaskGraphEditorComponent_Conditional_2_Conditional_0_Template, 6, 2, "mj-alert", 2);
68
- i0.ɵɵelementStart(1, "mj-flow-editor", 3);
69
- i0.ɵɵlistener("NodeSelected", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_NodeSelected_1_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnNodeSelected($event)); })("NodeRemoved", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_NodeRemoved_1_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnNodeRemoved($event)); })("ConnectionCreated", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_ConnectionCreated_1_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnConnectionCreated($event)); })("ConnectionRemoved", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_ConnectionRemoved_1_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnConnectionRemoved($event)); });
84
+ i0.ɵɵconditionalCreate(0, TaskGraphEditorComponent_Conditional_2_Conditional_0_Template, 1, 1, "mj-alert", 2);
85
+ i0.ɵɵconditionalCreate(1, TaskGraphEditorComponent_Conditional_2_Conditional_1_Template, 6, 2, "mj-alert", 3);
86
+ i0.ɵɵelementStart(2, "div", 4)(3, "mj-flow-editor", 5);
87
+ i0.ɵɵlistener("NodeSelected", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_NodeSelected_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnNodeSelected($event)); })("NodeAdded", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_NodeAdded_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnNodeAdded($event)); })("NodeRemoved", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_NodeRemoved_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnNodeRemoved($event)); })("NodesChanged", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_NodesChanged_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnNodesChanged($event)); })("NodeMoved", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_NodeMoved_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnNodeMoved($event)); })("ConnectionCreated", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_ConnectionCreated_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnConnectionCreated($event)); })("ConnectionRemoved", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_ConnectionRemoved_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnConnectionRemoved($event)); })("ConnectionSelected", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_ConnectionSelected_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnConnectionSelected($event)); })("LegendToggled", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_LegendToggled_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.OnLegendToggled($event)); })("BeforeContextMenu", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_BeforeContextMenu_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.BeforeContextMenu.emit($event)); })("AfterContextMenuAction", function TaskGraphEditorComponent_Conditional_2_Template_mj_flow_editor_AfterContextMenuAction_3_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.AfterContextMenuAction.emit($event)); });
88
+ i0.ɵɵelementEnd();
89
+ i0.ɵɵconditionalCreate(4, TaskGraphEditorComponent_Conditional_2_Conditional_4_Template, 1, 5, "mj-task-graph-properties", 6);
70
90
  i0.ɵɵelementEnd();
71
91
  } if (rf & 2) {
72
92
  const ctx_r0 = i0.ɵɵnextContext();
73
- i0.ɵɵconditional(!ctx_r0.IsValid && ctx_r0.ValidationErrors.length > 0 ? 0 : -1);
93
+ i0.ɵɵconditional(ctx_r0.IsEmpty ? 0 : -1);
74
94
  i0.ɵɵadvance();
75
- i0.ɵɵproperty("Nodes", ctx_r0.Nodes)("Connections", ctx_r0.Connections)("NodeTypes", ctx_r0.NodeTypes)("ReadOnly", ctx_r0.ReadOnly)("ShowToolbar", ctx_r0.ShowToolbar)("ShowPalette", ctx_r0.ShowPalette && !ctx_r0.ReadOnly)("ShowMinimap", ctx_r0.ShowMinimap)("ShowStatusBar", ctx_r0.ShowStatusBar)("AutoLayoutDirection", ctx_r0.AutoLayoutDirection);
95
+ i0.ɵɵconditional(!ctx_r0.IsValid && ctx_r0.ValidationErrors.length > 0 ? 1 : -1);
96
+ i0.ɵɵadvance(2);
97
+ i0.ɵɵproperty("Nodes", ctx_r0.Nodes)("Connections", ctx_r0.Connections)("NodeTypes", ctx_r0.NodeTypes)("ReadOnly", ctx_r0.ReadOnly)("ShowToolbar", ctx_r0.ShowToolbar)("ToolbarVisibility", ctx_r0.ToolbarVisibility)("ToolbarAlign", ctx_r0.ToolbarAlign)("ShowPalette", ctx_r0.ShowPalette && !ctx_r0.ReadOnly)("ShowMinimap", ctx_r0.ShowMinimap)("ShowLegend", ctx_r0.ShowLegend)("ShowStatusBar", ctx_r0.ShowStatusBar)("AutoLayoutDirection", ctx_r0.AutoLayoutDirection);
98
+ i0.ɵɵadvance();
99
+ i0.ɵɵconditional(ctx_r0.ShowProperties && !ctx_r0.ReadOnly ? 4 : -1);
76
100
  } }
77
101
  export class TaskGraphEditorComponent extends BaseAngularComponent {
78
102
  // ── Inputs ───────────────────────────────────────────────────────────────
@@ -100,15 +124,88 @@ export class TaskGraphEditorComponent extends BaseAngularComponent {
100
124
  get RuntimeStatus() {
101
125
  return this.currentRuntime;
102
126
  }
127
+ /**
128
+ * Geometry for the nodes, keyed by `tempId`. Supplying it places the graph instead of laying it
129
+ * out.
130
+ *
131
+ * **Why a host must be able to supply this.** The spec has no layout field, so without it every
132
+ * node projects to the origin — all of them, stacked in one place — and the canvas is left to
133
+ * rescue the situation with a deferred Dagre pass. That pass is fine in the editor, where the
134
+ * author is present and can rearrange. It was not fine in the RUN views, which held the real
135
+ * geometry (a workflow's authored positions, or a computed layout) and had no way to hand it
136
+ * over: every run rendered its steps piled on the origin, and the zoom-to-fit that followed
137
+ * fitted a bounding box one node wide and blew the viewport up past 250%.
138
+ *
139
+ * Applied as the starting geometry only. Once the canvas reports positions of its own — a drag,
140
+ * an arrange — those win, because the person moving a node is the authority on where it goes.
141
+ */
142
+ set NodePositions(value) {
143
+ if (!value || value.size === 0)
144
+ return;
145
+ const key = [...value.keys()].sort().join(',');
146
+ const firstForThisGraph = this.fittedGeometryKey !== key;
147
+ for (const [id, position] of value)
148
+ this.knownPositions.set(id, { ...position });
149
+ // Real geometry means the one-time Dagre pass has nothing to rescue.
150
+ this.hasLaidOut = true;
151
+ this.project();
152
+ // Fit once per graph. Live status / poll re-supplies the same positions as a new Map,
153
+ // and fitting on every write snaps the viewport back while the person is zooming.
154
+ if (firstForThisGraph) {
155
+ this.fittedGeometryKey = key;
156
+ this.zoomToFitSoon();
157
+ }
158
+ }
103
159
  /** Read-only mode. The same component is the viewer — there is no second, weaker renderer. */
104
160
  ReadOnly = false;
105
161
  ShowToolbar = true;
162
+ /** Full bar / chip / recover tab. Run views default this to minimized. */
163
+ ToolbarVisibility = 'shown';
164
+ ToolbarAlign = 'center';
106
165
  ShowPalette = true;
107
166
  ShowMinimap = true;
167
+ /**
168
+ * Whether the legend rides on the canvas.
169
+ *
170
+ * On for authoring, off for a run — and the difference is what the legend is FOR. It explains the
171
+ * authoring vocabulary (what a conditional edge means, what a duplicate default looks like),
172
+ * which is what someone drawing a graph needs. A run view is answering a different question —
173
+ * what happened — and the legend answers none of it while occluding a third of the canvas.
174
+ */
175
+ ShowLegend = true;
108
176
  ShowStatusBar = true;
109
177
  AutoLayoutDirection = 'vertical';
178
+ /**
179
+ * Whether the properties panel rides alongside the canvas.
180
+ *
181
+ * On by default, because without it a step added from the palette can never be named or
182
+ * assigned — the canvas draws structure, the panel supplies content, and one without the other
183
+ * is a graph the author can build but not finish. Hosts embedding the read-only viewer in a chat
184
+ * card turn it off.
185
+ */
186
+ ShowProperties = true;
187
+ /**
188
+ * Agent names offered when assigning a step. Supplied by the host, which owns data access —
189
+ * this is a widgets-layer component and does not query.
190
+ */
191
+ AvailableAgentNames = [];
192
+ /** Action names offered when assigning a step. Same ownership rule as `AvailableAgentNames`. */
193
+ AvailableActionNames = [];
110
194
  /** Shown when there is nothing to draw yet. */
111
195
  EmptyStateMessage = 'No steps yet. Add one to start building this workflow.';
196
+ /**
197
+ * Debug overlay for a run. Empty by default so an embed (agent-run timeline, test harness)
198
+ * never grows breakpoint badges or override styling it did not ask for.
199
+ */
200
+ set DebugOverlay(value) {
201
+ if (this.debugOverlay === value)
202
+ return;
203
+ this.debugOverlay = value;
204
+ this.project();
205
+ }
206
+ get DebugOverlay() {
207
+ return this.debugOverlay;
208
+ }
112
209
  // ── Outputs ──────────────────────────────────────────────────────────────
113
210
  BeforeTaskAdded = new EventEmitter();
114
211
  AfterTaskAdded = new EventEmitter();
@@ -121,21 +218,36 @@ export class TaskGraphEditorComponent extends BaseAngularComponent {
121
218
  BeforeDependencyRemoved = new EventEmitter();
122
219
  AfterDependencyRemoved = new EventEmitter();
123
220
  /** Informational — no `Before` pair, because these report what already happened. */
221
+ /**
222
+ * The legend was shown or hidden from the canvas toolbar.
223
+ *
224
+ * Forwarded so a host can PERSIST the choice. Without it the toolbar's toggle mutates the
225
+ * canvas's own flag and nothing else, so the preference dies with the view — and a host that
226
+ * wanted it remembered would have to add a second control beside the one that already exists,
227
+ * which is how a surface ends up with two buttons doing the same thing and disagreeing.
228
+ */
229
+ LegendToggled = new EventEmitter();
124
230
  SpecChanged = new EventEmitter();
125
231
  SelectionChanged = new EventEmitter();
126
232
  ValidationChanged = new EventEmitter();
127
233
  /** Intent-only — the host navigates; this widget has no Router and must not acquire one. */
128
234
  AgentOpenRequested = new EventEmitter();
129
235
  RecordOpenRequested = new EventEmitter();
236
+ /** A connection was clicked. Payload is the canvas connection, or null when cleared. */
237
+ ConnectionSelected = new EventEmitter();
238
+ BeforeContextMenu = new EventEmitter();
239
+ AfterContextMenuAction = new EventEmitter();
130
240
  // ── Rendered state ───────────────────────────────────────────────────────
131
241
  Nodes = [];
132
242
  Connections = [];
133
- NodeTypes = TASK_GRAPH_NODE_TYPES;
243
+ NodeTypes = [...TASK_GRAPH_NODE_TYPES];
134
244
  SelectedTask = null;
135
245
  ValidationErrors = [];
136
246
  IsValid = true;
247
+ canvas;
137
248
  currentSpec = null;
138
249
  currentRuntime = null;
250
+ debugOverlay = null;
139
251
  get IsEmpty() {
140
252
  return (this.currentSpec?.tasks?.length ?? 0) === 0;
141
253
  }
@@ -160,13 +272,18 @@ export class TaskGraphEditorComponent extends BaseAngularComponent {
160
272
  AddTask(partial = {}) {
161
273
  if (this.ReadOnly || !this.currentSpec)
162
274
  return null;
275
+ // Kind and configuration travel together — a partial that supplies one without the other
276
+ // would be a node the engine cannot run, so an unspecified partial defaults to an unassigned
277
+ // Agent step and the validator says so immediately.
163
278
  const task = {
164
279
  tempId: partial.tempId ?? NextTempId(this.currentSpec),
165
280
  name: partial.name ?? 'New step',
166
281
  description: partial.description ?? '',
167
- agentName: partial.agentName,
168
- assignToUser: partial.assignToUser,
282
+ kind: partial.kind ?? 'Agent',
283
+ configuration: partial.configuration ?? { agentName: '' },
169
284
  dependsOn: partial.dependsOn ?? [],
285
+ policy: partial.policy,
286
+ layout: partial.layout,
170
287
  inputPayload: partial.inputPayload,
171
288
  };
172
289
  const before = new BeforeTaskAddedEventArgs(task);
@@ -261,8 +378,9 @@ export class TaskGraphEditorComponent extends BaseAngularComponent {
261
378
  }
262
379
  /** Asks the host to open the agent behind a task. */
263
380
  RequestAgentOpen(task) {
264
- if (task.agentName) {
265
- this.AgentOpenRequested.emit(new AgentOpenRequestedEventArgs(task.agentName, task));
381
+ const agentName = ConfigOf(task, 'Agent')?.agentName;
382
+ if (agentName) {
383
+ this.AgentOpenRequested.emit(new AgentOpenRequestedEventArgs(agentName, task));
266
384
  }
267
385
  }
268
386
  /** Asks the host to open a record the graph references. */
@@ -273,6 +391,51 @@ export class TaskGraphEditorComponent extends BaseAngularComponent {
273
391
  OnNodeSelected(node) {
274
392
  this.selectTask(node ? this.findTask(node.ID) : null);
275
393
  }
394
+ /**
395
+ * A palette entry was clicked or dragged onto the canvas.
396
+ *
397
+ * **This binding is the bug.** The canvas has always emitted `NodeAdded` for a palette drop, and
398
+ * this component simply never listened — so the node the canvas announced was thrown away, the
399
+ * spec never gained a task, and the author was told "a task graph must contain at least one
400
+ * task" no matter how many times they tried to add one. The canvas does not mutate its own
401
+ * `Nodes` on purpose (the host owns the model); an unheard event is therefore a silent no-op
402
+ * rather than a visible failure, which is why it survived.
403
+ *
404
+ * The new step is selected immediately: it lands unnamed and, for an agent or action step with
405
+ * nothing available to default to, unassigned — so the properties panel is where the author has
406
+ * to go next, and putting them there beats making them find it.
407
+ */
408
+ OnNodeAdded(event) {
409
+ if (this.ReadOnly || !this.currentSpec)
410
+ return;
411
+ const type = GetNodeTypeConfig(event.Node.Type)?.Type;
412
+ // Only an authorable shape can be dropped from the palette. The render set is wider, and a
413
+ // display-only kind arriving here would mean the palette offered something with no editor.
414
+ if (!type || !IsAuthorableNodeType(type))
415
+ return;
416
+ const added = this.AddTask(NewTaskFromNodeType(this.currentSpec, type, {
417
+ agentName: this.AvailableAgentNames[0],
418
+ actionName: this.AvailableActionNames[0],
419
+ }));
420
+ if (!added)
421
+ return;
422
+ // Remember where the canvas put it BEFORE anything re-projects. The spec has no geometry
423
+ // field, so this map is the only record that the author dropped (or clicked) it here — and
424
+ // without it the node would snap back to the origin on the very next edit.
425
+ this.knownPositions.set(added.tempId, { ...event.Node.Position });
426
+ // A graph that has received a hand-placed node is laid out, by definition. Marking it here
427
+ // stops the one-time Dagre pass from firing later and discarding that placement.
428
+ this.hasLaidOut = true;
429
+ this.selectTask(added);
430
+ }
431
+ /** Applies a properties-panel edit through the same vetoable path a canvas edit takes. */
432
+ OnTaskPropertyChangeRequested(args) {
433
+ this.UpdateTask(args.TempId, args.Next);
434
+ }
435
+ /** Applies a properties-panel edge-condition edit. */
436
+ OnDependencyConditionChangeRequested(args) {
437
+ this.SetDependencyCondition(args.FromTempId, args.ToTempId, args.Condition);
438
+ }
276
439
  OnConnectionCreated(event) {
277
440
  this.AddDependency(event.SourceNodeID, event.TargetNodeID);
278
441
  }
@@ -303,43 +466,164 @@ export class TaskGraphEditorComponent extends BaseAngularComponent {
303
466
  this.Connections = [];
304
467
  this.ValidationErrors = [];
305
468
  this.IsValid = true;
469
+ this.hasLaidOut = false;
470
+ this.fittedGeometryKey = null;
471
+ this.knownPositions.clear();
306
472
  return;
307
473
  }
308
- this.Nodes = SpecToNodes(this.currentSpec, this.currentRuntime ?? undefined);
309
- this.Connections = SpecToConnections(this.currentSpec);
474
+ this.Nodes = SpecToNodes(this.currentSpec, this.currentRuntime ?? undefined, this.knownPositions, this.debugOverlay ?? undefined);
475
+ this.Connections = SpecToConnections(this.currentSpec, this.currentRuntime ?? undefined, this.debugOverlay ?? undefined);
310
476
  this.Validate();
477
+ this.arrangeIfNeverLaidOut();
478
+ }
479
+ /**
480
+ * Lays the graph out ONCE — when it arrives with no geometry of its own.
481
+ *
482
+ * A `TaskGraphSpec` carries no positions, so a spec opened for the first time projects with
483
+ * every node at the origin and needs Dagre to make it readable. After that the author's layout
484
+ * is the layout: `knownPositions` carries it across re-projections, and re-arranging again would
485
+ * throw away the arrangement they just made.
486
+ *
487
+ * It must also not run on every edit, because `AutoArrange` ends in `ZoomToFit` — so arranging
488
+ * per change meant the viewport snapped to fit after every added step and every drawn
489
+ * connection, which on a one-node graph zooms to maximum. That is the behaviour being fixed
490
+ * here; the rule mirrors the Flow Agent editor's (`flow-agent-editor.component.ts`), which has
491
+ * always arranged only when every node sits at the origin.
492
+ */
493
+ arrangeIfNeverLaidOut() {
494
+ if (this.Nodes.length === 0)
495
+ return;
496
+ if (this.hasLaidOut)
497
+ return;
498
+ // Nothing to rescue a layout from: a spec whose nodes all sit at the origin has never been
499
+ // arranged. One node at the origin is the legitimate starting case too.
500
+ const allAtOrigin = this.Nodes.every((n) => n.Position.X === 0 && n.Position.Y === 0);
501
+ if (!allAtOrigin) {
502
+ this.hasLaidOut = true;
503
+ return;
504
+ }
505
+ this.hasLaidOut = true;
506
+ // Deferred one turn: the canvas has to render the nodes before Dagre can measure them.
507
+ // Cleared on destroy so a pending layout cannot run against a torn-down view.
508
+ if (this.pendingLayout !== null)
509
+ clearTimeout(this.pendingLayout);
510
+ this.pendingLayout = setTimeout(() => {
511
+ this.pendingLayout = null;
512
+ this.canvas?.AutoArrange(this.AutoLayoutDirection);
513
+ });
514
+ }
515
+ /**
516
+ * Fits the viewport to the graph, once the canvas has drawn it.
517
+ *
518
+ * Deferred for the same reason the layout pass is: `fitToScreen` measures the rendered nodes, so
519
+ * calling it in the same turn as the projection fits whatever was on screen a moment ago. With
520
+ * every node still at the origin that bounding box is a single node wide, and "fit" means zoom
521
+ * to ~265% — the symptom that made a four-step workflow look like one enormous box.
522
+ */
523
+ zoomToFitSoon() {
524
+ if (this.pendingLayout !== null)
525
+ clearTimeout(this.pendingLayout);
526
+ this.pendingLayout = setTimeout(() => {
527
+ this.pendingLayout = null;
528
+ this.canvas?.ZoomToFit();
529
+ });
530
+ }
531
+ /**
532
+ * The canvas is the authority on geometry, so remember what it reports.
533
+ *
534
+ * Without this the spec — which has no geometry field — is the only survivor of a re-projection,
535
+ * and every edit silently moved every node back to the origin. That is what forced a re-arrange
536
+ * (and therefore a re-zoom) on each change.
537
+ */
538
+ OnNodesChanged(nodes) {
539
+ for (const n of nodes)
540
+ this.knownPositions.set(n.ID, { ...n.Position });
311
541
  }
542
+ /** The canvas toolbar showed or hid the legend; tell whoever is remembering that choice. */
543
+ OnLegendToggled(show) {
544
+ this.ShowLegend = show;
545
+ this.LegendToggled.emit(show);
546
+ }
547
+ OnConnectionSelected(connection) {
548
+ this.ConnectionSelected.emit(connection);
549
+ }
550
+ /** A single node was dragged. Same authority, narrower event. */
551
+ OnNodeMoved(event) {
552
+ this.knownPositions.set(event.NodeID, { ...event.NewPosition });
553
+ }
554
+ ngOnDestroy() {
555
+ if (this.pendingLayout !== null) {
556
+ clearTimeout(this.pendingLayout);
557
+ this.pendingLayout = null;
558
+ }
559
+ }
560
+ /** The topology the current layout was computed for; '' when nothing has been laid out. */
561
+ /**
562
+ * Node geometry, which the spec cannot hold.
563
+ *
564
+ * `TaskGraphSpec` is an execution contract with no layout field, so a re-projection would
565
+ * otherwise return every node to the origin. Keyed by `tempId`; written from the canvas
566
+ * (`NodesChanged` / `NodeMoved`) and from the drop position of a newly added node, and read back
567
+ * by `SpecToNodes` on every projection.
568
+ */
569
+ knownPositions = new Map();
570
+ /** Whether the one-time Dagre pass has run (or been made unnecessary by a hand-placed node). */
571
+ hasLaidOut = false;
572
+ /** Task-id set we last fitted. Same ids + a new Map must not re-fit. */
573
+ fittedGeometryKey = null;
574
+ pendingLayout = null;
312
575
  static ɵfac = /*@__PURE__*/ (() => { let ɵTaskGraphEditorComponent_BaseFactory; return function TaskGraphEditorComponent_Factory(__ngFactoryType__) { return (ɵTaskGraphEditorComponent_BaseFactory || (ɵTaskGraphEditorComponent_BaseFactory = i0.ɵɵgetInheritedFactory(TaskGraphEditorComponent)))(__ngFactoryType__ || TaskGraphEditorComponent); }; })();
313
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TaskGraphEditorComponent, selectors: [["mj-task-graph-editor"]], inputs: { Spec: "Spec", RuntimeStatus: "RuntimeStatus", ReadOnly: "ReadOnly", ShowToolbar: "ShowToolbar", ShowPalette: "ShowPalette", ShowMinimap: "ShowMinimap", ShowStatusBar: "ShowStatusBar", AutoLayoutDirection: "AutoLayoutDirection", EmptyStateMessage: "EmptyStateMessage" }, outputs: { BeforeTaskAdded: "BeforeTaskAdded", AfterTaskAdded: "AfterTaskAdded", BeforeTaskRemoved: "BeforeTaskRemoved", AfterTaskRemoved: "AfterTaskRemoved", BeforeTaskUpdated: "BeforeTaskUpdated", AfterTaskUpdated: "AfterTaskUpdated", BeforeDependencyAdded: "BeforeDependencyAdded", AfterDependencyAdded: "AfterDependencyAdded", BeforeDependencyRemoved: "BeforeDependencyRemoved", AfterDependencyRemoved: "AfterDependencyRemoved", SpecChanged: "SpecChanged", SelectionChanged: "SelectionChanged", ValidationChanged: "ValidationChanged", AgentOpenRequested: "AgentOpenRequested", RecordOpenRequested: "RecordOpenRequested" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 3, vars: 1, consts: [[1, "mj-tge"], ["Icon", "fa-diagram-project", "Title", "Nothing here yet", 3, "Message"], ["Variant", "warning", 1, "mj-tge__validation"], [1, "mj-tge__canvas", 3, "NodeSelected", "NodeRemoved", "ConnectionCreated", "ConnectionRemoved", "Nodes", "Connections", "NodeTypes", "ReadOnly", "ShowToolbar", "ShowPalette", "ShowMinimap", "ShowStatusBar", "AutoLayoutDirection"], [1, "mj-tge__validation-list"]], template: function TaskGraphEditorComponent_Template(rf, ctx) { if (rf & 1) {
576
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TaskGraphEditorComponent, selectors: [["mj-task-graph-editor"]], viewQuery: function TaskGraphEditorComponent_Query(rf, ctx) { if (rf & 1) {
577
+ i0.ɵɵviewQuery(FlowEditorComponent, 5);
578
+ } if (rf & 2) {
579
+ let _t;
580
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.canvas = _t.first);
581
+ } }, inputs: { Spec: "Spec", RuntimeStatus: "RuntimeStatus", NodePositions: "NodePositions", ReadOnly: "ReadOnly", ShowToolbar: "ShowToolbar", ToolbarVisibility: "ToolbarVisibility", ToolbarAlign: "ToolbarAlign", ShowPalette: "ShowPalette", ShowMinimap: "ShowMinimap", ShowLegend: "ShowLegend", ShowStatusBar: "ShowStatusBar", AutoLayoutDirection: "AutoLayoutDirection", ShowProperties: "ShowProperties", AvailableAgentNames: "AvailableAgentNames", AvailableActionNames: "AvailableActionNames", EmptyStateMessage: "EmptyStateMessage", DebugOverlay: "DebugOverlay" }, outputs: { BeforeTaskAdded: "BeforeTaskAdded", AfterTaskAdded: "AfterTaskAdded", BeforeTaskRemoved: "BeforeTaskRemoved", AfterTaskRemoved: "AfterTaskRemoved", BeforeTaskUpdated: "BeforeTaskUpdated", AfterTaskUpdated: "AfterTaskUpdated", BeforeDependencyAdded: "BeforeDependencyAdded", AfterDependencyAdded: "AfterDependencyAdded", BeforeDependencyRemoved: "BeforeDependencyRemoved", AfterDependencyRemoved: "AfterDependencyRemoved", LegendToggled: "LegendToggled", SpecChanged: "SpecChanged", SelectionChanged: "SelectionChanged", ValidationChanged: "ValidationChanged", AgentOpenRequested: "AgentOpenRequested", RecordOpenRequested: "RecordOpenRequested", ConnectionSelected: "ConnectionSelected", BeforeContextMenu: "BeforeContextMenu", AfterContextMenuAction: "AfterContextMenuAction" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 3, vars: 1, consts: [[1, "mj-tge"], ["Icon", "fa-diagram-project", "Title", "Nothing here yet", 3, "Message"], ["Variant", "info", 1, "mj-tge__empty-hint", 3, "Message"], ["Variant", "warning", 1, "mj-tge__validation"], [1, "mj-tge__workspace"], [1, "mj-tge__canvas", 3, "NodeSelected", "NodeAdded", "NodeRemoved", "NodesChanged", "NodeMoved", "ConnectionCreated", "ConnectionRemoved", "ConnectionSelected", "LegendToggled", "BeforeContextMenu", "AfterContextMenuAction", "Nodes", "Connections", "NodeTypes", "ReadOnly", "ShowToolbar", "ToolbarVisibility", "ToolbarAlign", "ShowPalette", "ShowMinimap", "ShowLegend", "ShowStatusBar", "AutoLayoutDirection"], [1, "mj-tge__properties", 3, "Task", "Spec", "ReadOnly", "AvailableAgentNames", "AvailableActionNames"], [1, "mj-tge__validation-list"], [1, "mj-tge__properties", 3, "TaskPropertyChangeRequested", "DependencyConditionChangeRequested", "Task", "Spec", "ReadOnly", "AvailableAgentNames", "AvailableActionNames"]], template: function TaskGraphEditorComponent_Template(rf, ctx) { if (rf & 1) {
314
582
  i0.ɵɵelementStart(0, "div", 0);
315
- i0.ɵɵconditionalCreate(1, TaskGraphEditorComponent_Conditional_1_Template, 1, 1, "mj-empty-state", 1)(2, TaskGraphEditorComponent_Conditional_2_Template, 2, 10);
583
+ i0.ɵɵconditionalCreate(1, TaskGraphEditorComponent_Conditional_1_Template, 1, 1, "mj-empty-state", 1)(2, TaskGraphEditorComponent_Conditional_2_Template, 5, 15);
316
584
  i0.ɵɵelementEnd();
317
585
  } if (rf & 2) {
318
586
  i0.ɵɵadvance();
319
- i0.ɵɵconditional(ctx.IsEmpty ? 1 : 2);
320
- } }, dependencies: [i1.FlowEditorComponent, i2.MJEmptyStateComponent, i2.MJAlertComponent], styles: ["\n\n.mj-tge[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 0;\n background: var(--mj-bg-page);\n}\n\n.mj-tge__validation[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n margin: var(--mj-space-sm, 0.5rem);\n}\n\n.mj-tge__validation-list[_ngcontent-%COMP%] {\n margin: var(--mj-space-xs, 0.25rem) 0 0;\n padding-inline-start: 1.25rem;\n}\n\n\n\n.mj-tge__canvas[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-height: 0;\n}"] });
587
+ i0.ɵɵconditional(ctx.IsEmpty && ctx.ReadOnly ? 1 : 2);
588
+ } }, dependencies: [i1.FlowEditorComponent, i2.MJEmptyStateComponent, i2.MJAlertComponent, i3.TaskGraphPropertiesPanelComponent], styles: ["\n\n\n\n\n\n[_nghost-%COMP%] {\n display: block;\n height: 100%;\n min-height: 0;\n}\n\n.mj-tge[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 0;\n background: var(--mj-bg-page);\n}\n\n.mj-tge__validation[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n margin: var(--mj-space-sm, 0.5rem);\n}\n\n.mj-tge__validation-list[_ngcontent-%COMP%] {\n margin: var(--mj-space-xs, 0.25rem) 0 0;\n padding-inline-start: 1.25rem;\n}\n\n\n\n.mj-tge__workspace[_ngcontent-%COMP%] {\n display: flex;\n flex: 1 1 auto;\n min-height: 0;\n}\n\n\n\n.mj-tge__canvas[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n}\n\n.mj-tge__properties[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n width: 300px;\n min-height: 0;\n overflow-y: auto;\n border-left: 1px solid var(--mj-border-default);\n}\n\n@media (max-width: 900px) {\n \n\n .mj-tge__workspace[_ngcontent-%COMP%] {\n flex-direction: column;\n }\n\n .mj-tge__properties[_ngcontent-%COMP%] {\n width: 100%;\n max-height: 40%;\n border-left: 0;\n border-top: 1px solid var(--mj-border-default);\n }\n}\n\n\n\n.mj-tge__empty-hint[_ngcontent-%COMP%] {\n margin: var(--mj-space-2) var(--mj-space-3) 0;\n}"] });
321
589
  }
322
590
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TaskGraphEditorComponent, [{
323
591
  type: Component,
324
- args: [{ standalone: false, selector: 'mj-task-graph-editor', template: "<div class=\"mj-tge\">\n @if (IsEmpty) {\n <mj-empty-state\n Icon=\"fa-diagram-project\"\n Title=\"Nothing here yet\"\n [Message]=\"EmptyStateMessage\">\n </mj-empty-state>\n } @else {\n @if (!IsValid && ValidationErrors.length > 0) {\n <!-- Author-time validation comes from the SAME function the engine runs at submission, so a\n graph that looks fine here cannot be rejected later for a reason the canvas never showed. -->\n <mj-alert Variant=\"warning\" class=\"mj-tge__validation\">\n <strong>{{ ValidationErrors.length }} problem{{ ValidationErrors.length === 1 ? '' : 's' }} to fix</strong>\n <ul class=\"mj-tge__validation-list\">\n @for (err of ValidationErrors; track err.Code + (err.TempId ?? '')) {\n <li>{{ err.Message }}</li>\n }\n </ul>\n </mj-alert>\n }\n\n <mj-flow-editor\n class=\"mj-tge__canvas\"\n [Nodes]=\"Nodes\"\n [Connections]=\"Connections\"\n [NodeTypes]=\"NodeTypes\"\n [ReadOnly]=\"ReadOnly\"\n [ShowToolbar]=\"ShowToolbar\"\n [ShowPalette]=\"ShowPalette && !ReadOnly\"\n [ShowMinimap]=\"ShowMinimap\"\n [ShowStatusBar]=\"ShowStatusBar\"\n [AutoLayoutDirection]=\"AutoLayoutDirection\"\n (NodeSelected)=\"OnNodeSelected($event)\"\n (NodeRemoved)=\"OnNodeRemoved($event)\"\n (ConnectionCreated)=\"OnConnectionCreated($event)\"\n (ConnectionRemoved)=\"OnConnectionRemoved($event)\">\n </mj-flow-editor>\n }\n</div>\n", styles: ["/* Design tokens only \u2014 no hardcoded colors (see .claude/rules/design-tokens.md). */\n.mj-tge {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 0;\n background: var(--mj-bg-page);\n}\n\n.mj-tge__validation {\n flex: 0 0 auto;\n margin: var(--mj-space-sm, 0.5rem);\n}\n\n.mj-tge__validation-list {\n margin: var(--mj-space-xs, 0.25rem) 0 0;\n padding-inline-start: 1.25rem;\n}\n\n/* The canvas takes the remaining height; min-height:0 is what lets it actually shrink inside flex. */\n.mj-tge__canvas {\n flex: 1 1 auto;\n min-height: 0;\n}\n"] }]
592
+ args: [{ standalone: false, selector: 'mj-task-graph-editor', template: "<div class=\"mj-tge\">\n <!-- An EDITABLE empty canvas must still render the editor, because the palette is the only way to\n add a step \u2014 showing a bare empty state instead made \"Add one to start building this workflow\"\n an instruction the user could not follow. The hint stays, as a banner above a usable canvas. -->\n @if (IsEmpty && ReadOnly) {\n <mj-empty-state\n Icon=\"fa-diagram-project\"\n Title=\"Nothing here yet\"\n [Message]=\"EmptyStateMessage\">\n </mj-empty-state>\n } @else {\n @if (IsEmpty) {\n <mj-alert Variant=\"info\" class=\"mj-tge__empty-hint\" [Message]=\"EmptyStateMessage\"></mj-alert>\n }\n @if (!IsValid && ValidationErrors.length > 0) {\n <!-- Author-time validation comes from the SAME function the engine runs at submission, so a\n graph that looks fine here cannot be rejected later for a reason the canvas never showed. -->\n <mj-alert Variant=\"warning\" class=\"mj-tge__validation\">\n <strong>{{ ValidationErrors.length }} problem{{ ValidationErrors.length === 1 ? '' : 's' }} to fix</strong>\n <ul class=\"mj-tge__validation-list\">\n @for (err of ValidationErrors; track err.Code + (err.TempId ?? '')) {\n <li>{{ err.Message }}</li>\n }\n </ul>\n </mj-alert>\n }\n\n <div class=\"mj-tge__workspace\">\n <mj-flow-editor\n class=\"mj-tge__canvas\"\n [Nodes]=\"Nodes\"\n [Connections]=\"Connections\"\n [NodeTypes]=\"NodeTypes\"\n [ReadOnly]=\"ReadOnly\"\n [ShowToolbar]=\"ShowToolbar\"\n [ToolbarVisibility]=\"ToolbarVisibility\"\n [ToolbarAlign]=\"ToolbarAlign\"\n [ShowPalette]=\"ShowPalette && !ReadOnly\"\n [ShowMinimap]=\"ShowMinimap\"\n [ShowLegend]=\"ShowLegend\"\n [ShowStatusBar]=\"ShowStatusBar\"\n [AutoLayoutDirection]=\"AutoLayoutDirection\"\n (NodeSelected)=\"OnNodeSelected($event)\"\n (NodeAdded)=\"OnNodeAdded($event)\"\n (NodeRemoved)=\"OnNodeRemoved($event)\"\n (NodesChanged)=\"OnNodesChanged($event)\"\n (NodeMoved)=\"OnNodeMoved($event)\"\n (ConnectionCreated)=\"OnConnectionCreated($event)\"\n (ConnectionRemoved)=\"OnConnectionRemoved($event)\"\n (ConnectionSelected)=\"OnConnectionSelected($event)\"\n (LegendToggled)=\"OnLegendToggled($event)\"\n (BeforeContextMenu)=\"BeforeContextMenu.emit($event)\"\n (AfterContextMenuAction)=\"AfterContextMenuAction.emit($event)\">\n </mj-flow-editor>\n\n <!-- The canvas draws structure; this supplies content. A step added from the palette arrives\n unnamed and (with nothing to default to) unassigned, so without this panel the author can\n create a step but never finish one. -->\n @if (ShowProperties && !ReadOnly) {\n <mj-task-graph-properties\n class=\"mj-tge__properties\"\n [Task]=\"SelectedTask\"\n [Spec]=\"Spec\"\n [ReadOnly]=\"ReadOnly\"\n [AvailableAgentNames]=\"AvailableAgentNames\"\n [AvailableActionNames]=\"AvailableActionNames\"\n (TaskPropertyChangeRequested)=\"OnTaskPropertyChangeRequested($event)\"\n (DependencyConditionChangeRequested)=\"OnDependencyConditionChangeRequested($event)\">\n </mj-task-graph-properties>\n }\n </div>\n }\n</div>\n", styles: ["/* Design tokens only \u2014 no hardcoded colors (see .claude/rules/design-tokens.md). */\n\n/* Same reason as the flow editor's host: a custom element is inline with auto height, so the\n `height: 100%` on `.mj-tge` below had nothing to resolve against. */\n:host {\n display: block;\n height: 100%;\n min-height: 0;\n}\n\n.mj-tge {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 0;\n background: var(--mj-bg-page);\n}\n\n.mj-tge__validation {\n flex: 0 0 auto;\n margin: var(--mj-space-sm, 0.5rem);\n}\n\n.mj-tge__validation-list {\n margin: var(--mj-space-xs, 0.25rem) 0 0;\n padding-inline-start: 1.25rem;\n}\n\n/* Canvas + properties side by side; the row takes the remaining height. */\n.mj-tge__workspace {\n display: flex;\n flex: 1 1 auto;\n min-height: 0;\n}\n\n/* The canvas takes the remaining width; min-width:0 is what lets it actually shrink inside flex. */\n.mj-tge__canvas {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n}\n\n.mj-tge__properties {\n flex: 0 0 auto;\n width: 300px;\n min-height: 0;\n overflow-y: auto;\n border-left: 1px solid var(--mj-border-default);\n}\n\n@media (max-width: 900px) {\n /* Below this the two side by side leave neither usable; the panel drops under the canvas. */\n .mj-tge__workspace {\n flex-direction: column;\n }\n\n .mj-tge__properties {\n width: 100%;\n max-height: 40%;\n border-left: 0;\n border-top: 1px solid var(--mj-border-default);\n }\n}\n\n/* Shown above a usable, empty canvas \u2014 the palette stays reachable so the hint's advice is followable. */\n.mj-tge__empty-hint {\n margin: var(--mj-space-2) var(--mj-space-3) 0;\n}\n"] }]
325
593
  }], null, { Spec: [{
326
594
  type: Input
327
595
  }], RuntimeStatus: [{
328
596
  type: Input
597
+ }], NodePositions: [{
598
+ type: Input
329
599
  }], ReadOnly: [{
330
600
  type: Input
331
601
  }], ShowToolbar: [{
332
602
  type: Input
603
+ }], ToolbarVisibility: [{
604
+ type: Input
605
+ }], ToolbarAlign: [{
606
+ type: Input
333
607
  }], ShowPalette: [{
334
608
  type: Input
335
609
  }], ShowMinimap: [{
336
610
  type: Input
611
+ }], ShowLegend: [{
612
+ type: Input
337
613
  }], ShowStatusBar: [{
338
614
  type: Input
339
615
  }], AutoLayoutDirection: [{
340
616
  type: Input
617
+ }], ShowProperties: [{
618
+ type: Input
619
+ }], AvailableAgentNames: [{
620
+ type: Input
621
+ }], AvailableActionNames: [{
622
+ type: Input
341
623
  }], EmptyStateMessage: [{
342
624
  type: Input
625
+ }], DebugOverlay: [{
626
+ type: Input
343
627
  }], BeforeTaskAdded: [{
344
628
  type: Output
345
629
  }], AfterTaskAdded: [{
@@ -360,6 +644,8 @@ export class TaskGraphEditorComponent extends BaseAngularComponent {
360
644
  type: Output
361
645
  }], AfterDependencyRemoved: [{
362
646
  type: Output
647
+ }], LegendToggled: [{
648
+ type: Output
363
649
  }], SpecChanged: [{
364
650
  type: Output
365
651
  }], SelectionChanged: [{
@@ -370,6 +656,15 @@ export class TaskGraphEditorComponent extends BaseAngularComponent {
370
656
  type: Output
371
657
  }], RecordOpenRequested: [{
372
658
  type: Output
659
+ }], ConnectionSelected: [{
660
+ type: Output
661
+ }], BeforeContextMenu: [{
662
+ type: Output
663
+ }], AfterContextMenuAction: [{
664
+ type: Output
665
+ }], canvas: [{
666
+ type: ViewChild,
667
+ args: [FlowEditorComponent]
373
668
  }] }); })();
374
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TaskGraphEditorComponent, { className: "TaskGraphEditorComponent", filePath: "src/lib/task-graph-editor.component.ts", lineNumber: 80 }); })();
669
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TaskGraphEditorComponent, { className: "TaskGraphEditorComponent", filePath: "src/lib/task-graph-editor.component.ts", lineNumber: 97 }); })();
375
670
  //# sourceMappingURL=task-graph-editor.component.js.map