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

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 (31) hide show
  1. package/LICENSE +7 -0
  2. package/dist/lib/task-graph-canvas-adapter.d.ts +98 -0
  3. package/dist/lib/task-graph-canvas-adapter.d.ts.map +1 -0
  4. package/dist/lib/task-graph-canvas-adapter.js +244 -0
  5. package/dist/lib/task-graph-canvas-adapter.js.map +1 -0
  6. package/dist/lib/task-graph-editor-events.d.ts +137 -0
  7. package/dist/lib/task-graph-editor-events.d.ts.map +1 -0
  8. package/dist/lib/task-graph-editor-events.js +180 -0
  9. package/dist/lib/task-graph-editor-events.js.map +1 -0
  10. package/dist/lib/task-graph-editor.component.d.ts +128 -0
  11. package/dist/lib/task-graph-editor.component.d.ts.map +1 -0
  12. package/dist/lib/task-graph-editor.component.js +375 -0
  13. package/dist/lib/task-graph-editor.component.js.map +1 -0
  14. package/dist/lib/task-graph-editor.module.d.ts +13 -0
  15. package/dist/lib/task-graph-editor.module.d.ts.map +1 -0
  16. package/dist/lib/task-graph-editor.module.js +23 -0
  17. package/dist/lib/task-graph-editor.module.js.map +1 -0
  18. package/dist/lib/task-graph-properties-panel.component.d.ts +71 -0
  19. package/dist/lib/task-graph-properties-panel.component.d.ts.map +1 -0
  20. package/dist/lib/task-graph-properties-panel.component.js +238 -0
  21. package/dist/lib/task-graph-properties-panel.component.js.map +1 -0
  22. package/dist/lib/task-graph-runtime-source.d.ts +55 -0
  23. package/dist/lib/task-graph-runtime-source.d.ts.map +1 -0
  24. package/dist/lib/task-graph-runtime-source.js +66 -0
  25. package/dist/lib/task-graph-runtime-source.js.map +1 -0
  26. package/dist/public-api.d.ts +7 -0
  27. package/dist/public-api.d.ts.map +1 -0
  28. package/dist/public-api.js +15 -0
  29. package/dist/public-api.js.map +1 -0
  30. package/package.json +52 -7
  31. package/README.md +0 -45
@@ -0,0 +1,238 @@
1
+ /**
2
+ * @fileoverview Edits the selected task's properties, plus the graph-level traversal settings.
3
+ *
4
+ * Split out of the canvas component for the reason panels usually are — the canvas is about
5
+ * *structure* (what connects to what) and this is about *content* (what a step actually does). But
6
+ * also for a sharper reason: a host embedding the read-only viewer in a chat card or a run history
7
+ * pane wants the graph without a form beside it, and a panel welded into the canvas cannot be
8
+ * declined.
9
+ *
10
+ * Emits intent rather than mutating: the canvas component owns the spec, so every edit here leaves
11
+ * as a request the parent applies through the same `Before*`/`After*` path a drag or a delete takes.
12
+ * Two write paths into one spec would be two places for the veto contract to be wrong.
13
+ *
14
+ * @module @memberjunction/ng-task-graph-editor
15
+ */
16
+ import { Component, EventEmitter, Input, Output } from '@angular/core';
17
+ import { GetDependencies } from './task-graph-canvas-adapter';
18
+ import * as i0 from "@angular/core";
19
+ import * as i1 from "@angular/forms";
20
+ import * as i2 from "@memberjunction/ng-ui-components";
21
+ const _forTrack0 = ($index, $item) => $item.FromTempId;
22
+ function TaskGraphPropertiesPanelComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
23
+ i0.ɵɵelement(0, "mj-empty-state", 1);
24
+ } }
25
+ function TaskGraphPropertiesPanelComponent_Conditional_2_Conditional_15_For_2_Template(rf, ctx) { if (rf & 1) {
26
+ i0.ɵɵelementStart(0, "option", 14);
27
+ i0.ɵɵtext(1);
28
+ i0.ɵɵelementEnd();
29
+ } if (rf & 2) {
30
+ const name_r4 = ctx.$implicit;
31
+ i0.ɵɵproperty("value", name_r4);
32
+ i0.ɵɵadvance();
33
+ i0.ɵɵtextInterpolate(name_r4);
34
+ } }
35
+ function TaskGraphPropertiesPanelComponent_Conditional_2_Conditional_15_Template(rf, ctx) { if (rf & 1) {
36
+ const _r3 = i0.ɵɵgetCurrentView();
37
+ i0.ɵɵelementStart(0, "select", 13);
38
+ i0.ɵɵtwoWayListener("ngModelChange", function TaskGraphPropertiesPanelComponent_Conditional_2_Conditional_15_Template_select_ngModelChange_0_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); i0.ɵɵtwoWayBindingSet(ctx_r1.Draft.agentName, $event) || (ctx_r1.Draft.agentName = $event); return i0.ɵɵresetView($event); });
39
+ i0.ɵɵlistener("change", function TaskGraphPropertiesPanelComponent_Conditional_2_Conditional_15_Template_select_change_0_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.Commit()); });
40
+ i0.ɵɵrepeaterCreate(1, TaskGraphPropertiesPanelComponent_Conditional_2_Conditional_15_For_2_Template, 2, 2, "option", 14, i0.ɵɵrepeaterTrackByIdentity);
41
+ i0.ɵɵelementEnd();
42
+ } if (rf & 2) {
43
+ const ctx_r1 = i0.ɵɵnextContext(2);
44
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.Draft.agentName);
45
+ i0.ɵɵproperty("disabled", ctx_r1.ReadOnly);
46
+ i0.ɵɵadvance();
47
+ i0.ɵɵrepeater(ctx_r1.AvailableAgentNames);
48
+ } }
49
+ function TaskGraphPropertiesPanelComponent_Conditional_2_Conditional_16_Template(rf, ctx) { if (rf & 1) {
50
+ i0.ɵɵelement(0, "mj-alert", 12);
51
+ } }
52
+ function TaskGraphPropertiesPanelComponent_Conditional_2_Conditional_17_For_3_Template(rf, ctx) { if (rf & 1) {
53
+ const _r5 = i0.ɵɵgetCurrentView();
54
+ i0.ɵɵelementStart(0, "div", 15)(1, "span", 16);
55
+ i0.ɵɵelement(2, "i", 17);
56
+ i0.ɵɵtext(3);
57
+ i0.ɵɵelementEnd();
58
+ i0.ɵɵelementStart(4, "input", 18);
59
+ i0.ɵɵlistener("blur", function TaskGraphPropertiesPanelComponent_Conditional_2_Conditional_17_For_3_Template_input_blur_4_listener($event) { const edge_r6 = i0.ɵɵrestoreView(_r5).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.CommitCondition(edge_r6.FromTempId, $event.target.value)); });
60
+ i0.ɵɵelementEnd()();
61
+ } if (rf & 2) {
62
+ const edge_r6 = ctx.$implicit;
63
+ const ctx_r1 = i0.ɵɵnextContext(3);
64
+ i0.ɵɵadvance(3);
65
+ i0.ɵɵtextInterpolate1(" ", edge_r6.FromName);
66
+ i0.ɵɵadvance();
67
+ i0.ɵɵproperty("ngModel", edge_r6.Condition)("disabled", ctx_r1.ReadOnly);
68
+ } }
69
+ function TaskGraphPropertiesPanelComponent_Conditional_2_Conditional_17_Template(rf, ctx) { if (rf & 1) {
70
+ i0.ɵɵelementStart(0, "label", 6);
71
+ i0.ɵɵtext(1, "Runs after");
72
+ i0.ɵɵelementEnd();
73
+ i0.ɵɵrepeaterCreate(2, TaskGraphPropertiesPanelComponent_Conditional_2_Conditional_17_For_3_Template, 5, 3, "div", 15, _forTrack0);
74
+ } if (rf & 2) {
75
+ const ctx_r1 = i0.ɵɵnextContext(2);
76
+ i0.ɵɵadvance(2);
77
+ i0.ɵɵrepeater(ctx_r1.IncomingEdges);
78
+ } }
79
+ function TaskGraphPropertiesPanelComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
80
+ const _r1 = i0.ɵɵgetCurrentView();
81
+ i0.ɵɵelementStart(0, "label", 2);
82
+ i0.ɵɵtext(1, "Name");
83
+ i0.ɵɵelementEnd();
84
+ i0.ɵɵelementStart(2, "input", 3);
85
+ i0.ɵɵtwoWayListener("ngModelChange", function TaskGraphPropertiesPanelComponent_Conditional_2_Template_input_ngModelChange_2_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r1.Draft.name, $event) || (ctx_r1.Draft.name = $event); return i0.ɵɵresetView($event); });
86
+ i0.ɵɵlistener("blur", function TaskGraphPropertiesPanelComponent_Conditional_2_Template_input_blur_2_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.Commit()); });
87
+ i0.ɵɵelementEnd();
88
+ i0.ɵɵelementStart(3, "label", 4);
89
+ i0.ɵɵtext(4, "What this step does");
90
+ i0.ɵɵelementEnd();
91
+ i0.ɵɵelementStart(5, "textarea", 5);
92
+ i0.ɵɵtwoWayListener("ngModelChange", function TaskGraphPropertiesPanelComponent_Conditional_2_Template_textarea_ngModelChange_5_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r1.Draft.description, $event) || (ctx_r1.Draft.description = $event); return i0.ɵɵresetView($event); });
93
+ i0.ɵɵlistener("blur", function TaskGraphPropertiesPanelComponent_Conditional_2_Template_textarea_blur_5_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.Commit()); });
94
+ i0.ɵɵelementEnd();
95
+ i0.ɵɵelementStart(6, "label", 6);
96
+ i0.ɵɵtext(7, "Who does it");
97
+ i0.ɵɵelementEnd();
98
+ i0.ɵɵelementStart(8, "div", 7)(9, "button", 8);
99
+ i0.ɵɵlistener("click", function TaskGraphPropertiesPanelComponent_Conditional_2_Template_button_click_9_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.SetAssignment("agent")); });
100
+ i0.ɵɵelement(10, "i", 9);
101
+ i0.ɵɵtext(11, " An agent ");
102
+ i0.ɵɵelementEnd();
103
+ i0.ɵɵelementStart(12, "button", 8);
104
+ i0.ɵɵlistener("click", function TaskGraphPropertiesPanelComponent_Conditional_2_Template_button_click_12_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.SetAssignment("human")); });
105
+ i0.ɵɵelement(13, "i", 10);
106
+ i0.ɵɵtext(14, " A person ");
107
+ i0.ɵɵelementEnd()();
108
+ i0.ɵɵconditionalCreate(15, TaskGraphPropertiesPanelComponent_Conditional_2_Conditional_15_Template, 3, 2, "select", 11)(16, TaskGraphPropertiesPanelComponent_Conditional_2_Conditional_16_Template, 1, 0, "mj-alert", 12);
109
+ i0.ɵɵconditionalCreate(17, TaskGraphPropertiesPanelComponent_Conditional_2_Conditional_17_Template, 4, 0);
110
+ } if (rf & 2) {
111
+ const ctx_r1 = i0.ɵɵnextContext();
112
+ i0.ɵɵadvance(2);
113
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.Draft.name);
114
+ i0.ɵɵproperty("disabled", ctx_r1.ReadOnly);
115
+ i0.ɵɵadvance(3);
116
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.Draft.description);
117
+ i0.ɵɵproperty("disabled", ctx_r1.ReadOnly);
118
+ i0.ɵɵadvance(4);
119
+ i0.ɵɵproperty("variant", !ctx_r1.IsHumanTask ? "primary" : "outline")("disabled", ctx_r1.ReadOnly);
120
+ i0.ɵɵadvance(3);
121
+ i0.ɵɵproperty("variant", ctx_r1.IsHumanTask ? "primary" : "outline")("disabled", ctx_r1.ReadOnly);
122
+ i0.ɵɵadvance(3);
123
+ i0.ɵɵconditional(!ctx_r1.IsHumanTask ? 15 : 16);
124
+ i0.ɵɵadvance(2);
125
+ i0.ɵɵconditional(ctx_r1.IncomingEdges.length > 0 ? 17 : -1);
126
+ } }
127
+ /** A requested change to one task. The parent applies it; this panel never writes. */
128
+ export class TaskPropertyChangeRequestedEventArgs {
129
+ TempId;
130
+ Next;
131
+ constructor(TempId, Next) {
132
+ this.TempId = TempId;
133
+ this.Next = Next;
134
+ }
135
+ }
136
+ /** A requested change to a dependency edge's condition. */
137
+ export class DependencyConditionChangeRequestedEventArgs {
138
+ FromTempId;
139
+ ToTempId;
140
+ Condition;
141
+ constructor(FromTempId, ToTempId,
142
+ /** Empty string clears the condition, making the edge unconditional again. */
143
+ Condition) {
144
+ this.FromTempId = FromTempId;
145
+ this.ToTempId = ToTempId;
146
+ this.Condition = Condition;
147
+ }
148
+ }
149
+ export class TaskGraphPropertiesPanelComponent {
150
+ /** The task being edited. Null shows the empty state rather than a form bound to nothing. */
151
+ set Task(value) {
152
+ this.currentTask = value;
153
+ // A working copy, so a half-typed name never reaches the spec — and so Cancel is possible
154
+ // at all. Editing the live node would make every keystroke an unvetoable mutation.
155
+ this.Draft = value ? { ...value } : null;
156
+ }
157
+ get Task() {
158
+ return this.currentTask;
159
+ }
160
+ /** The whole graph, for resolving the selected task's incoming edges. */
161
+ Spec = null;
162
+ /** Agent names offered in the assignment dropdown. Supplied by the host, which owns data access. */
163
+ AvailableAgentNames = [];
164
+ ReadOnly = false;
165
+ TaskPropertyChangeRequested = new EventEmitter();
166
+ DependencyConditionChangeRequested = new EventEmitter();
167
+ Draft = null;
168
+ currentTask = null;
169
+ /**
170
+ * Whether this step waits on a person.
171
+ *
172
+ * Derived from the absence of an agent rather than stored separately, because the spec's own
173
+ * rule is that a task has exactly one assignee. A separate boolean could disagree with
174
+ * `agentName` and the validator would then reject a graph the form said was fine.
175
+ */
176
+ get IsHumanTask() {
177
+ return !this.Draft?.agentName;
178
+ }
179
+ /** The edges into this task, so their conditions are editable where the step is. */
180
+ get IncomingEdges() {
181
+ if (!this.Draft || !this.Spec)
182
+ return [];
183
+ const nameOf = (id) => this.Spec.tasks.find((t) => t.tempId === id)?.name ?? id;
184
+ return GetDependencies(this.Draft).map((d) => ({
185
+ FromTempId: d.tempId,
186
+ FromName: nameOf(d.tempId),
187
+ Condition: d.condition ?? '',
188
+ }));
189
+ }
190
+ /** Switches the step between an agent and a person, keeping the spec's xor rule intact. */
191
+ SetAssignment(kind, agentName) {
192
+ if (!this.Draft || this.ReadOnly)
193
+ return;
194
+ this.Draft = kind === 'human'
195
+ ? { ...this.Draft, agentName: undefined, assignToUser: true }
196
+ : { ...this.Draft, agentName: agentName ?? this.AvailableAgentNames[0], assignToUser: undefined };
197
+ this.Commit();
198
+ }
199
+ /** Asks the parent to apply the draft. */
200
+ Commit() {
201
+ if (!this.Draft || !this.currentTask || this.ReadOnly)
202
+ return;
203
+ this.TaskPropertyChangeRequested.emit(new TaskPropertyChangeRequestedEventArgs(this.currentTask.tempId, { ...this.Draft }));
204
+ }
205
+ /** Asks the parent to change (or, with an empty string, clear) an edge's condition. */
206
+ CommitCondition(fromTempId, condition) {
207
+ if (!this.Draft || this.ReadOnly)
208
+ return;
209
+ this.DependencyConditionChangeRequested.emit(new DependencyConditionChangeRequestedEventArgs(fromTempId, this.Draft.tempId, condition));
210
+ }
211
+ static ɵfac = function TaskGraphPropertiesPanelComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || TaskGraphPropertiesPanelComponent)(); };
212
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TaskGraphPropertiesPanelComponent, selectors: [["mj-task-graph-properties"]], inputs: { Task: "Task", Spec: "Spec", AvailableAgentNames: "AvailableAgentNames", ReadOnly: "ReadOnly" }, outputs: { TaskPropertyChangeRequested: "TaskPropertyChangeRequested", DependencyConditionChangeRequested: "DependencyConditionChangeRequested" }, standalone: false, decls: 3, vars: 1, consts: [[1, "mj-tgp"], ["Icon", "fa-hand-pointer", "Title", "No step selected", "Message", "Pick a step on the canvas to edit what it does."], ["for", "tgp-name", 1, "mj-tgp__label"], ["id", "tgp-name", "type", "text", 1, "mj-input", 3, "ngModelChange", "blur", "ngModel", "disabled"], ["for", "tgp-desc", 1, "mj-tgp__label"], ["id", "tgp-desc", "rows", "3", 1, "mj-textarea", 3, "ngModelChange", "blur", "ngModel", "disabled"], [1, "mj-tgp__label"], [1, "mj-tgp__assignment"], ["mjButton", "", "size", "sm", 3, "click", "variant", "disabled"], [1, "fa-solid", "fa-robot"], [1, "fa-solid", "fa-user-check"], [1, "mj-input", 3, "ngModel", "disabled"], ["Variant", "info", "Size", "sm", "Message", "This step waits for you. Assigning it to someone else isn't available yet."], [1, "mj-input", 3, "ngModelChange", "change", "ngModel", "disabled"], [3, "value"], [1, "mj-tgp__edge"], [1, "mj-tgp__edge-name"], [1, "fa-solid", "fa-arrow-right-long"], ["type", "text", "placeholder", "Always \u2014 or a condition, e.g. output.severity > 3", 1, "mj-input", "mj-tgp__edge-condition", 3, "blur", "ngModel", "disabled"]], template: function TaskGraphPropertiesPanelComponent_Template(rf, ctx) { if (rf & 1) {
213
+ i0.ɵɵelementStart(0, "div", 0);
214
+ i0.ɵɵconditionalCreate(1, TaskGraphPropertiesPanelComponent_Conditional_1_Template, 1, 0, "mj-empty-state", 1)(2, TaskGraphPropertiesPanelComponent_Conditional_2_Template, 18, 10);
215
+ i0.ɵɵelementEnd();
216
+ } if (rf & 2) {
217
+ i0.ɵɵadvance();
218
+ i0.ɵɵconditional(!ctx.Draft ? 1 : 2);
219
+ } }, dependencies: [i1.NgSelectOption, i1.ɵNgSelectMultipleOption, i1.DefaultValueAccessor, i1.SelectControlValueAccessor, i1.NgControlStatus, i1.NgModel, i2.MJEmptyStateComponent, i2.MJAlertComponent, i2.MJButtonDirective], styles: ["\n\n.mj-tgp[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: var(--mj-space-sm, 0.5rem);\n padding: var(--mj-space-md, 1rem);\n overflow-y: auto;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n}\n\n.mj-tgp__label[_ngcontent-%COMP%] {\n font-size: var(--mj-font-size-sm, 0.8125rem);\n font-weight: 600;\n color: var(--mj-text-secondary);\n margin-top: var(--mj-space-xs, 0.25rem);\n}\n\n.mj-tgp__assignment[_ngcontent-%COMP%] {\n display: flex;\n gap: var(--mj-space-xs, 0.25rem);\n}\n\n.mj-tgp__edge[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: var(--mj-space-2xs, 0.125rem);\n padding: var(--mj-space-xs, 0.25rem) 0;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n.mj-tgp__edge-name[_ngcontent-%COMP%] {\n font-size: var(--mj-font-size-sm, 0.8125rem);\n color: var(--mj-text-secondary);\n}\n\n.mj-tgp__edge-condition[_ngcontent-%COMP%] {\n width: 100%;\n}"] });
220
+ }
221
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TaskGraphPropertiesPanelComponent, [{
222
+ type: Component,
223
+ args: [{ standalone: false, selector: 'mj-task-graph-properties', template: "<div class=\"mj-tgp\">\n @if (!Draft) {\n <mj-empty-state\n Icon=\"fa-hand-pointer\"\n Title=\"No step selected\"\n Message=\"Pick a step on the canvas to edit what it does.\">\n </mj-empty-state>\n } @else {\n <label class=\"mj-tgp__label\" for=\"tgp-name\">Name</label>\n <input id=\"tgp-name\" class=\"mj-input\" type=\"text\"\n [(ngModel)]=\"Draft.name\" [disabled]=\"ReadOnly\" (blur)=\"Commit()\" />\n\n <label class=\"mj-tgp__label\" for=\"tgp-desc\">What this step does</label>\n <textarea id=\"tgp-desc\" class=\"mj-textarea\" rows=\"3\"\n [(ngModel)]=\"Draft.description\" [disabled]=\"ReadOnly\" (blur)=\"Commit()\"></textarea>\n\n <label class=\"mj-tgp__label\">Who does it</label>\n <div class=\"mj-tgp__assignment\">\n <button mjButton [variant]=\"!IsHumanTask ? 'primary' : 'outline'\" size=\"sm\"\n [disabled]=\"ReadOnly\" (click)=\"SetAssignment('agent')\">\n <i class=\"fa-solid fa-robot\"></i> An agent\n </button>\n <button mjButton [variant]=\"IsHumanTask ? 'primary' : 'outline'\" size=\"sm\"\n [disabled]=\"ReadOnly\" (click)=\"SetAssignment('human')\">\n <i class=\"fa-solid fa-user-check\"></i> A person\n </button>\n </div>\n\n @if (!IsHumanTask) {\n <select class=\"mj-input\" [(ngModel)]=\"Draft.agentName\" [disabled]=\"ReadOnly\" (change)=\"Commit()\">\n @for (name of AvailableAgentNames; track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n } @else {\n <!-- Cross-user assignment is rejected at submission until the authorization model lands\n (#3524), so the panel states the rule rather than offering a picker that would fail. -->\n <mj-alert Variant=\"info\" Size=\"sm\"\n Message=\"This step waits for you. Assigning it to someone else isn't available yet.\">\n </mj-alert>\n }\n\n @if (IncomingEdges.length > 0) {\n <label class=\"mj-tgp__label\">Runs after</label>\n @for (edge of IncomingEdges; track edge.FromTempId) {\n <div class=\"mj-tgp__edge\">\n <span class=\"mj-tgp__edge-name\"><i class=\"fa-solid fa-arrow-right-long\"></i> {{ edge.FromName }}</span>\n <!-- Leaving this empty makes the edge unconditional, which is how a fallback is written. -->\n <input class=\"mj-input mj-tgp__edge-condition\" type=\"text\"\n placeholder=\"Always \u2014 or a condition, e.g. output.severity > 3\"\n [ngModel]=\"edge.Condition\" [disabled]=\"ReadOnly\"\n (blur)=\"CommitCondition(edge.FromTempId, $any($event.target).value)\" />\n </div>\n }\n }\n }\n</div>\n", styles: ["/* Design tokens only \u2014 no hardcoded colors. */\n.mj-tgp {\n display: flex;\n flex-direction: column;\n gap: var(--mj-space-sm, 0.5rem);\n padding: var(--mj-space-md, 1rem);\n overflow-y: auto;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n}\n\n.mj-tgp__label {\n font-size: var(--mj-font-size-sm, 0.8125rem);\n font-weight: 600;\n color: var(--mj-text-secondary);\n margin-top: var(--mj-space-xs, 0.25rem);\n}\n\n.mj-tgp__assignment {\n display: flex;\n gap: var(--mj-space-xs, 0.25rem);\n}\n\n.mj-tgp__edge {\n display: flex;\n flex-direction: column;\n gap: var(--mj-space-2xs, 0.125rem);\n padding: var(--mj-space-xs, 0.25rem) 0;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n.mj-tgp__edge-name {\n font-size: var(--mj-font-size-sm, 0.8125rem);\n color: var(--mj-text-secondary);\n}\n\n.mj-tgp__edge-condition {\n width: 100%;\n}\n"] }]
224
+ }], null, { Task: [{
225
+ type: Input
226
+ }], Spec: [{
227
+ type: Input
228
+ }], AvailableAgentNames: [{
229
+ type: Input
230
+ }], ReadOnly: [{
231
+ type: Input
232
+ }], TaskPropertyChangeRequested: [{
233
+ type: Output
234
+ }], DependencyConditionChangeRequested: [{
235
+ type: Output
236
+ }] }); })();
237
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TaskGraphPropertiesPanelComponent, { className: "TaskGraphPropertiesPanelComponent", filePath: "src/lib/task-graph-properties-panel.component.ts", lineNumber: 44 }); })();
238
+ //# sourceMappingURL=task-graph-properties-panel.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-graph-properties-panel.component.js","sourceRoot":"","sources":["../../src/lib/task-graph-properties-panel.component.ts","../../src/lib/task-graph-properties-panel.component.html"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Edits the selected task's properties, plus the graph-level traversal settings.\n *\n * Split out of the canvas component for the reason panels usually are — the canvas is about\n * *structure* (what connects to what) and this is about *content* (what a step actually does). But\n * also for a sharper reason: a host embedding the read-only viewer in a chat card or a run history\n * pane wants the graph without a form beside it, and a panel welded into the canvas cannot be\n * declined.\n *\n * Emits intent rather than mutating: the canvas component owns the spec, so every edit here leaves\n * as a request the parent applies through the same `Before*`/`After*` path a drag or a delete takes.\n * Two write paths into one spec would be two places for the veto contract to be wrong.\n *\n * @module @memberjunction/ng-task-graph-editor\n */\nimport { Component, EventEmitter, Input, Output } from '@angular/core';\nimport type { TaskGraphSpec, TaskGraphSpecNode } from '@memberjunction/ai-core-plus';\nimport { GetDependencies } from './task-graph-canvas-adapter';\n\n/** A requested change to one task. The parent applies it; this panel never writes. */\nexport class TaskPropertyChangeRequestedEventArgs {\n constructor(\n public readonly TempId: string,\n public readonly Next: TaskGraphSpecNode,\n ) {}\n}\n\n/** A requested change to a dependency edge's condition. */\nexport class DependencyConditionChangeRequestedEventArgs {\n constructor(\n public readonly FromTempId: string,\n public readonly ToTempId: string,\n /** Empty string clears the condition, making the edge unconditional again. */\n public readonly Condition: string,\n ) {}\n}\n\n@Component({\n standalone: false,\n selector: 'mj-task-graph-properties',\n templateUrl: './task-graph-properties-panel.component.html',\n styleUrls: ['./task-graph-properties-panel.component.css'],\n})\nexport class TaskGraphPropertiesPanelComponent {\n /** The task being edited. Null shows the empty state rather than a form bound to nothing. */\n @Input()\n public set Task(value: TaskGraphSpecNode | null) {\n this.currentTask = value;\n // A working copy, so a half-typed name never reaches the spec — and so Cancel is possible\n // at all. Editing the live node would make every keystroke an unvetoable mutation.\n this.Draft = value ? { ...value } : null;\n }\n public get Task(): TaskGraphSpecNode | null {\n return this.currentTask;\n }\n\n /** The whole graph, for resolving the selected task's incoming edges. */\n @Input() public Spec: TaskGraphSpec | null = null;\n\n /** Agent names offered in the assignment dropdown. Supplied by the host, which owns data access. */\n @Input() public AvailableAgentNames: readonly string[] = [];\n\n @Input() public ReadOnly: boolean = false;\n\n @Output() public TaskPropertyChangeRequested = new EventEmitter<TaskPropertyChangeRequestedEventArgs>();\n @Output() public DependencyConditionChangeRequested = new EventEmitter<DependencyConditionChangeRequestedEventArgs>();\n\n public Draft: TaskGraphSpecNode | null = null;\n\n private currentTask: TaskGraphSpecNode | null = null;\n\n /**\n * Whether this step waits on a person.\n *\n * Derived from the absence of an agent rather than stored separately, because the spec's own\n * rule is that a task has exactly one assignee. A separate boolean could disagree with\n * `agentName` and the validator would then reject a graph the form said was fine.\n */\n public get IsHumanTask(): boolean {\n return !this.Draft?.agentName;\n }\n\n /** The edges into this task, so their conditions are editable where the step is. */\n public get IncomingEdges(): Array<{ FromTempId: string; FromName: string; Condition: string }> {\n if (!this.Draft || !this.Spec) return [];\n const nameOf = (id: string) => this.Spec!.tasks.find((t) => t.tempId === id)?.name ?? id;\n return GetDependencies(this.Draft).map((d) => ({\n FromTempId: d.tempId,\n FromName: nameOf(d.tempId),\n Condition: d.condition ?? '',\n }));\n }\n\n /** Switches the step between an agent and a person, keeping the spec's xor rule intact. */\n public SetAssignment(kind: 'agent' | 'human', agentName?: string): void {\n if (!this.Draft || this.ReadOnly) return;\n this.Draft = kind === 'human'\n ? { ...this.Draft, agentName: undefined, assignToUser: true }\n : { ...this.Draft, agentName: agentName ?? this.AvailableAgentNames[0], assignToUser: undefined };\n this.Commit();\n }\n\n /** Asks the parent to apply the draft. */\n public Commit(): void {\n if (!this.Draft || !this.currentTask || this.ReadOnly) return;\n this.TaskPropertyChangeRequested.emit(\n new TaskPropertyChangeRequestedEventArgs(this.currentTask.tempId, { ...this.Draft }),\n );\n }\n\n /** Asks the parent to change (or, with an empty string, clear) an edge's condition. */\n public CommitCondition(fromTempId: string, condition: string): void {\n if (!this.Draft || this.ReadOnly) return;\n this.DependencyConditionChangeRequested.emit(\n new DependencyConditionChangeRequestedEventArgs(fromTempId, this.Draft.tempId, condition),\n );\n }\n}\n","<div class=\"mj-tgp\">\n @if (!Draft) {\n <mj-empty-state\n Icon=\"fa-hand-pointer\"\n Title=\"No step selected\"\n Message=\"Pick a step on the canvas to edit what it does.\">\n </mj-empty-state>\n } @else {\n <label class=\"mj-tgp__label\" for=\"tgp-name\">Name</label>\n <input id=\"tgp-name\" class=\"mj-input\" type=\"text\"\n [(ngModel)]=\"Draft.name\" [disabled]=\"ReadOnly\" (blur)=\"Commit()\" />\n\n <label class=\"mj-tgp__label\" for=\"tgp-desc\">What this step does</label>\n <textarea id=\"tgp-desc\" class=\"mj-textarea\" rows=\"3\"\n [(ngModel)]=\"Draft.description\" [disabled]=\"ReadOnly\" (blur)=\"Commit()\"></textarea>\n\n <label class=\"mj-tgp__label\">Who does it</label>\n <div class=\"mj-tgp__assignment\">\n <button mjButton [variant]=\"!IsHumanTask ? 'primary' : 'outline'\" size=\"sm\"\n [disabled]=\"ReadOnly\" (click)=\"SetAssignment('agent')\">\n <i class=\"fa-solid fa-robot\"></i> An agent\n </button>\n <button mjButton [variant]=\"IsHumanTask ? 'primary' : 'outline'\" size=\"sm\"\n [disabled]=\"ReadOnly\" (click)=\"SetAssignment('human')\">\n <i class=\"fa-solid fa-user-check\"></i> A person\n </button>\n </div>\n\n @if (!IsHumanTask) {\n <select class=\"mj-input\" [(ngModel)]=\"Draft.agentName\" [disabled]=\"ReadOnly\" (change)=\"Commit()\">\n @for (name of AvailableAgentNames; track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n } @else {\n <!-- Cross-user assignment is rejected at submission until the authorization model lands\n (#3524), so the panel states the rule rather than offering a picker that would fail. -->\n <mj-alert Variant=\"info\" Size=\"sm\"\n Message=\"This step waits for you. Assigning it to someone else isn't available yet.\">\n </mj-alert>\n }\n\n @if (IncomingEdges.length > 0) {\n <label class=\"mj-tgp__label\">Runs after</label>\n @for (edge of IncomingEdges; track edge.FromTempId) {\n <div class=\"mj-tgp__edge\">\n <span class=\"mj-tgp__edge-name\"><i class=\"fa-solid fa-arrow-right-long\"></i> {{ edge.FromName }}</span>\n <!-- Leaving this empty makes the edge unconditional, which is how a fallback is written. -->\n <input class=\"mj-input mj-tgp__edge-condition\" type=\"text\"\n placeholder=\"Always — or a condition, e.g. output.severity > 3\"\n [ngModel]=\"edge.Condition\" [disabled]=\"ReadOnly\"\n (blur)=\"CommitCondition(edge.FromTempId, $any($event.target).value)\" />\n </div>\n }\n }\n }\n</div>\n"]}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @fileoverview Turning live `MJ: Tasks` rows into the canvas's runtime overlay.
3
+ *
4
+ * **Why this is a pure mapper and not a subscription.** A `widgets`-layer component must not own a
5
+ * data subscription: it cannot know which provider it is on, whose rows it may read, or when the
6
+ * host wants to start and stop watching. The host owns the subscription — it already has the
7
+ * `BaseEntity` event stream or a `RunView` — and passes the rows here to be shaped for the canvas.
8
+ *
9
+ * That split is also what makes the runtime overlay work for **both** provenances. A durable graph
10
+ * is watched through `MJ: Tasks` rows; an in-run flow through `AIAgentRunStep` rows. Different
11
+ * sources, same shape by the time they reach the canvas, so one renderer serves both — which is the
12
+ * whole point of the convergence.
13
+ *
14
+ * @module @memberjunction/ng-task-graph-editor
15
+ */
16
+ import type { TaskGraphRuntimeState, TaskGraphRuntimeStatus } from './task-graph-canvas-adapter';
17
+ /** The minimum a row must expose to drive the overlay. Structural, so any source can satisfy it. */
18
+ export type TaskGraphRuntimeRow = {
19
+ /** Correlates the row back to a spec node. See {@link BuildRuntimeStatus} for how. */
20
+ ID: string;
21
+ Name: string;
22
+ Status: string;
23
+ };
24
+ /**
25
+ * Shapes live rows into the canvas's `RuntimeStatus` map.
26
+ *
27
+ * **Correlation is by name, with an ID fallback, and that ordering is deliberate.** A submitted
28
+ * graph's Task rows carry database IDs, while the spec on the canvas carries producer-assigned
29
+ * `tempId`s — the two never match, because the producer could not know real IDs at authoring time.
30
+ * The task's *name* is the only value that survives submission unchanged, so it is the join key. An
31
+ * ID match is still honored first for the case where a host is showing a graph whose `tempId`s
32
+ * genuinely are row IDs — which is exactly what `ConvertAgentSpecToTaskGraph` produces.
33
+ *
34
+ * Rows that match nothing are skipped rather than guessed at: a wrong node lighting up green is
35
+ * worse than a node that stays grey, because the first is believed.
36
+ */
37
+ export declare function BuildRuntimeStatus(rows: readonly TaskGraphRuntimeRow[], specTempIdsByName: ReadonlyMap<string, string>, knownTempIds: ReadonlySet<string>): TaskGraphRuntimeStatus;
38
+ /**
39
+ * Coerces a row's status string into the canvas vocabulary.
40
+ *
41
+ * Unknown values fall back to `Pending` rather than throwing. A status the UI has not heard of is a
42
+ * schema that moved ahead of the client — which must degrade to "we don't know yet", not to a blank
43
+ * canvas or an exception in a render path.
44
+ */
45
+ export declare function NormalizeRuntimeState(status: string | null | undefined): TaskGraphRuntimeState;
46
+ /** Index of task name → `tempId`, for the name-based correlation above. */
47
+ export declare function BuildNameIndex(tasks: readonly {
48
+ tempId: string;
49
+ name: string;
50
+ }[]): Map<string, string>;
51
+ /** True once every task has reached a state nothing will move it out of. */
52
+ export declare function IsRuntimeSettled(status: TaskGraphRuntimeStatus, tempIds: readonly string[]): boolean;
53
+ /** A one-line progress summary, for hosts that show one beside the canvas. */
54
+ export declare function SummarizeRuntime(status: TaskGraphRuntimeStatus, tempIds: readonly string[]): string;
55
+ //# sourceMappingURL=task-graph-runtime-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-graph-runtime-source.d.ts","sourceRoot":"","sources":["../../src/lib/task-graph-runtime-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAEjG,oGAAoG;AACpG,MAAM,MAAM,mBAAmB,GAAG;IAC9B,sFAAsF;IACtF,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAC9B,IAAI,EAAE,SAAS,mBAAmB,EAAE,EACpC,iBAAiB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9C,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,GAClC,sBAAsB,CASxB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,qBAAqB,CAa9F;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAItG;AAED,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAGpG;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAQnG"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Shapes live rows into the canvas's `RuntimeStatus` map.
3
+ *
4
+ * **Correlation is by name, with an ID fallback, and that ordering is deliberate.** A submitted
5
+ * graph's Task rows carry database IDs, while the spec on the canvas carries producer-assigned
6
+ * `tempId`s — the two never match, because the producer could not know real IDs at authoring time.
7
+ * The task's *name* is the only value that survives submission unchanged, so it is the join key. An
8
+ * ID match is still honored first for the case where a host is showing a graph whose `tempId`s
9
+ * genuinely are row IDs — which is exactly what `ConvertAgentSpecToTaskGraph` produces.
10
+ *
11
+ * Rows that match nothing are skipped rather than guessed at: a wrong node lighting up green is
12
+ * worse than a node that stays grey, because the first is believed.
13
+ */
14
+ export function BuildRuntimeStatus(rows, specTempIdsByName, knownTempIds) {
15
+ const status = {};
16
+ for (const row of rows) {
17
+ const tempId = knownTempIds.has(row.ID) ? row.ID : specTempIdsByName.get(row.Name);
18
+ if (!tempId)
19
+ continue;
20
+ status[tempId] = NormalizeRuntimeState(row.Status);
21
+ }
22
+ return status;
23
+ }
24
+ /**
25
+ * Coerces a row's status string into the canvas vocabulary.
26
+ *
27
+ * Unknown values fall back to `Pending` rather than throwing. A status the UI has not heard of is a
28
+ * schema that moved ahead of the client — which must degrade to "we don't know yet", not to a blank
29
+ * canvas or an exception in a render path.
30
+ */
31
+ export function NormalizeRuntimeState(status) {
32
+ switch (status) {
33
+ case 'In Progress':
34
+ case 'Complete':
35
+ case 'Failed':
36
+ case 'Blocked':
37
+ case 'Cancelled':
38
+ case 'Deferred':
39
+ case 'Pending':
40
+ return status;
41
+ default:
42
+ return 'Pending';
43
+ }
44
+ }
45
+ /** Index of task name → `tempId`, for the name-based correlation above. */
46
+ export function BuildNameIndex(tasks) {
47
+ // Last writer wins on a duplicate name. Names are not unique in a spec — only `tempId` is — so
48
+ // a duplicated name is inherently ambiguous; picking one deterministically beats dropping both.
49
+ return new Map(tasks.map((t) => [t.name, t.tempId]));
50
+ }
51
+ /** True once every task has reached a state nothing will move it out of. */
52
+ export function IsRuntimeSettled(status, tempIds) {
53
+ const terminal = new Set(['Complete', 'Failed', 'Cancelled']);
54
+ return tempIds.length > 0 && tempIds.every((id) => terminal.has(status[id] ?? 'Pending'));
55
+ }
56
+ /** A one-line progress summary, for hosts that show one beside the canvas. */
57
+ export function SummarizeRuntime(status, tempIds) {
58
+ const counts = new Map();
59
+ for (const id of tempIds) {
60
+ const state = status[id] ?? 'Pending';
61
+ counts.set(state, (counts.get(state) ?? 0) + 1);
62
+ }
63
+ const parts = [...counts.entries()].map(([state, n]) => `${n} ${state.toLowerCase()}`);
64
+ return `${tempIds.length} step${tempIds.length === 1 ? '' : 's'} — ${parts.join(', ')}`;
65
+ }
66
+ //# sourceMappingURL=task-graph-runtime-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-graph-runtime-source.js","sourceRoot":"","sources":["../../src/lib/task-graph-runtime-source.ts"],"names":[],"mappings":"AAyBA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAC9B,IAAoC,EACpC,iBAA8C,EAC9C,YAAiC;IAEjC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAE1C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnF,IAAI,CAAC,MAAM;YAAE,SAAS;QACtB,MAAM,CAAC,MAAM,CAAC,GAAG,qBAAqB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAiC;IACnE,QAAQ,MAAM,EAAE,CAAC;QACb,KAAK,aAAa,CAAC;QACnB,KAAK,UAAU,CAAC;QAChB,KAAK,QAAQ,CAAC;QACd,KAAK,SAAS,CAAC;QACf,KAAK,WAAW,CAAC;QACjB,KAAK,UAAU,CAAC;QAChB,KAAK,SAAS;YACV,OAAO,MAAM,CAAC;QAClB;YACI,OAAO,SAAS,CAAC;IACzB,CAAC;AACL,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,KAAkD;IAC7E,+FAA+F;IAC/F,gGAAgG;IAChG,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,gBAAgB,CAAC,MAA8B,EAAE,OAA0B;IACvF,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAwB,CAAC,UAAU,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;IACrF,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC;AAC9F,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,gBAAgB,CAAC,MAA8B,EAAE,OAA0B;IACvF,MAAM,MAAM,GAAG,IAAI,GAAG,EAAiC,CAAC;IACxD,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,IAAI,SAAS,CAAC;QACtC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACvF,OAAO,GAAG,OAAO,CAAC,MAAM,QAAQ,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAC5F,CAAC","sourcesContent":["/**\n * @fileoverview Turning live `MJ: Tasks` rows into the canvas's runtime overlay.\n *\n * **Why this is a pure mapper and not a subscription.** A `widgets`-layer component must not own a\n * data subscription: it cannot know which provider it is on, whose rows it may read, or when the\n * host wants to start and stop watching. The host owns the subscription — it already has the\n * `BaseEntity` event stream or a `RunView` — and passes the rows here to be shaped for the canvas.\n *\n * That split is also what makes the runtime overlay work for **both** provenances. A durable graph\n * is watched through `MJ: Tasks` rows; an in-run flow through `AIAgentRunStep` rows. Different\n * sources, same shape by the time they reach the canvas, so one renderer serves both — which is the\n * whole point of the convergence.\n *\n * @module @memberjunction/ng-task-graph-editor\n */\nimport type { TaskGraphRuntimeState, TaskGraphRuntimeStatus } from './task-graph-canvas-adapter';\n\n/** The minimum a row must expose to drive the overlay. Structural, so any source can satisfy it. */\nexport type TaskGraphRuntimeRow = {\n /** Correlates the row back to a spec node. See {@link BuildRuntimeStatus} for how. */\n ID: string;\n Name: string;\n Status: string;\n};\n\n/**\n * Shapes live rows into the canvas's `RuntimeStatus` map.\n *\n * **Correlation is by name, with an ID fallback, and that ordering is deliberate.** A submitted\n * graph's Task rows carry database IDs, while the spec on the canvas carries producer-assigned\n * `tempId`s — the two never match, because the producer could not know real IDs at authoring time.\n * The task's *name* is the only value that survives submission unchanged, so it is the join key. An\n * ID match is still honored first for the case where a host is showing a graph whose `tempId`s\n * genuinely are row IDs — which is exactly what `ConvertAgentSpecToTaskGraph` produces.\n *\n * Rows that match nothing are skipped rather than guessed at: a wrong node lighting up green is\n * worse than a node that stays grey, because the first is believed.\n */\nexport function BuildRuntimeStatus(\n rows: readonly TaskGraphRuntimeRow[],\n specTempIdsByName: ReadonlyMap<string, string>,\n knownTempIds: ReadonlySet<string>,\n): TaskGraphRuntimeStatus {\n const status: TaskGraphRuntimeStatus = {};\n\n for (const row of rows) {\n const tempId = knownTempIds.has(row.ID) ? row.ID : specTempIdsByName.get(row.Name);\n if (!tempId) continue;\n status[tempId] = NormalizeRuntimeState(row.Status);\n }\n return status;\n}\n\n/**\n * Coerces a row's status string into the canvas vocabulary.\n *\n * Unknown values fall back to `Pending` rather than throwing. A status the UI has not heard of is a\n * schema that moved ahead of the client — which must degrade to \"we don't know yet\", not to a blank\n * canvas or an exception in a render path.\n */\nexport function NormalizeRuntimeState(status: string | null | undefined): TaskGraphRuntimeState {\n switch (status) {\n case 'In Progress':\n case 'Complete':\n case 'Failed':\n case 'Blocked':\n case 'Cancelled':\n case 'Deferred':\n case 'Pending':\n return status;\n default:\n return 'Pending';\n }\n}\n\n/** Index of task name → `tempId`, for the name-based correlation above. */\nexport function BuildNameIndex(tasks: readonly { tempId: string; name: string }[]): Map<string, string> {\n // Last writer wins on a duplicate name. Names are not unique in a spec — only `tempId` is — so\n // a duplicated name is inherently ambiguous; picking one deterministically beats dropping both.\n return new Map(tasks.map((t) => [t.name, t.tempId]));\n}\n\n/** True once every task has reached a state nothing will move it out of. */\nexport function IsRuntimeSettled(status: TaskGraphRuntimeStatus, tempIds: readonly string[]): boolean {\n const terminal = new Set<TaskGraphRuntimeState>(['Complete', 'Failed', 'Cancelled']);\n return tempIds.length > 0 && tempIds.every((id) => terminal.has(status[id] ?? 'Pending'));\n}\n\n/** A one-line progress summary, for hosts that show one beside the canvas. */\nexport function SummarizeRuntime(status: TaskGraphRuntimeStatus, tempIds: readonly string[]): string {\n const counts = new Map<TaskGraphRuntimeState, number>();\n for (const id of tempIds) {\n const state = status[id] ?? 'Pending';\n counts.set(state, (counts.get(state) ?? 0) + 1);\n }\n const parts = [...counts.entries()].map(([state, n]) => `${n} ${state.toLowerCase()}`);\n return `${tempIds.length} step${tempIds.length === 1 ? '' : 's'} — ${parts.join(', ')}`;\n}\n"]}
@@ -0,0 +1,7 @@
1
+ export * from './lib/task-graph-editor.module';
2
+ export * from './lib/task-graph-editor.component';
3
+ export * from './lib/task-graph-editor-events';
4
+ export * from './lib/task-graph-canvas-adapter';
5
+ export * from './lib/task-graph-properties-panel.component';
6
+ export * from './lib/task-graph-runtime-source';
7
+ //# sourceMappingURL=public-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-api.d.ts","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAQA,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mCAAmC,CAAC;AAClD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,iCAAiC,CAAC"}
@@ -0,0 +1,15 @@
1
+ /*
2
+ * Public API Surface of @memberjunction/ng-task-graph-editor
3
+ *
4
+ * A `widgets`-layer component for viewing and editing a `TaskGraphSpec` — the one graph contract
5
+ * shared by design-time flows and runtime task graphs. Import `TaskGraphSpec` itself from
6
+ * `@memberjunction/ai-core-plus`; this package deliberately does not re-export it (see the
7
+ * no-cross-package-re-exports rule in `.claude/rules/typescript-style.md`).
8
+ */
9
+ export * from './lib/task-graph-editor.module';
10
+ export * from './lib/task-graph-editor.component';
11
+ export * from './lib/task-graph-editor-events';
12
+ export * from './lib/task-graph-canvas-adapter';
13
+ export * from './lib/task-graph-properties-panel.component';
14
+ export * from './lib/task-graph-runtime-source';
15
+ //# sourceMappingURL=public-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-api.js","sourceRoot":"","sources":["../src/public-api.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mCAAmC,CAAC;AAClD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,iCAAiC,CAAC","sourcesContent":["/*\n * Public API Surface of @memberjunction/ng-task-graph-editor\n *\n * A `widgets`-layer component for viewing and editing a `TaskGraphSpec` — the one graph contract\n * shared by design-time flows and runtime task graphs. Import `TaskGraphSpec` itself from\n * `@memberjunction/ai-core-plus`; this package deliberately does not re-export it (see the\n * no-cross-package-re-exports rule in `.claude/rules/typescript-style.md`).\n */\nexport * from './lib/task-graph-editor.module';\nexport * from './lib/task-graph-editor.component';\nexport * from './lib/task-graph-editor-events';\nexport * from './lib/task-graph-canvas-adapter';\nexport * from './lib/task-graph-properties-panel.component';\nexport * from './lib/task-graph-runtime-source';\n"]}
package/package.json CHANGED
@@ -1,10 +1,55 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-task-graph-editor",
3
- "version": "0.0.0",
4
- "description": "OIDC trusted publishing setup package for @memberjunction/ng-task-graph-editor",
3
+ "version": "6.1.0-edge.1",
4
+ "description": "MemberJunction: View and edit a TaskGraphSpec on a canvas — the one graph contract shared by design-time flows and runtime task graphs",
5
+ "main": "./dist/public-api.js",
6
+ "typings": "./dist/public-api.d.ts",
7
+ "files": [
8
+ "/dist"
9
+ ],
5
10
  "keywords": [
6
- "oidc",
7
- "trusted-publishing",
8
- "setup"
9
- ]
10
- }
11
+ "angular",
12
+ "task-graph",
13
+ "workflow",
14
+ "dag",
15
+ "graph-editor",
16
+ "memberjunction"
17
+ ],
18
+ "author": "",
19
+ "license": "ISC",
20
+ "devDependencies": {
21
+ "@angular/compiler": "21.1.3",
22
+ "@angular/compiler-cli": "21.1.3",
23
+ "@memberjunction/ng-test-utils": "6.1.0-edge.1",
24
+ "sass": "^1.97.3",
25
+ "vitest": "^4.0.18"
26
+ },
27
+ "peerDependencies": {
28
+ "@angular/common": "^21.1.3",
29
+ "@angular/core": "^21.1.3",
30
+ "@angular/forms": "^21.1.3",
31
+ "@angular/platform-browser": "^21.1.3"
32
+ },
33
+ "dependencies": {
34
+ "@memberjunction/ai-core-plus": "6.1.0-edge.1",
35
+ "@memberjunction/core": "6.1.0-edge.1",
36
+ "@memberjunction/global": "6.1.0-edge.1",
37
+ "@memberjunction/ng-base-types": "6.1.0-edge.1",
38
+ "@memberjunction/ng-flow-editor": "6.1.0-edge.1",
39
+ "@memberjunction/ng-ui-components": "6.1.0-edge.1",
40
+ "rxjs": "^7.8.2",
41
+ "tslib": "^2.8.1"
42
+ },
43
+ "sideEffects": false,
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/MemberJunction/MJ"
47
+ },
48
+ "mjUILayer": "widgets",
49
+ "scripts": {
50
+ "test": "vitest run",
51
+ "test:types": "tsc --noEmit -p tsconfig.spec.json",
52
+ "build": "ngc",
53
+ "test:watch": "vitest"
54
+ }
55
+ }
package/README.md DELETED
@@ -1,45 +0,0 @@
1
- # @memberjunction/ng-task-graph-editor
2
-
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
4
-
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
6
-
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
8
-
9
- ## Purpose
10
-
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@memberjunction/ng-task-graph-editor`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
15
-
16
- ## What is OIDC Trusted Publishing?
17
-
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
19
-
20
- ## Setup Instructions
21
-
22
- To properly configure OIDC trusted publishing for this package:
23
-
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
28
-
29
- ## DO NOT USE THIS PACKAGE
30
-
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
36
-
37
- ## More Information
38
-
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
42
-
43
- ---
44
-
45
- **Maintained for OIDC setup purposes only**