@echothink-ui/task 0.1.0

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 (64) hide show
  1. package/README.md +5 -0
  2. package/dist/components/BackendThinkingChain.d.ts +2 -0
  3. package/dist/components/BlockingReasonPanel.d.ts +2 -0
  4. package/dist/components/DAGEdge.d.ts +2 -0
  5. package/dist/components/DAGLegend.d.ts +2 -0
  6. package/dist/components/DAGNode.d.ts +4 -0
  7. package/dist/components/DecisionRequiredPanel.d.ts +2 -0
  8. package/dist/components/HumanInterventionPanel.d.ts +2 -0
  9. package/dist/components/MobileTaskShell.d.ts +12 -0
  10. package/dist/components/TaskApprovalPanel.d.ts +2 -0
  11. package/dist/components/TaskCard.d.ts +2 -0
  12. package/dist/components/TaskDependencyList.d.ts +2 -0
  13. package/dist/components/TaskDetailPanel.d.ts +2 -0
  14. package/dist/components/TaskHandoffPanel.d.ts +2 -0
  15. package/dist/components/TaskProgressIndicator.d.ts +2 -0
  16. package/dist/components/TaskRetryPanel.d.ts +2 -0
  17. package/dist/components/TaskRunLog.d.ts +2 -0
  18. package/dist/components/TaskStatusBadge.d.ts +2 -0
  19. package/dist/components/TaskTable.d.ts +5 -0
  20. package/dist/components/TaskTimeline.d.ts +2 -0
  21. package/dist/components/TaskWaveDAG.d.ts +2 -0
  22. package/dist/components/TaskWaveHeader.d.ts +2 -0
  23. package/dist/components/TaskWaveTable.d.ts +2 -0
  24. package/dist/components/utils.d.ts +13 -0
  25. package/dist/index.cjs +2434 -0
  26. package/dist/index.cjs.map +1 -0
  27. package/dist/index.css +2402 -0
  28. package/dist/index.css.map +1 -0
  29. package/dist/index.d.ts +27 -0
  30. package/dist/index.js +2388 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/types.d.ts +249 -0
  33. package/package.json +45 -0
  34. package/src/components/BackendThinkingChain.tsx +129 -0
  35. package/src/components/BlockingReasonPanel.tsx +67 -0
  36. package/src/components/DAGEdge.tsx +97 -0
  37. package/src/components/DAGLegend.tsx +86 -0
  38. package/src/components/DAGNode.tsx +103 -0
  39. package/src/components/DecisionRequiredPanel.tsx +166 -0
  40. package/src/components/HumanInterventionPanel.tsx +82 -0
  41. package/src/components/MobileTaskShell.tsx +52 -0
  42. package/src/components/TaskApprovalPanel.tsx +159 -0
  43. package/src/components/TaskCard.tsx +71 -0
  44. package/src/components/TaskDependencyList.test.tsx +54 -0
  45. package/src/components/TaskDependencyList.tsx +105 -0
  46. package/src/components/TaskDetailPanel.test.tsx +49 -0
  47. package/src/components/TaskDetailPanel.tsx +139 -0
  48. package/src/components/TaskHandoffPanel.tsx +125 -0
  49. package/src/components/TaskProgressIndicator.tsx +70 -0
  50. package/src/components/TaskRetryPanel.test.tsx +29 -0
  51. package/src/components/TaskRetryPanel.tsx +103 -0
  52. package/src/components/TaskRunLog.tsx +156 -0
  53. package/src/components/TaskStatusBadge.tsx +29 -0
  54. package/src/components/TaskTable.tsx +294 -0
  55. package/src/components/TaskTimeline.tsx +98 -0
  56. package/src/components/TaskWaveDAG.tsx +202 -0
  57. package/src/components/TaskWaveHeader.tsx +82 -0
  58. package/src/components/TaskWaveTable.tsx +151 -0
  59. package/src/components/css.d.ts +1 -0
  60. package/src/components/utils.ts +116 -0
  61. package/src/index.test.tsx +316 -0
  62. package/src/index.tsx +90 -0
  63. package/src/styles.css +2889 -0
  64. package/src/types.ts +289 -0
package/dist/index.js ADDED
@@ -0,0 +1,2388 @@
1
+ // src/components/BackendThinkingChain.tsx
2
+ import * as React from "react";
3
+ import clsx from "clsx";
4
+ import { Badge, EmptyState, StatusDot, Tag } from "@echothink-ui/core";
5
+
6
+ // src/components/utils.ts
7
+ import { statusLabel } from "@echothink-ui/core";
8
+ function severityForStatus(status) {
9
+ if (status === "running" || status === "queued" || status === "in-progress") return "info";
10
+ if (status === "blocked" || status === "failed") return "danger";
11
+ if (status === "pending-approval" || status === "approval-required" || status === "warning") {
12
+ return "warning";
13
+ }
14
+ if (status === "succeeded" || status === "completed" || status === "synced") return "success";
15
+ return "neutral";
16
+ }
17
+ function severityForPriority(priority) {
18
+ if (priority === "critical") return "danger";
19
+ if (priority === "high") return "warning";
20
+ if (priority === "medium") return "info";
21
+ return "neutral";
22
+ }
23
+ function severityForRisk(riskLevel) {
24
+ if (riskLevel === "critical" || riskLevel === "high") return "danger";
25
+ if (riskLevel === "medium") return "warning";
26
+ if (riskLevel === "low") return "info";
27
+ return "neutral";
28
+ }
29
+ function labelForStatus(status) {
30
+ return statusLabel(status);
31
+ }
32
+ function formatDateTime(value) {
33
+ if (!value) return "-";
34
+ const date = value instanceof Date ? value : new Date(value);
35
+ if (Number.isNaN(date.valueOf())) return String(value);
36
+ return new Intl.DateTimeFormat(void 0, {
37
+ dateStyle: "medium",
38
+ timeStyle: "short"
39
+ }).format(date);
40
+ }
41
+ function formatDuration(durationMs) {
42
+ if (durationMs == null) return "-";
43
+ if (durationMs < 1e3) return `${durationMs}ms`;
44
+ const seconds = Math.round(durationMs / 1e3);
45
+ if (seconds < 60) return `${seconds}s`;
46
+ const minutes = Math.floor(seconds / 60);
47
+ const remainder = seconds % 60;
48
+ if (minutes < 60) return remainder ? `${minutes}m ${remainder}s` : `${minutes}m`;
49
+ const hours = Math.floor(minutes / 60);
50
+ const minuteRemainder = minutes % 60;
51
+ return minuteRemainder ? `${hours}h ${minuteRemainder}m` : `${hours}h`;
52
+ }
53
+ function clampProgress(value, total = 100) {
54
+ if (value == null || Number.isNaN(value)) return 0;
55
+ if (total <= 0) return 0;
56
+ const percent = total === 100 ? value : value / total * 100;
57
+ return Math.max(0, Math.min(100, Math.round(percent)));
58
+ }
59
+ function waveProgress(wave) {
60
+ if (!wave?.totalTasks) return 0;
61
+ const completed = (wave.statuses.completed ?? 0) + (wave.statuses.succeeded ?? 0);
62
+ return clampProgress(completed, wave.totalTasks);
63
+ }
64
+ function statusColor(status) {
65
+ switch (status) {
66
+ case "completed":
67
+ case "succeeded":
68
+ case "synced":
69
+ case "active":
70
+ return "var(--eth-color-success)";
71
+ case "running":
72
+ case "in-progress":
73
+ case "queued":
74
+ return "var(--eth-color-info)";
75
+ case "blocked":
76
+ case "failed":
77
+ case "approval-required":
78
+ return "var(--eth-color-danger)";
79
+ case "pending-approval":
80
+ case "warning":
81
+ return "var(--eth-color-warning)";
82
+ default:
83
+ return "var(--eth-color-border-strong)";
84
+ }
85
+ }
86
+
87
+ // src/components/BackendThinkingChain.tsx
88
+ import { jsx, jsxs } from "react/jsx-runtime";
89
+ var STREAMING_STATUSES = /* @__PURE__ */ new Set(["running", "in-progress"]);
90
+ function flattenSteps(steps) {
91
+ return steps.flatMap((step) => [step, ...flattenSteps(step.children ?? [])]);
92
+ }
93
+ function currentStreamingStepId(steps) {
94
+ const flattened = flattenSteps(steps);
95
+ for (let index = flattened.length - 1; index >= 0; index -= 1) {
96
+ if (STREAMING_STATUSES.has(flattened[index].status)) {
97
+ return flattened[index].id;
98
+ }
99
+ }
100
+ return flattened.at(-1)?.id;
101
+ }
102
+ function ThinkingStepItem({
103
+ step,
104
+ streamingStepId
105
+ }) {
106
+ const isStreaming = streamingStepId === step.id;
107
+ const hasChildren = Boolean(step.children?.length);
108
+ const [expanded, setExpanded] = React.useState(true);
109
+ return /* @__PURE__ */ jsxs(
110
+ "li",
111
+ {
112
+ className: clsx(
113
+ "eth-task-thinking-chain__item",
114
+ `eth-task-thinking-chain__item--${step.status}`,
115
+ step.redacted && "eth-task-thinking-chain__item--redacted"
116
+ ),
117
+ children: [
118
+ /* @__PURE__ */ jsx("span", { className: "eth-task-thinking-chain__marker", "aria-hidden": "true" }),
119
+ /* @__PURE__ */ jsxs(
120
+ "details",
121
+ {
122
+ className: "eth-task-thinking-chain__step",
123
+ open: expanded,
124
+ onToggle: (event) => {
125
+ setExpanded(event.currentTarget.open);
126
+ },
127
+ children: [
128
+ /* @__PURE__ */ jsxs("summary", { className: "eth-task-thinking-chain__step-header", children: [
129
+ /* @__PURE__ */ jsx("span", { className: "eth-task-thinking-chain__disclosure-icon", "aria-hidden": "true" }),
130
+ /* @__PURE__ */ jsxs("span", { className: "eth-task-thinking-chain__step-header-inner", children: [
131
+ /* @__PURE__ */ jsxs("span", { className: "eth-task-thinking-chain__step-heading", children: [
132
+ /* @__PURE__ */ jsx("strong", { className: "eth-task-thinking-chain__step-title", children: step.redacted ? "Restricted step" : step.title }),
133
+ hasChildren ? /* @__PURE__ */ jsxs("span", { className: "eth-task-thinking-chain__branch-count", children: [
134
+ step.children?.length,
135
+ " ",
136
+ step.children?.length === 1 ? "substep" : "substeps"
137
+ ] }) : null,
138
+ isStreaming ? /* @__PURE__ */ jsxs("span", { className: "eth-task-thinking-chain__streaming", "aria-label": "Streaming update", children: [
139
+ /* @__PURE__ */ jsx("span", { className: "eth-task-thinking-chain__pulse", "aria-hidden": "true" }),
140
+ "Streaming"
141
+ ] }) : null
142
+ ] }),
143
+ /* @__PURE__ */ jsxs("span", { className: "eth-task-thinking-chain__step-meta", children: [
144
+ step.redacted ? /* @__PURE__ */ jsx(Badge, { severity: "warning", children: "Redacted" }) : null,
145
+ /* @__PURE__ */ jsx(StatusDot, { status: step.status, label: labelForStatus(step.status) }),
146
+ step.toolName ? /* @__PURE__ */ jsx(Tag, { children: step.toolName }) : null,
147
+ step.durationMs != null ? /* @__PURE__ */ jsx("span", { className: "eth-task-thinking-chain__duration", children: formatDuration(step.durationMs) }) : null
148
+ ] })
149
+ ] })
150
+ ] }),
151
+ step.redacted ? /* @__PURE__ */ jsx("p", { className: "eth-task-thinking-chain__redacted", children: "Details are hidden by policy." }) : step.summary ? /* @__PURE__ */ jsx("p", { className: "eth-task-thinking-chain__summary", children: step.summary }) : null,
152
+ hasChildren ? /* @__PURE__ */ jsx("ol", { className: "eth-task-thinking-chain__children", children: step.children?.map((child) => /* @__PURE__ */ jsx(ThinkingStepItem, { step: child, streamingStepId }, child.id)) }) : null
153
+ ]
154
+ }
155
+ )
156
+ ]
157
+ }
158
+ );
159
+ }
160
+ function BackendThinkingChain({
161
+ steps = [],
162
+ streaming,
163
+ className,
164
+ ...props
165
+ }) {
166
+ const streamingStepId = React.useMemo(
167
+ () => streaming ? currentStreamingStepId(steps) : void 0,
168
+ [steps, streaming]
169
+ );
170
+ return /* @__PURE__ */ jsx(
171
+ "section",
172
+ {
173
+ ...props,
174
+ className: clsx("eth-task-thinking-chain", className),
175
+ "aria-live": streaming ? "polite" : void 0,
176
+ "data-eth-component": "BackendThinkingChain",
177
+ children: steps.length ? /* @__PURE__ */ jsx("ol", { className: "eth-task-thinking-chain__list", children: steps.map((step) => /* @__PURE__ */ jsx(ThinkingStepItem, { step, streamingStepId }, step.id)) }) : /* @__PURE__ */ jsx(EmptyState, { title: "No backend steps" })
178
+ }
179
+ );
180
+ }
181
+
182
+ // src/components/BlockingReasonPanel.tsx
183
+ import clsx2 from "clsx";
184
+ import { ActionGroup, Badge as Badge2, EmptyState as EmptyState2, Surface } from "@echothink-ui/core";
185
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
186
+ function BlockingReasonPanel({
187
+ blockers = [],
188
+ className,
189
+ title = "Blocking reasons",
190
+ description,
191
+ severity,
192
+ status,
193
+ ...props
194
+ }) {
195
+ const hasBlockers = blockers.length > 0;
196
+ return /* @__PURE__ */ jsx2(
197
+ Surface,
198
+ {
199
+ ...props,
200
+ className: clsx2("eth-task-blocking-reason-panel", className),
201
+ title,
202
+ description,
203
+ status: status ?? (hasBlockers ? "blocked" : void 0),
204
+ severity: severity ?? (hasBlockers ? "danger" : void 0),
205
+ "data-eth-component": "BlockingReasonPanel",
206
+ children: hasBlockers ? /* @__PURE__ */ jsx2("ul", { className: "eth-task-blocking-reason-panel__list", "aria-label": "Active blocking reasons", children: blockers.map((blocker) => {
207
+ const resolveActions = blocker.resolveAction ? [
208
+ {
209
+ ...blocker.resolveAction,
210
+ intent: blocker.resolveAction.intent ?? "tertiary"
211
+ }
212
+ ] : void 0;
213
+ return /* @__PURE__ */ jsxs2("li", { className: "eth-task-blocking-reason-panel__item", children: [
214
+ /* @__PURE__ */ jsxs2("div", { className: "eth-task-blocking-reason-panel__content", children: [
215
+ /* @__PURE__ */ jsx2("strong", { className: "eth-task-blocking-reason-panel__reason", children: blocker.reason }),
216
+ /* @__PURE__ */ jsxs2("div", { className: "eth-task-blocking-reason-panel__meta", children: [
217
+ /* @__PURE__ */ jsx2(Badge2, { severity: "danger", children: "Blocked" }),
218
+ blocker.ownerActor ? /* @__PURE__ */ jsxs2("span", { children: [
219
+ "Owner: ",
220
+ blocker.ownerActor
221
+ ] }) : null
222
+ ] })
223
+ ] }),
224
+ resolveActions ? /* @__PURE__ */ jsx2("div", { className: "eth-task-blocking-reason-panel__actions", children: /* @__PURE__ */ jsx2(ActionGroup, { actions: resolveActions }) }) : null
225
+ ] }, blocker.id);
226
+ }) }) : /* @__PURE__ */ jsx2(
227
+ EmptyState2,
228
+ {
229
+ title: "No blocking reasons",
230
+ description: "This task can continue without unblock actions."
231
+ }
232
+ )
233
+ }
234
+ );
235
+ }
236
+
237
+ // src/components/DAGEdge.tsx
238
+ import * as React2 from "react";
239
+ import clsx3 from "clsx";
240
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
241
+ function markerIdFromUseId(id) {
242
+ return `eth-dag-arrow-${id.replace(/[^a-zA-Z0-9_-]/g, "")}`;
243
+ }
244
+ function DAGEdge({
245
+ from,
246
+ to,
247
+ status,
248
+ label,
249
+ className,
250
+ "aria-label": ariaLabel,
251
+ ...props
252
+ }) {
253
+ const reactId = React2.useId();
254
+ const markerId = markerIdFromUseId(reactId);
255
+ const horizontal = Math.abs(to.x - from.x) >= Math.abs(to.y - from.y);
256
+ const controlA = horizontal ? { x: (from.x + to.x) / 2, y: from.y } : { x: from.x, y: (from.y + to.y) / 2 };
257
+ const controlB = horizontal ? { x: (from.x + to.x) / 2, y: to.y } : { x: to.x, y: (from.y + to.y) / 2 };
258
+ const d = `M ${from.x} ${from.y} C ${controlA.x} ${controlA.y}, ${controlB.x} ${controlB.y}, ${to.x} ${to.y}`;
259
+ const color = statusColor(status);
260
+ const midpoint = { x: (from.x + to.x) / 2, y: (from.y + to.y) / 2 };
261
+ const labelText = label?.trim();
262
+ const labelWidth = labelText ? Math.max(56, labelText.length * 7 + 20) : 0;
263
+ const labelPosition = horizontal ? { x: midpoint.x, y: midpoint.y - 16 } : { x: midpoint.x + 44, y: midpoint.y };
264
+ const statusText = status ? labelForStatus(status) : "Neutral";
265
+ const accessibleLabel = typeof ariaLabel === "string" && ariaLabel.trim() ? ariaLabel : labelText ? `${labelText} dependency edge, ${statusText}` : `Dependency edge, ${statusText}`;
266
+ return /* @__PURE__ */ jsxs3(
267
+ "g",
268
+ {
269
+ className: clsx3("eth-dag-edge", status && `eth-dag-edge--${status}`, className),
270
+ role: "group",
271
+ "aria-label": accessibleLabel,
272
+ children: [
273
+ /* @__PURE__ */ jsx3("defs", { children: /* @__PURE__ */ jsx3(
274
+ "marker",
275
+ {
276
+ id: markerId,
277
+ markerWidth: "10",
278
+ markerHeight: "10",
279
+ refX: "9",
280
+ refY: "3",
281
+ orient: "auto",
282
+ markerUnits: "strokeWidth",
283
+ children: /* @__PURE__ */ jsx3("path", { d: "M0,0 L0,6 L9,3 z", fill: color })
284
+ }
285
+ ) }),
286
+ /* @__PURE__ */ jsx3(
287
+ "path",
288
+ {
289
+ ...props,
290
+ d,
291
+ fill: "none",
292
+ stroke: color,
293
+ strokeLinecap: "round",
294
+ strokeLinejoin: "round",
295
+ strokeWidth: 2,
296
+ markerEnd: `url(#${markerId})`,
297
+ vectorEffect: "non-scaling-stroke",
298
+ "aria-hidden": "true",
299
+ "data-eth-component": "DAGEdge"
300
+ }
301
+ ),
302
+ labelText ? /* @__PURE__ */ jsxs3("g", { className: "eth-dag-edge__label", "aria-hidden": "true", children: [
303
+ /* @__PURE__ */ jsx3(
304
+ "rect",
305
+ {
306
+ x: labelPosition.x - labelWidth / 2,
307
+ y: labelPosition.y - 11,
308
+ width: labelWidth,
309
+ height: 22,
310
+ rx: 0
311
+ }
312
+ ),
313
+ /* @__PURE__ */ jsx3(
314
+ "text",
315
+ {
316
+ x: labelPosition.x,
317
+ y: labelPosition.y,
318
+ textAnchor: "middle",
319
+ dominantBaseline: "middle",
320
+ children: labelText
321
+ }
322
+ )
323
+ ] }) : null
324
+ ]
325
+ }
326
+ );
327
+ }
328
+
329
+ // src/components/DAGLegend.tsx
330
+ import clsx4 from "clsx";
331
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
332
+ var defaultStatuses = [
333
+ "not-started",
334
+ "queued",
335
+ "running",
336
+ "blocked",
337
+ "failed",
338
+ "pending-approval",
339
+ "completed"
340
+ ];
341
+ var defaultEdgeTypes = [
342
+ { id: "dependency", label: "Dependency", variant: "dependency" },
343
+ { id: "critical-path", label: "Critical path", status: "running", variant: "critical" },
344
+ {
345
+ id: "approval-gate",
346
+ label: "Approval gate",
347
+ status: "pending-approval",
348
+ variant: "conditional"
349
+ }
350
+ ];
351
+ function colorVariable(color) {
352
+ return { "--eth-dag-legend-color": color };
353
+ }
354
+ function DAGLegend({
355
+ statuses = defaultStatuses,
356
+ edgeTypes = defaultEdgeTypes,
357
+ className,
358
+ "aria-label": ariaLabel,
359
+ ...props
360
+ }) {
361
+ return /* @__PURE__ */ jsxs4(
362
+ "div",
363
+ {
364
+ ...props,
365
+ className: clsx4("eth-dag-legend", className),
366
+ "data-eth-component": "DAGLegend",
367
+ role: "group",
368
+ "aria-label": ariaLabel ?? "DAG legend",
369
+ children: [
370
+ statuses.length ? /* @__PURE__ */ jsxs4("div", { className: "eth-dag-legend__group", children: [
371
+ /* @__PURE__ */ jsx4("span", { className: "eth-dag-legend__heading", children: "Node status" }),
372
+ /* @__PURE__ */ jsx4("ul", { className: "eth-dag-legend__list", "aria-label": "DAG node status legend", children: statuses.map((status) => /* @__PURE__ */ jsxs4("li", { className: "eth-dag-legend__item", children: [
373
+ /* @__PURE__ */ jsx4(
374
+ "span",
375
+ {
376
+ className: clsx4("eth-dag-legend__swatch", `eth-dag-legend__swatch--${status}`),
377
+ style: colorVariable(statusColor(status)),
378
+ "aria-hidden": "true"
379
+ }
380
+ ),
381
+ /* @__PURE__ */ jsx4("span", { className: "eth-dag-legend__label", children: labelForStatus(status) })
382
+ ] }, status)) })
383
+ ] }) : null,
384
+ edgeTypes.length ? /* @__PURE__ */ jsxs4("div", { className: "eth-dag-legend__group eth-dag-legend__group--edges", children: [
385
+ /* @__PURE__ */ jsx4("span", { className: "eth-dag-legend__heading", children: "Edge type" }),
386
+ /* @__PURE__ */ jsx4("ul", { className: "eth-dag-legend__list", "aria-label": "DAG edge type legend", children: edgeTypes.map((edgeType) => /* @__PURE__ */ jsxs4("li", { className: "eth-dag-legend__item", children: [
387
+ /* @__PURE__ */ jsx4(
388
+ "span",
389
+ {
390
+ className: clsx4(
391
+ "eth-dag-legend__edge",
392
+ `eth-dag-legend__edge--${edgeType.variant ?? "dependency"}`
393
+ ),
394
+ style: colorVariable(statusColor(edgeType.status)),
395
+ "aria-hidden": "true"
396
+ }
397
+ ),
398
+ /* @__PURE__ */ jsx4("span", { className: "eth-dag-legend__label", children: edgeType.label })
399
+ ] }, edgeType.id)) })
400
+ ] }) : null
401
+ ]
402
+ }
403
+ );
404
+ }
405
+
406
+ // src/components/DAGNode.tsx
407
+ import clsx5 from "clsx";
408
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
409
+ var nodeWidth = 156;
410
+ var nodeHeight = 60;
411
+ function truncateLabel(label) {
412
+ return label.length > 20 ? `${label.slice(0, 19)}...` : label;
413
+ }
414
+ function DAGNode({ node, selected, onSelect, className, ...props }) {
415
+ const statusLabel2 = labelForStatus(node.status);
416
+ const x = node.x - nodeWidth / 2;
417
+ const y = node.y - nodeHeight / 2;
418
+ const handleSelect = () => onSelect?.(node);
419
+ const interactive = Boolean(onSelect);
420
+ return /* @__PURE__ */ jsxs5(
421
+ "g",
422
+ {
423
+ ...props,
424
+ className: clsx5(
425
+ "eth-dag-node",
426
+ `eth-dag-node--${node.status}`,
427
+ interactive && "eth-dag-node--interactive",
428
+ selected && "eth-dag-node--selected",
429
+ className
430
+ ),
431
+ role: interactive ? "button" : "group",
432
+ tabIndex: interactive ? 0 : void 0,
433
+ "aria-label": `${node.label}, ${statusLabel2}`,
434
+ "aria-pressed": interactive ? Boolean(selected) : void 0,
435
+ onClick: interactive ? handleSelect : void 0,
436
+ onKeyDown: interactive ? (event) => {
437
+ if (event.key === "Enter" || event.key === " ") {
438
+ event.preventDefault();
439
+ handleSelect();
440
+ }
441
+ } : void 0,
442
+ "data-eth-component": "DAGNode",
443
+ children: [
444
+ /* @__PURE__ */ jsx5("title", { children: `${node.label} - ${statusLabel2}` }),
445
+ /* @__PURE__ */ jsx5(
446
+ "rect",
447
+ {
448
+ className: "eth-dag-node__selection",
449
+ x: x - 3,
450
+ y: y - 3,
451
+ width: nodeWidth + 6,
452
+ height: nodeHeight + 6,
453
+ rx: 0
454
+ }
455
+ ),
456
+ /* @__PURE__ */ jsx5(
457
+ "rect",
458
+ {
459
+ className: "eth-dag-node__body",
460
+ x,
461
+ y,
462
+ width: nodeWidth,
463
+ height: nodeHeight,
464
+ rx: 0,
465
+ fill: "var(--eth-color-layer-01)"
466
+ }
467
+ ),
468
+ /* @__PURE__ */ jsx5(
469
+ "rect",
470
+ {
471
+ className: "eth-dag-node__status-strip",
472
+ x,
473
+ y,
474
+ width: 4,
475
+ height: nodeHeight,
476
+ fill: statusColor(node.status)
477
+ }
478
+ ),
479
+ /* @__PURE__ */ jsx5("circle", { className: "eth-dag-node__port", cx: x, cy: node.y, r: 3 }),
480
+ /* @__PURE__ */ jsx5("circle", { className: "eth-dag-node__port", cx: x + nodeWidth, cy: node.y, r: 3 }),
481
+ /* @__PURE__ */ jsx5(
482
+ "circle",
483
+ {
484
+ className: "eth-dag-node__status-dot",
485
+ cx: x + 18,
486
+ cy: y + 18,
487
+ r: 3.5,
488
+ fill: statusColor(node.status)
489
+ }
490
+ ),
491
+ /* @__PURE__ */ jsx5(
492
+ "text",
493
+ {
494
+ x: x + 28,
495
+ y: y + 22,
496
+ textAnchor: "start",
497
+ className: "eth-dag-node__label",
498
+ children: truncateLabel(node.label)
499
+ }
500
+ ),
501
+ /* @__PURE__ */ jsx5(
502
+ "text",
503
+ {
504
+ x: x + 28,
505
+ y: y + 44,
506
+ textAnchor: "start",
507
+ className: "eth-dag-node__status",
508
+ children: statusLabel2
509
+ }
510
+ )
511
+ ]
512
+ }
513
+ );
514
+ }
515
+ var DAG_NODE_WIDTH = nodeWidth;
516
+ var DAG_NODE_HEIGHT = nodeHeight;
517
+
518
+ // src/components/DecisionRequiredPanel.tsx
519
+ import * as React3 from "react";
520
+ import clsx6 from "clsx";
521
+ import { InlineNotification, Surface as Surface2, Tag as Tag2 } from "@echothink-ui/core";
522
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
523
+ function optionsFromActions(actions) {
524
+ return actions?.map((action) => ({
525
+ id: action.id,
526
+ label: action.label,
527
+ intent: action.intent,
528
+ action
529
+ })) ?? [];
530
+ }
531
+ function riskTitle(riskLevel) {
532
+ if (!riskLevel) return "Decision required";
533
+ return `${riskLevel.charAt(0).toUpperCase()}${riskLevel.slice(1)} risk decision`;
534
+ }
535
+ function optionIntent(option) {
536
+ return option.intent ?? "secondary";
537
+ }
538
+ function DecisionRequiredPanel({
539
+ title,
540
+ summary,
541
+ riskLevel,
542
+ evidence = [],
543
+ options,
544
+ onDecide,
545
+ decidedOptionId,
546
+ className,
547
+ description,
548
+ actions,
549
+ items,
550
+ severity,
551
+ status,
552
+ ...props
553
+ }) {
554
+ const titleId = React3.useId();
555
+ const evidenceId = React3.useId();
556
+ const optionsId = React3.useId();
557
+ const resolvedOptions = options?.map((option) => ({ ...option })) ?? (actions?.length ? optionsFromActions(actions) : items?.length ? items.map((item) => ({
558
+ id: item.id,
559
+ label: String(item.label),
560
+ description: item.description ? String(item.description) : void 0,
561
+ intent: item.status === "active" ? "primary" : "secondary"
562
+ })) : []);
563
+ const riskSeverity = riskLevel ? severityForRisk(riskLevel) : severity ?? "warning";
564
+ return /* @__PURE__ */ jsxs6(
565
+ Surface2,
566
+ {
567
+ ...props,
568
+ role: "region",
569
+ "aria-labelledby": titleId,
570
+ className: clsx6(
571
+ "eth-task-decision-panel",
572
+ riskLevel && `eth-task-decision-panel--${riskLevel}`,
573
+ className
574
+ ),
575
+ title: /* @__PURE__ */ jsx6("span", { id: titleId, children: title }),
576
+ severity: riskSeverity === "neutral" ? severity : riskSeverity,
577
+ status: status ?? "approval-required",
578
+ "data-eth-component": "DecisionRequiredPanel",
579
+ children: [
580
+ /* @__PURE__ */ jsx6(InlineNotification, { severity: riskSeverity, title: riskTitle(riskLevel), children: description }),
581
+ summary ? /* @__PURE__ */ jsxs6("section", { className: "eth-task-decision-panel__summary", "aria-label": "Decision context", children: [
582
+ /* @__PURE__ */ jsx6("div", { className: "eth-task-decision-panel__section-label", children: "Decision context" }),
583
+ /* @__PURE__ */ jsx6("div", { className: "eth-task-decision-panel__summary-body", children: summary })
584
+ ] }) : null,
585
+ evidence.length ? /* @__PURE__ */ jsxs6("section", { className: "eth-task-decision-panel__evidence", "aria-labelledby": evidenceId, children: [
586
+ /* @__PURE__ */ jsxs6("div", { className: "eth-task-decision-panel__section-header", children: [
587
+ /* @__PURE__ */ jsx6("h3", { id: evidenceId, children: "Evidence" }),
588
+ /* @__PURE__ */ jsxs6("span", { children: [
589
+ evidence.length,
590
+ " source",
591
+ evidence.length === 1 ? "" : "s"
592
+ ] })
593
+ ] }),
594
+ /* @__PURE__ */ jsx6("ul", { children: evidence.map((item) => /* @__PURE__ */ jsxs6("li", { children: [
595
+ /* @__PURE__ */ jsx6("div", { className: "eth-task-decision-panel__evidence-label", children: item.href ? /* @__PURE__ */ jsx6("a", { className: "eth-task-decision-panel__evidence-link", href: item.href, children: item.label }) : /* @__PURE__ */ jsx6("strong", { children: item.label }) }),
596
+ item.preview ? /* @__PURE__ */ jsx6("div", { className: "eth-task-decision-panel__evidence-preview", children: item.preview }) : null
597
+ ] }, item.id)) })
598
+ ] }) : null,
599
+ resolvedOptions.length ? /* @__PURE__ */ jsxs6(
600
+ "section",
601
+ {
602
+ className: "eth-task-decision-panel__options",
603
+ role: "group",
604
+ "aria-labelledby": optionsId,
605
+ children: [
606
+ /* @__PURE__ */ jsxs6("div", { className: "eth-task-decision-panel__section-header", children: [
607
+ /* @__PURE__ */ jsx6("h3", { id: optionsId, children: "Decision options" }),
608
+ /* @__PURE__ */ jsxs6("span", { children: [
609
+ resolvedOptions.length,
610
+ " available"
611
+ ] })
612
+ ] }),
613
+ resolvedOptions.map((option) => /* @__PURE__ */ jsxs6(
614
+ "button",
615
+ {
616
+ type: "button",
617
+ className: clsx6(
618
+ "eth-task-decision-panel__option",
619
+ `eth-task-decision-panel__option--${optionIntent(option)}`
620
+ ),
621
+ disabled: option.action?.disabled,
622
+ "aria-pressed": decidedOptionId === option.id,
623
+ onClick: () => {
624
+ option.action?.onSelect?.();
625
+ onDecide?.(option.id);
626
+ },
627
+ children: [
628
+ /* @__PURE__ */ jsxs6("span", { className: "eth-task-decision-panel__option-main", children: [
629
+ /* @__PURE__ */ jsx6("span", { className: "eth-task-decision-panel__option-label", children: option.label }),
630
+ option.description ? /* @__PURE__ */ jsx6("span", { className: "eth-task-decision-panel__option-description", children: option.description }) : null
631
+ ] }),
632
+ /* @__PURE__ */ jsx6("span", { className: "eth-task-decision-panel__option-state", children: decidedOptionId === option.id ? "Selected" : "Choose" })
633
+ ]
634
+ },
635
+ option.id
636
+ ))
637
+ ]
638
+ }
639
+ ) : null,
640
+ decidedOptionId ? /* @__PURE__ */ jsx6("div", { className: "eth-task-decision-panel__decision", children: /* @__PURE__ */ jsxs6(Tag2, { children: [
641
+ "Decided: ",
642
+ decidedOptionId
643
+ ] }) }) : null
644
+ ]
645
+ }
646
+ );
647
+ }
648
+
649
+ // src/components/HumanInterventionPanel.tsx
650
+ import * as React4 from "react";
651
+ import clsx7 from "clsx";
652
+ import { ActionGroup as ActionGroup2, Badge as Badge3, InlineNotification as InlineNotification2, Surface as Surface3 } from "@echothink-ui/core";
653
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
654
+ function urgencySeverity(urgency) {
655
+ if (urgency === "high") return "danger";
656
+ if (urgency === "medium") return "warning";
657
+ return "info";
658
+ }
659
+ function urgencyLabel(urgency) {
660
+ if (urgency === "high") return "High urgency";
661
+ if (urgency === "medium") return "Medium urgency";
662
+ return "Low urgency";
663
+ }
664
+ function HumanInterventionPanel({
665
+ reason,
666
+ affectedTask,
667
+ actions,
668
+ urgency = "medium",
669
+ className,
670
+ title = "Human intervention required",
671
+ status = "approval-required",
672
+ severity,
673
+ role = "region",
674
+ "aria-labelledby": ariaLabelledBy,
675
+ ...props
676
+ }) {
677
+ const titleId = React4.useId();
678
+ const panelSeverity = severity ?? urgencySeverity(urgency);
679
+ const labelledBy = ariaLabelledBy ?? (title ? titleId : void 0);
680
+ return /* @__PURE__ */ jsxs7(
681
+ Surface3,
682
+ {
683
+ ...props,
684
+ role,
685
+ "aria-labelledby": labelledBy,
686
+ className: clsx7("eth-task-human-intervention-panel", className),
687
+ title: title ? /* @__PURE__ */ jsx7("span", { id: titleId, children: title }) : void 0,
688
+ status,
689
+ severity: panelSeverity,
690
+ "data-eth-component": "HumanInterventionPanel",
691
+ children: [
692
+ /* @__PURE__ */ jsx7(InlineNotification2, { severity: panelSeverity, title: "Intervention requested", children: reason ?? "Review this task and choose the next action before the agent can continue." }),
693
+ /* @__PURE__ */ jsxs7("dl", { className: "eth-task-human-intervention-panel__details", "aria-label": "Intervention details", children: [
694
+ affectedTask ? /* @__PURE__ */ jsxs7("div", { children: [
695
+ /* @__PURE__ */ jsx7("dt", { children: "Affected task" }),
696
+ /* @__PURE__ */ jsxs7("dd", { children: [
697
+ /* @__PURE__ */ jsx7("span", { className: "eth-task-human-intervention-panel__task-title", children: affectedTask.title }),
698
+ /* @__PURE__ */ jsx7("span", { className: "eth-task-human-intervention-panel__task-id", children: affectedTask.id })
699
+ ] })
700
+ ] }) : null,
701
+ /* @__PURE__ */ jsxs7("div", { children: [
702
+ /* @__PURE__ */ jsx7("dt", { children: "Urgency" }),
703
+ /* @__PURE__ */ jsx7("dd", { children: /* @__PURE__ */ jsx7(Badge3, { severity: panelSeverity, children: urgencyLabel(urgency) }) })
704
+ ] })
705
+ ] }),
706
+ actions?.length ? /* @__PURE__ */ jsx7(
707
+ "div",
708
+ {
709
+ className: "eth-task-human-intervention-panel__actions",
710
+ role: "group",
711
+ "aria-label": "Intervention actions",
712
+ children: /* @__PURE__ */ jsx7(ActionGroup2, { actions })
713
+ }
714
+ ) : null
715
+ ]
716
+ }
717
+ );
718
+ }
719
+
720
+ // src/components/TaskApprovalPanel.tsx
721
+ import * as React5 from "react";
722
+ import { Button, FormField, InlineNotification as InlineNotification3, Textarea } from "@echothink-ui/core";
723
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
724
+ function refEvidence(taskRef, diffRef, policyRef, evidence = []) {
725
+ const refs = [];
726
+ if (taskRef) refs.push({ id: "task-ref", label: "Task reference", preview: taskRef });
727
+ if (diffRef) refs.push({ id: "diff-ref", label: "Diff reference", preview: diffRef });
728
+ if (policyRef) refs.push({ id: "policy-ref", label: "Policy reference", preview: policyRef });
729
+ return [...refs, ...evidence];
730
+ }
731
+ function stateNotification(state) {
732
+ if (state === "approved") {
733
+ return {
734
+ severity: "success",
735
+ title: "Output approved",
736
+ description: "The approval has been recorded with the current audit metadata."
737
+ };
738
+ }
739
+ if (state === "rejected") {
740
+ return {
741
+ severity: "danger",
742
+ title: "Output rejected",
743
+ description: "The rejection reason and evidence references are retained for review."
744
+ };
745
+ }
746
+ if (state === "pending") {
747
+ return {
748
+ severity: "info",
749
+ title: "Decision pending",
750
+ description: "The approval decision is being prepared for audit capture."
751
+ };
752
+ }
753
+ return null;
754
+ }
755
+ function TaskApprovalPanel({
756
+ taskRef,
757
+ diffRef,
758
+ policyRef,
759
+ summary,
760
+ evidence,
761
+ onApprove,
762
+ onReject,
763
+ onRequestChanges,
764
+ state = "idle",
765
+ title = "Task approval required",
766
+ description,
767
+ footer,
768
+ ...props
769
+ }) {
770
+ const [reason, setReason] = React5.useState("");
771
+ const reasonId = React5.useId();
772
+ const notification = stateNotification(state);
773
+ const policy = policyRef ?? "Default task policy";
774
+ const isPending = state === "pending";
775
+ return /* @__PURE__ */ jsx8("section", { className: "eth-task-approval-panel", "data-eth-component": "TaskApprovalPanel", children: /* @__PURE__ */ jsx8(
776
+ DecisionRequiredPanel,
777
+ {
778
+ ...props,
779
+ title,
780
+ summary: /* @__PURE__ */ jsxs8("div", { className: "eth-task-approval-panel__body", children: [
781
+ summary ? /* @__PURE__ */ jsx8("div", { className: "eth-task-approval-panel__summary", children: summary }) : null,
782
+ notification ? /* @__PURE__ */ jsx8(
783
+ InlineNotification3,
784
+ {
785
+ className: "eth-task-approval-panel__state",
786
+ severity: notification.severity,
787
+ title: notification.title,
788
+ children: notification.description
789
+ }
790
+ ) : null,
791
+ /* @__PURE__ */ jsx8(
792
+ FormField,
793
+ {
794
+ id: reasonId,
795
+ label: "Decision reason",
796
+ helperText: "Stored with approval audit metadata.",
797
+ className: "eth-task-approval-panel__reason",
798
+ children: /* @__PURE__ */ jsx8(
799
+ Textarea,
800
+ {
801
+ value: reason,
802
+ rows: 4,
803
+ placeholder: "Summarize the approval rationale, rejection issue, or requested change.",
804
+ onChange: (event) => setReason(event.currentTarget.value)
805
+ }
806
+ )
807
+ }
808
+ ),
809
+ /* @__PURE__ */ jsxs8("dl", { className: "eth-task-approval-panel__audit", children: [
810
+ /* @__PURE__ */ jsxs8("div", { children: [
811
+ /* @__PURE__ */ jsx8("dt", { children: "Task" }),
812
+ /* @__PURE__ */ jsx8("dd", { children: taskRef ?? "-" })
813
+ ] }),
814
+ diffRef ? /* @__PURE__ */ jsxs8("div", { children: [
815
+ /* @__PURE__ */ jsx8("dt", { children: "Diff" }),
816
+ /* @__PURE__ */ jsx8("dd", { children: diffRef })
817
+ ] }) : null,
818
+ /* @__PURE__ */ jsxs8("div", { children: [
819
+ /* @__PURE__ */ jsx8("dt", { children: "Policy" }),
820
+ /* @__PURE__ */ jsx8("dd", { children: policy })
821
+ ] })
822
+ ] })
823
+ ] }),
824
+ description: description ?? "Review the task output and choose an approval outcome.",
825
+ riskLevel: "high",
826
+ evidence: refEvidence(taskRef, diffRef, policyRef, evidence),
827
+ options: [],
828
+ footer: /* @__PURE__ */ jsxs8("div", { className: "eth-task-approval-panel__footer", children: [
829
+ /* @__PURE__ */ jsxs8(
830
+ "div",
831
+ {
832
+ className: "eth-task-approval-panel__actions",
833
+ role: "group",
834
+ "aria-label": "Approval actions",
835
+ children: [
836
+ /* @__PURE__ */ jsx8(
837
+ Button,
838
+ {
839
+ intent: "success",
840
+ disabled: isPending || !onApprove,
841
+ "aria-pressed": state === "approved",
842
+ onClick: () => onApprove?.(reason),
843
+ children: "Approve"
844
+ }
845
+ ),
846
+ /* @__PURE__ */ jsx8(
847
+ Button,
848
+ {
849
+ intent: "danger",
850
+ disabled: isPending || !onReject,
851
+ "aria-pressed": state === "rejected",
852
+ onClick: () => onReject?.(reason),
853
+ children: "Reject"
854
+ }
855
+ ),
856
+ /* @__PURE__ */ jsx8(
857
+ Button,
858
+ {
859
+ intent: "secondary",
860
+ disabled: isPending || !onRequestChanges,
861
+ onClick: () => onRequestChanges?.(reason),
862
+ children: "Request changes"
863
+ }
864
+ )
865
+ ]
866
+ }
867
+ ),
868
+ footer ? /* @__PURE__ */ jsx8("div", { className: "eth-task-approval-panel__custom-footer", children: footer }) : null
869
+ ] })
870
+ }
871
+ ) });
872
+ }
873
+
874
+ // src/components/TaskCard.tsx
875
+ import clsx8 from "clsx";
876
+ import { Badge as Badge4, Surface as Surface4, Tag as Tag3 } from "@echothink-ui/core";
877
+ import { jsx as jsx9 } from "react/jsx-runtime";
878
+ function TaskCard({ task, actions, onSelect, className, ...props }) {
879
+ const cardActions = actions?.map((action) => ({
880
+ ...action,
881
+ intent: action.intent ?? "tertiary"
882
+ }));
883
+ const interactiveProps = onSelect ? {
884
+ role: "button",
885
+ tabIndex: 0,
886
+ onClick: () => onSelect(task),
887
+ onKeyDown: (event) => {
888
+ if (event.key === "Enter" || event.key === " ") {
889
+ event.preventDefault();
890
+ onSelect(task);
891
+ }
892
+ }
893
+ } : {};
894
+ return /* @__PURE__ */ jsx9(
895
+ Surface4,
896
+ {
897
+ ...props,
898
+ ...interactiveProps,
899
+ className: clsx8("eth-task-card", onSelect && "eth-task-card--selectable", className),
900
+ density: "compact",
901
+ title: task.title,
902
+ subtitle: task.id,
903
+ description: task.description,
904
+ status: task.status,
905
+ actions: cardActions,
906
+ metadata: [
907
+ {
908
+ label: "Assignee",
909
+ value: task.assignee ? /* @__PURE__ */ jsx9("span", { className: "eth-task-card__assignee", children: task.assignee }) : "-"
910
+ },
911
+ {
912
+ label: "Due",
913
+ value: formatDateTime(task.dueAt)
914
+ },
915
+ {
916
+ label: "Priority",
917
+ value: task.priority ? /* @__PURE__ */ jsx9(Badge4, { severity: severityForPriority(task.priority), children: task.priority }) : "-"
918
+ }
919
+ ],
920
+ "data-eth-component": "TaskCard",
921
+ children: task.tags?.length ? /* @__PURE__ */ jsx9("div", { className: "eth-task-card__tags", "aria-label": "Task tags", children: task.tags.map((tag) => /* @__PURE__ */ jsx9(Tag3, { children: tag }, tag)) }) : null
922
+ }
923
+ );
924
+ }
925
+
926
+ // src/components/TaskDependencyList.tsx
927
+ import * as React6 from "react";
928
+ import clsx10 from "clsx";
929
+ import { Badge as Badge6, EmptyState as EmptyState3 } from "@echothink-ui/core";
930
+
931
+ // src/components/TaskStatusBadge.tsx
932
+ import clsx9 from "clsx";
933
+ import { Badge as Badge5 } from "@echothink-ui/core";
934
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
935
+ function TaskStatusBadge({
936
+ status,
937
+ label,
938
+ className,
939
+ title,
940
+ ...props
941
+ }) {
942
+ const statusLabel2 = label ?? labelForStatus(status);
943
+ return /* @__PURE__ */ jsxs9(
944
+ Badge5,
945
+ {
946
+ ...props,
947
+ className: clsx9("eth-task-status-badge", `eth-task-status-badge--${status}`, className),
948
+ "data-eth-component": "TaskStatusBadge",
949
+ "data-status": status,
950
+ severity: severityForStatus(status),
951
+ title: title ?? statusLabel2,
952
+ children: [
953
+ /* @__PURE__ */ jsx10("span", { className: "eth-task-status-badge__indicator", "aria-hidden": "true" }),
954
+ /* @__PURE__ */ jsx10("span", { className: "eth-task-status-badge__label", children: statusLabel2 })
955
+ ]
956
+ }
957
+ );
958
+ }
959
+
960
+ // src/components/TaskDependencyList.tsx
961
+ import { Fragment, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
962
+ var groupDescriptions = {
963
+ "blocked-by": "Prerequisite work that must clear before this task can move forward.",
964
+ blocks: "Downstream work waiting on this task."
965
+ };
966
+ function taskCountLabel(count) {
967
+ return `${count} task${count === 1 ? "" : "s"}`;
968
+ }
969
+ function relationshipLabel(relationship) {
970
+ return relationship === "blocks" ? "Dependent" : "Prerequisite";
971
+ }
972
+ function DependencyGroup({
973
+ title,
974
+ relationship,
975
+ dependencies
976
+ }) {
977
+ const headingId = React6.useId();
978
+ if (!dependencies.length) return null;
979
+ return /* @__PURE__ */ jsxs10("section", { className: "eth-task-dependencies__group", "aria-labelledby": headingId, children: [
980
+ /* @__PURE__ */ jsxs10("div", { className: "eth-task-dependencies__group-header", children: [
981
+ /* @__PURE__ */ jsxs10("div", { className: "eth-task-dependencies__group-heading", children: [
982
+ /* @__PURE__ */ jsx11("h4", { id: headingId, children: title }),
983
+ /* @__PURE__ */ jsx11("p", { children: groupDescriptions[relationship] })
984
+ ] }),
985
+ /* @__PURE__ */ jsx11(Badge6, { children: taskCountLabel(dependencies.length) })
986
+ ] }),
987
+ /* @__PURE__ */ jsx11("ul", { className: "eth-task-dependencies__list", children: dependencies.map((dependency) => /* @__PURE__ */ jsxs10(
988
+ "li",
989
+ {
990
+ className: clsx10(
991
+ "eth-task-dependencies__item",
992
+ `eth-task-dependencies__item--${dependency.status}`
993
+ ),
994
+ "data-status": dependency.status,
995
+ children: [
996
+ /* @__PURE__ */ jsxs10("div", { className: "eth-task-dependencies__item-main", children: [
997
+ /* @__PURE__ */ jsx11("span", { className: "eth-task-dependencies__relationship", children: relationshipLabel(relationship) }),
998
+ /* @__PURE__ */ jsx11("span", { className: "eth-task-dependencies__title", children: dependency.title }),
999
+ /* @__PURE__ */ jsx11("span", { className: "eth-task-dependencies__id", children: dependency.id })
1000
+ ] }),
1001
+ /* @__PURE__ */ jsx11(TaskStatusBadge, { status: dependency.status })
1002
+ ]
1003
+ },
1004
+ dependency.id
1005
+ )) })
1006
+ ] });
1007
+ }
1008
+ function TaskDependencyList({
1009
+ dependencies = [],
1010
+ blocks = [],
1011
+ blockedBy = [],
1012
+ emptyTitle = "No dependencies",
1013
+ className,
1014
+ ...props
1015
+ }) {
1016
+ const derivedBlocks = dependencies.filter((dependency) => dependency.relationship === "blocks");
1017
+ const derivedBlockedBy = dependencies.filter(
1018
+ (dependency) => dependency.relationship !== "blocks"
1019
+ );
1020
+ const blockList = blocks.length ? blocks : derivedBlocks;
1021
+ const blockedByList = blockedBy.length ? blockedBy : derivedBlockedBy;
1022
+ const hasDependencies = blockList.length || blockedByList.length;
1023
+ return /* @__PURE__ */ jsx11(
1024
+ "div",
1025
+ {
1026
+ ...props,
1027
+ className: clsx10("eth-task-dependencies", className),
1028
+ "data-eth-component": "TaskDependencyList",
1029
+ children: hasDependencies ? /* @__PURE__ */ jsxs10(Fragment, { children: [
1030
+ /* @__PURE__ */ jsx11(
1031
+ DependencyGroup,
1032
+ {
1033
+ title: "Blocked by",
1034
+ relationship: "blocked-by",
1035
+ dependencies: blockedByList
1036
+ }
1037
+ ),
1038
+ /* @__PURE__ */ jsx11(DependencyGroup, { title: "Blocks", relationship: "blocks", dependencies: blockList })
1039
+ ] }) : /* @__PURE__ */ jsx11(EmptyState3, { title: emptyTitle })
1040
+ }
1041
+ );
1042
+ }
1043
+
1044
+ // src/components/TaskDetailPanel.tsx
1045
+ import clsx12 from "clsx";
1046
+ import { ActionGroup as ActionGroup3, Badge as Badge8, EmptyState as EmptyState5, Surface as Surface5 } from "@echothink-ui/core";
1047
+
1048
+ // src/components/TaskTimeline.tsx
1049
+ import clsx11 from "clsx";
1050
+ import { Badge as Badge7, EmptyState as EmptyState4 } from "@echothink-ui/core";
1051
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1052
+ var eventKindMeta = {
1053
+ status_change: { label: "Status", tone: "info" },
1054
+ comment: { label: "Comment", tone: "neutral" },
1055
+ assignment: { label: "Assignment", tone: "info" },
1056
+ attachment: { label: "Attachment", tone: "success" },
1057
+ system: { label: "System", tone: "neutral" }
1058
+ };
1059
+ function timestampMeta(value) {
1060
+ const date = value instanceof Date ? value : new Date(value);
1061
+ if (Number.isNaN(date.valueOf())) {
1062
+ const fallback = String(value);
1063
+ return { dateTime: fallback, label: fallback };
1064
+ }
1065
+ return {
1066
+ dateTime: date.toISOString(),
1067
+ label: formatDateTime(date)
1068
+ };
1069
+ }
1070
+ function TaskTimeline({
1071
+ events = [],
1072
+ className,
1073
+ role,
1074
+ "aria-label": ariaLabel,
1075
+ ...props
1076
+ }) {
1077
+ if (!events.length) {
1078
+ return /* @__PURE__ */ jsx12(
1079
+ "div",
1080
+ {
1081
+ ...props,
1082
+ className: clsx11("eth-task-timeline", className),
1083
+ "data-eth-component": "TaskTimeline",
1084
+ role: role ?? "region",
1085
+ "aria-label": ariaLabel ?? "Task timeline",
1086
+ children: /* @__PURE__ */ jsx12(
1087
+ EmptyState4,
1088
+ {
1089
+ title: "No task history",
1090
+ description: "Task activity and state changes appear here."
1091
+ }
1092
+ )
1093
+ }
1094
+ );
1095
+ }
1096
+ return /* @__PURE__ */ jsx12(
1097
+ "div",
1098
+ {
1099
+ ...props,
1100
+ className: clsx11("eth-task-timeline", className),
1101
+ "data-eth-component": "TaskTimeline",
1102
+ role: role ?? "region",
1103
+ "aria-label": ariaLabel ?? "Task timeline",
1104
+ children: /* @__PURE__ */ jsx12("ol", { className: "eth-task-timeline__list", children: events.map((event) => {
1105
+ const kind = eventKindMeta[event.kind];
1106
+ const time = timestampMeta(event.timestamp);
1107
+ return /* @__PURE__ */ jsxs11(
1108
+ "li",
1109
+ {
1110
+ className: clsx11(
1111
+ "eth-task-timeline__event",
1112
+ `eth-task-timeline__event--${event.kind}`
1113
+ ),
1114
+ "data-kind": event.kind,
1115
+ children: [
1116
+ /* @__PURE__ */ jsx12("time", { className: "eth-task-timeline__time", dateTime: time.dateTime, children: time.label }),
1117
+ /* @__PURE__ */ jsx12("div", { className: "eth-task-timeline__rail", "aria-hidden": true, children: /* @__PURE__ */ jsx12("span", { className: "eth-task-timeline__marker" }) }),
1118
+ /* @__PURE__ */ jsxs11("article", { className: "eth-task-timeline__body", children: [
1119
+ /* @__PURE__ */ jsxs11("header", { className: "eth-task-timeline__event-header", children: [
1120
+ /* @__PURE__ */ jsx12(Badge7, { severity: kind.tone, children: kind.label }),
1121
+ event.actor ? /* @__PURE__ */ jsxs11("span", { className: "eth-task-timeline__actor", children: [
1122
+ "By ",
1123
+ event.actor
1124
+ ] }) : null
1125
+ ] }),
1126
+ /* @__PURE__ */ jsx12("strong", { className: "eth-task-timeline__summary", children: event.summary }),
1127
+ event.details ? /* @__PURE__ */ jsx12("p", { className: "eth-task-timeline__details", children: event.details }) : null
1128
+ ] })
1129
+ ]
1130
+ },
1131
+ event.id
1132
+ );
1133
+ }) })
1134
+ }
1135
+ );
1136
+ }
1137
+
1138
+ // src/components/TaskDetailPanel.tsx
1139
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1140
+ function formatCount(count, singular, plural = `${singular}s`) {
1141
+ return `${count} ${count === 1 ? singular : plural}`;
1142
+ }
1143
+ function DetailSection({
1144
+ title,
1145
+ meta,
1146
+ open,
1147
+ children,
1148
+ className
1149
+ }) {
1150
+ return /* @__PURE__ */ jsxs12("details", { open, className: clsx12("eth-task-detail-panel__section", className), children: [
1151
+ /* @__PURE__ */ jsxs12("summary", { className: "eth-task-detail-panel__summary", children: [
1152
+ /* @__PURE__ */ jsx13("span", { className: "eth-task-detail-panel__summary-label", children: title }),
1153
+ meta ? /* @__PURE__ */ jsx13("span", { className: "eth-task-detail-panel__summary-meta", children: meta }) : null
1154
+ ] }),
1155
+ /* @__PURE__ */ jsx13("div", { className: "eth-task-detail-panel__section-body", children })
1156
+ ] });
1157
+ }
1158
+ function TaskDetailPanel({
1159
+ task,
1160
+ dependencies,
1161
+ events,
1162
+ actions,
1163
+ className,
1164
+ title,
1165
+ description,
1166
+ items,
1167
+ ...props
1168
+ }) {
1169
+ if (!task) {
1170
+ return /* @__PURE__ */ jsx13(
1171
+ Surface5,
1172
+ {
1173
+ ...props,
1174
+ className: clsx12("eth-task-detail-panel", className),
1175
+ title: title ?? "Task detail",
1176
+ description,
1177
+ items,
1178
+ actions,
1179
+ "data-eth-component": "TaskDetailPanel",
1180
+ children: !items?.length ? /* @__PURE__ */ jsx13(EmptyState5, { title: "No task selected" }) : null
1181
+ }
1182
+ );
1183
+ }
1184
+ const resolvedDependencies = dependencies ?? task.dependencies ?? [];
1185
+ const resolvedEvents = events ?? task.history ?? [];
1186
+ return /* @__PURE__ */ jsx13(
1187
+ Surface5,
1188
+ {
1189
+ ...props,
1190
+ className: clsx12("eth-task-detail-panel", className),
1191
+ title: title ?? task.title,
1192
+ subtitle: task.id,
1193
+ status: task.status,
1194
+ "data-eth-component": "TaskDetailPanel",
1195
+ children: /* @__PURE__ */ jsxs12("div", { className: "eth-task-detail-panel__stack", children: [
1196
+ /* @__PURE__ */ jsx13(DetailSection, { title: "Metadata", open: true, children: /* @__PURE__ */ jsxs12("dl", { className: "eth-meta-grid eth-task-detail-panel__metadata", children: [
1197
+ /* @__PURE__ */ jsxs12("div", { children: [
1198
+ /* @__PURE__ */ jsx13("dt", { children: "Status" }),
1199
+ /* @__PURE__ */ jsx13("dd", { children: /* @__PURE__ */ jsx13(TaskStatusBadge, { status: task.status }) })
1200
+ ] }),
1201
+ /* @__PURE__ */ jsxs12("div", { children: [
1202
+ /* @__PURE__ */ jsx13("dt", { children: "Assignee" }),
1203
+ /* @__PURE__ */ jsx13("dd", { children: task.assignee ?? "-" })
1204
+ ] }),
1205
+ /* @__PURE__ */ jsxs12("div", { children: [
1206
+ /* @__PURE__ */ jsx13("dt", { children: "Due" }),
1207
+ /* @__PURE__ */ jsx13("dd", { children: formatDateTime(task.dueAt) })
1208
+ ] }),
1209
+ /* @__PURE__ */ jsxs12("div", { children: [
1210
+ /* @__PURE__ */ jsx13("dt", { children: "Priority" }),
1211
+ /* @__PURE__ */ jsx13("dd", { children: task.priority ? /* @__PURE__ */ jsx13(Badge8, { severity: severityForPriority(task.priority), children: task.priority }) : "-" })
1212
+ ] })
1213
+ ] }) }),
1214
+ /* @__PURE__ */ jsx13(DetailSection, { title: "Description", open: true, children: /* @__PURE__ */ jsx13("div", { className: "eth-task-detail-panel__description", children: task.description ?? description ?? "No description provided." }) }),
1215
+ /* @__PURE__ */ jsx13(
1216
+ DetailSection,
1217
+ {
1218
+ title: "Dependencies",
1219
+ meta: formatCount(resolvedDependencies.length, "dependency", "dependencies"),
1220
+ open: resolvedDependencies.length > 0,
1221
+ children: /* @__PURE__ */ jsx13(TaskDependencyList, { dependencies: resolvedDependencies })
1222
+ }
1223
+ ),
1224
+ /* @__PURE__ */ jsx13(
1225
+ DetailSection,
1226
+ {
1227
+ title: "History",
1228
+ meta: formatCount(resolvedEvents.length, "event"),
1229
+ open: resolvedEvents.length > 0,
1230
+ children: /* @__PURE__ */ jsx13(TaskTimeline, { events: resolvedEvents })
1231
+ }
1232
+ ),
1233
+ actions?.length ? /* @__PURE__ */ jsx13(DetailSection, { title: "Actions", meta: formatCount(actions.length, "action"), open: true, children: /* @__PURE__ */ jsx13("div", { className: "eth-task-detail-panel__actions", children: /* @__PURE__ */ jsx13(ActionGroup3, { actions }) }) }) : null
1234
+ ] })
1235
+ }
1236
+ );
1237
+ }
1238
+
1239
+ // src/components/TaskHandoffPanel.tsx
1240
+ import * as React7 from "react";
1241
+ import clsx13 from "clsx";
1242
+ import {
1243
+ Badge as Badge9,
1244
+ Button as Button2,
1245
+ EmptyState as EmptyState6,
1246
+ FormField as FormField2,
1247
+ Surface as Surface6,
1248
+ Textarea as Textarea2
1249
+ } from "@echothink-ui/core";
1250
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
1251
+ function TaskHandoffPanel({
1252
+ assigneeOptions = [],
1253
+ currentAssigneeId,
1254
+ onHandoff,
1255
+ reasonRequired,
1256
+ className,
1257
+ title = "Task handoff",
1258
+ description,
1259
+ ...props
1260
+ }) {
1261
+ const [selectedId, setSelectedId] = React7.useState(
1262
+ currentAssigneeId ?? assigneeOptions[0]?.id ?? ""
1263
+ );
1264
+ const [reason, setReason] = React7.useState("");
1265
+ const targetId = React7.useId();
1266
+ const reasonId = React7.useId();
1267
+ const trimmedReason = reason.trim();
1268
+ const cannotSubmit = !selectedId || reasonRequired && !trimmedReason;
1269
+ const submitHandoff = (event) => {
1270
+ event?.preventDefault();
1271
+ if (cannotSubmit) return;
1272
+ onHandoff?.(selectedId, trimmedReason);
1273
+ };
1274
+ const handleReasonKeyDown = (event) => {
1275
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
1276
+ event.preventDefault();
1277
+ submitHandoff();
1278
+ }
1279
+ };
1280
+ return /* @__PURE__ */ jsx14(
1281
+ Surface6,
1282
+ {
1283
+ ...props,
1284
+ className: clsx13("eth-task-handoff-panel", className),
1285
+ title,
1286
+ description,
1287
+ "data-eth-component": "TaskHandoffPanel",
1288
+ children: assigneeOptions.length ? /* @__PURE__ */ jsxs13("form", { className: "eth-task-handoff-panel__body", onSubmit: submitHandoff, children: [
1289
+ /* @__PURE__ */ jsxs13("div", { className: "eth-task-handoff-panel__target-field", children: [
1290
+ /* @__PURE__ */ jsx14("p", { id: targetId, className: "eth-task-handoff-panel__target-heading", children: "Handoff target" }),
1291
+ /* @__PURE__ */ jsx14(
1292
+ "div",
1293
+ {
1294
+ className: "eth-task-handoff-panel__list",
1295
+ role: "group",
1296
+ "aria-labelledby": targetId,
1297
+ children: assigneeOptions.map((option) => /* @__PURE__ */ jsxs13(
1298
+ "button",
1299
+ {
1300
+ type: "button",
1301
+ "aria-label": `Hand off to ${option.label} (${formatAssigneeKind(
1302
+ option.kind
1303
+ )})`,
1304
+ "aria-pressed": selectedId === option.id,
1305
+ className: clsx13(
1306
+ "eth-task-handoff-panel__option",
1307
+ selectedId === option.id && "eth-task-handoff-panel__option--selected"
1308
+ ),
1309
+ onClick: () => setSelectedId(option.id),
1310
+ children: [
1311
+ /* @__PURE__ */ jsx14("span", { className: "eth-task-handoff-panel__option-copy", children: /* @__PURE__ */ jsx14("span", { className: "eth-task-handoff-panel__option-label", children: option.label }) }),
1312
+ /* @__PURE__ */ jsxs13("span", { className: "eth-task-handoff-panel__option-meta", children: [
1313
+ selectedId === option.id ? /* @__PURE__ */ jsx14(Badge9, { severity: "info", children: "Selected" }) : null,
1314
+ /* @__PURE__ */ jsx14(Badge9, { severity: "neutral", children: formatAssigneeKind(option.kind) })
1315
+ ] })
1316
+ ]
1317
+ },
1318
+ option.id
1319
+ ))
1320
+ }
1321
+ )
1322
+ ] }),
1323
+ /* @__PURE__ */ jsx14(
1324
+ FormField2,
1325
+ {
1326
+ id: reasonId,
1327
+ label: "Handoff reason",
1328
+ required: reasonRequired,
1329
+ className: "eth-task-handoff-panel__reason-field",
1330
+ helperText: reasonRequired ? "A reason is required for this handoff." : void 0,
1331
+ children: /* @__PURE__ */ jsx14(
1332
+ Textarea2,
1333
+ {
1334
+ className: "eth-task-handoff-panel__reason",
1335
+ name: "handoff-reason",
1336
+ autoComplete: "off",
1337
+ placeholder: "Summarize context, blockers, and the next action...",
1338
+ value: reason,
1339
+ rows: 3,
1340
+ onKeyDown: handleReasonKeyDown,
1341
+ onChange: (event) => setReason(event.currentTarget.value)
1342
+ }
1343
+ )
1344
+ }
1345
+ ),
1346
+ /* @__PURE__ */ jsx14("div", { className: "eth-task-handoff-panel__actions", children: /* @__PURE__ */ jsx14(Button2, { type: "submit", disabled: cannotSubmit, children: "Handoff" }) })
1347
+ ] }) : /* @__PURE__ */ jsx14(EmptyState6, { title: "No assignees available" })
1348
+ }
1349
+ );
1350
+ }
1351
+ function formatAssigneeKind(kind) {
1352
+ return kind.charAt(0).toUpperCase() + kind.slice(1);
1353
+ }
1354
+
1355
+ // src/components/TaskProgressIndicator.tsx
1356
+ import clsx14 from "clsx";
1357
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
1358
+ var currentStepStatuses = /* @__PURE__ */ new Set(["active", "in-progress", "running"]);
1359
+ function TaskProgressIndicator({
1360
+ value,
1361
+ total = 100,
1362
+ steps,
1363
+ label = "Task progress",
1364
+ className,
1365
+ ...props
1366
+ }) {
1367
+ if (steps?.length) {
1368
+ return /* @__PURE__ */ jsx15(
1369
+ "div",
1370
+ {
1371
+ ...props,
1372
+ className: clsx14("eth-task-progress eth-task-progress--steps", className),
1373
+ "data-eth-component": "TaskProgressIndicator",
1374
+ children: /* @__PURE__ */ jsx15("ol", { className: "eth-task-progress__steps", "aria-label": label, children: steps.map((step, index) => {
1375
+ const statusLabel2 = labelForStatus(step.status);
1376
+ const isCurrentStep = currentStepStatuses.has(step.status);
1377
+ return /* @__PURE__ */ jsxs14(
1378
+ "li",
1379
+ {
1380
+ className: clsx14(
1381
+ "eth-task-progress__step",
1382
+ `eth-task-progress__step--${step.status}`
1383
+ ),
1384
+ "aria-current": isCurrentStep ? "step" : void 0,
1385
+ "data-status": step.status,
1386
+ children: [
1387
+ /* @__PURE__ */ jsxs14("span", { className: "eth-task-progress__step-track", "aria-hidden": "true", children: [
1388
+ /* @__PURE__ */ jsx15("span", { className: "eth-task-progress__circle" }),
1389
+ index < steps.length - 1 ? /* @__PURE__ */ jsx15("span", { className: "eth-task-progress__connector" }) : null
1390
+ ] }),
1391
+ /* @__PURE__ */ jsxs14("span", { className: "eth-task-progress__step-copy", children: [
1392
+ /* @__PURE__ */ jsx15("span", { className: "eth-task-progress__step-label", children: step.label }),
1393
+ /* @__PURE__ */ jsx15("span", { className: "eth-task-progress__step-status", children: statusLabel2 })
1394
+ ] })
1395
+ ]
1396
+ },
1397
+ step.id
1398
+ );
1399
+ }) })
1400
+ }
1401
+ );
1402
+ }
1403
+ const progressValue = clampProgress(value, total);
1404
+ return /* @__PURE__ */ jsxs14(
1405
+ "div",
1406
+ {
1407
+ ...props,
1408
+ className: clsx14("eth-task-progress eth-task-progress--bar", className),
1409
+ "data-eth-component": "TaskProgressIndicator",
1410
+ children: [
1411
+ /* @__PURE__ */ jsxs14("div", { className: "eth-task-progress__label", children: [
1412
+ /* @__PURE__ */ jsx15("span", { children: label }),
1413
+ /* @__PURE__ */ jsxs14("strong", { children: [
1414
+ progressValue,
1415
+ "%"
1416
+ ] })
1417
+ ] }),
1418
+ /* @__PURE__ */ jsx15("progress", { value: progressValue, max: 100, "aria-label": label })
1419
+ ]
1420
+ }
1421
+ );
1422
+ }
1423
+
1424
+ // src/components/TaskRetryPanel.tsx
1425
+ import * as React8 from "react";
1426
+ import clsx15 from "clsx";
1427
+ import { Button as Button3, FormField as FormField3, InlineNotification as InlineNotification4, Surface as Surface7, Textarea as Textarea3 } from "@echothink-ui/core";
1428
+ import { RetryIcon } from "@echothink-ui/icons";
1429
+ import { Fragment as Fragment2, jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
1430
+ var backoffLabels = {
1431
+ exponential: "Exponential",
1432
+ linear: "Linear"
1433
+ };
1434
+ function TaskRetryPanel({
1435
+ taskRef,
1436
+ failureSummary,
1437
+ retryPolicy,
1438
+ attemptCount = 0,
1439
+ onRetry,
1440
+ className,
1441
+ title = "Retry failed task",
1442
+ description,
1443
+ ...props
1444
+ }) {
1445
+ const [reason, setReason] = React8.useState("");
1446
+ const reasonId = React8.useId();
1447
+ const retriesExhausted = retryPolicy ? attemptCount >= retryPolicy.maxRetries : false;
1448
+ const attemptLabel = retryPolicy ? `${attemptCount} / ${retryPolicy.maxRetries}` : attemptCount;
1449
+ const backoffLabel = retryPolicy ? backoffLabels[retryPolicy.backoff] : "-";
1450
+ return /* @__PURE__ */ jsxs15(
1451
+ Surface7,
1452
+ {
1453
+ ...props,
1454
+ className: clsx15("eth-task-retry-panel", className),
1455
+ title,
1456
+ description,
1457
+ severity: "danger",
1458
+ "data-eth-component": "TaskRetryPanel",
1459
+ children: [
1460
+ /* @__PURE__ */ jsx16(InlineNotification4, { severity: "danger", title: "Task failed", children: failureSummary ?? "The task did not complete successfully." }),
1461
+ /* @__PURE__ */ jsxs15("dl", { className: "eth-task-retry-panel__policy", "aria-label": "Retry policy", children: [
1462
+ /* @__PURE__ */ jsxs15("div", { children: [
1463
+ /* @__PURE__ */ jsx16("dt", { children: "Task" }),
1464
+ /* @__PURE__ */ jsx16("dd", { children: taskRef ?? "-" })
1465
+ ] }),
1466
+ /* @__PURE__ */ jsxs15(
1467
+ "div",
1468
+ {
1469
+ className: clsx15(
1470
+ "eth-task-retry-panel__attempts",
1471
+ retriesExhausted && "eth-task-retry-panel__attempts--exhausted"
1472
+ ),
1473
+ children: [
1474
+ /* @__PURE__ */ jsx16("dt", { children: "Attempts" }),
1475
+ /* @__PURE__ */ jsxs15("dd", { children: [
1476
+ /* @__PURE__ */ jsx16("span", { children: attemptLabel }),
1477
+ retriesExhausted ? /* @__PURE__ */ jsx16("span", { className: "eth-task-retry-panel__policy-note", children: "Automatic retries exhausted" }) : null
1478
+ ] })
1479
+ ]
1480
+ }
1481
+ ),
1482
+ retryPolicy ? /* @__PURE__ */ jsxs15(Fragment2, { children: [
1483
+ /* @__PURE__ */ jsxs15("div", { children: [
1484
+ /* @__PURE__ */ jsx16("dt", { children: "Backoff" }),
1485
+ /* @__PURE__ */ jsx16("dd", { children: backoffLabel })
1486
+ ] }),
1487
+ /* @__PURE__ */ jsxs15("div", { children: [
1488
+ /* @__PURE__ */ jsx16("dt", { children: "Interval" }),
1489
+ /* @__PURE__ */ jsx16("dd", { children: formatDuration(retryPolicy.intervalMs) })
1490
+ ] })
1491
+ ] }) : null
1492
+ ] }),
1493
+ /* @__PURE__ */ jsxs15("div", { className: "eth-task-retry-panel__controls", children: [
1494
+ /* @__PURE__ */ jsx16(
1495
+ FormField3,
1496
+ {
1497
+ id: reasonId,
1498
+ label: "Retry reason",
1499
+ helperText: "Stored with retry audit metadata.",
1500
+ className: "eth-task-retry-panel__reason-field",
1501
+ children: /* @__PURE__ */ jsx16(
1502
+ Textarea3,
1503
+ {
1504
+ value: reason,
1505
+ rows: 3,
1506
+ placeholder: "Summarize what changed before retrying.",
1507
+ onChange: (event) => setReason(event.currentTarget.value)
1508
+ }
1509
+ )
1510
+ }
1511
+ ),
1512
+ /* @__PURE__ */ jsxs15("div", { className: "eth-task-retry-panel__actions", children: [
1513
+ /* @__PURE__ */ jsx16(
1514
+ Button3,
1515
+ {
1516
+ icon: /* @__PURE__ */ jsx16(RetryIcon, { size: 16 }),
1517
+ intent: "primary",
1518
+ onClick: () => onRetry?.(reason),
1519
+ children: "Retry task"
1520
+ }
1521
+ ),
1522
+ /* @__PURE__ */ jsx16("p", { children: "Creates a new attempt and records this reason in the task audit trail." })
1523
+ ] })
1524
+ ] })
1525
+ ]
1526
+ }
1527
+ );
1528
+ }
1529
+
1530
+ // src/components/TaskRunLog.tsx
1531
+ import * as React9 from "react";
1532
+ import clsx16 from "clsx";
1533
+ import { Badge as Badge10, Checkbox, EmptyState as EmptyState7, IconButton, Surface as Surface8 } from "@echothink-ui/core";
1534
+ import { PauseIcon, PlayIcon } from "@echothink-ui/icons";
1535
+ import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
1536
+ var logLevels = ["error", "warn", "info", "debug"];
1537
+ function TaskRunLog({
1538
+ entries = [],
1539
+ streaming,
1540
+ onPauseToggle,
1541
+ onFilter,
1542
+ levelFilter,
1543
+ windowSize = 200,
1544
+ className,
1545
+ title = "Task run log",
1546
+ description,
1547
+ ...props
1548
+ }) {
1549
+ const [internalFilter, setInternalFilter] = React9.useState(logLevels);
1550
+ const activeFilter = levelFilter ?? internalFilter;
1551
+ const counts = React9.useMemo(
1552
+ () => logLevels.reduce(
1553
+ (summary, level) => ({
1554
+ ...summary,
1555
+ [level]: entries.filter((entry) => entry.level === level).length
1556
+ }),
1557
+ {}
1558
+ ),
1559
+ [entries]
1560
+ );
1561
+ const visibleEntries = React9.useMemo(
1562
+ () => entries.filter((entry) => activeFilter.includes(entry.level)).slice(-windowSize),
1563
+ [activeFilter, entries, windowSize]
1564
+ );
1565
+ const toggleLevel = (level) => {
1566
+ onFilter?.(level);
1567
+ if (!levelFilter) {
1568
+ setInternalFilter(
1569
+ (current) => current.includes(level) ? current.filter((candidate) => candidate !== level) : [...current, level]
1570
+ );
1571
+ }
1572
+ };
1573
+ const streamLabel = streaming ? "Streaming" : "Paused";
1574
+ return /* @__PURE__ */ jsxs16(
1575
+ Surface8,
1576
+ {
1577
+ ...props,
1578
+ className: clsx16("eth-task-run-log", className),
1579
+ title,
1580
+ description,
1581
+ "data-eth-component": "TaskRunLog",
1582
+ children: [
1583
+ /* @__PURE__ */ jsxs16("div", { className: "eth-task-run-log__toolbar", role: "toolbar", "aria-label": "Task log controls", children: [
1584
+ /* @__PURE__ */ jsxs16("div", { className: "eth-task-run-log__stream-state", "aria-live": "polite", children: [
1585
+ onPauseToggle ? /* @__PURE__ */ jsx17(
1586
+ IconButton,
1587
+ {
1588
+ label: streaming ? "Pause log stream" : "Resume log stream",
1589
+ icon: streaming ? /* @__PURE__ */ jsx17(PauseIcon, { size: 16 }) : /* @__PURE__ */ jsx17(PlayIcon, { size: 16 }),
1590
+ intent: "ghost",
1591
+ density: "compact",
1592
+ onClick: onPauseToggle
1593
+ }
1594
+ ) : null,
1595
+ /* @__PURE__ */ jsx17(Badge10, { severity: streaming ? "success" : "neutral", children: streamLabel }),
1596
+ /* @__PURE__ */ jsxs16("span", { className: "eth-task-run-log__buffer-count", children: [
1597
+ visibleEntries.length,
1598
+ " of ",
1599
+ entries.length,
1600
+ " entries"
1601
+ ] })
1602
+ ] }),
1603
+ /* @__PURE__ */ jsxs16("fieldset", { className: "eth-task-run-log__filters", children: [
1604
+ /* @__PURE__ */ jsx17("legend", { children: "Levels" }),
1605
+ /* @__PURE__ */ jsx17("div", { className: "eth-task-run-log__filter-options", children: logLevels.map((level) => /* @__PURE__ */ jsx17(
1606
+ Checkbox,
1607
+ {
1608
+ label: level,
1609
+ checked: activeFilter.includes(level),
1610
+ onChange: () => toggleLevel(level)
1611
+ },
1612
+ level
1613
+ )) })
1614
+ ] })
1615
+ ] }),
1616
+ /* @__PURE__ */ jsxs16("dl", { className: "eth-task-run-log__summary", "aria-label": "Task log summary", children: [
1617
+ /* @__PURE__ */ jsxs16("div", { children: [
1618
+ /* @__PURE__ */ jsx17("dt", { children: "Buffered" }),
1619
+ /* @__PURE__ */ jsx17("dd", { children: entries.length })
1620
+ ] }),
1621
+ /* @__PURE__ */ jsxs16("div", { children: [
1622
+ /* @__PURE__ */ jsx17("dt", { children: "Visible" }),
1623
+ /* @__PURE__ */ jsx17("dd", { children: visibleEntries.length })
1624
+ ] }),
1625
+ /* @__PURE__ */ jsxs16("div", { children: [
1626
+ /* @__PURE__ */ jsx17("dt", { children: "Warnings" }),
1627
+ /* @__PURE__ */ jsx17("dd", { children: counts.warn })
1628
+ ] }),
1629
+ /* @__PURE__ */ jsxs16("div", { children: [
1630
+ /* @__PURE__ */ jsx17("dt", { children: "Errors" }),
1631
+ /* @__PURE__ */ jsx17("dd", { children: counts.error })
1632
+ ] })
1633
+ ] }),
1634
+ visibleEntries.length ? /* @__PURE__ */ jsxs16(
1635
+ "div",
1636
+ {
1637
+ className: "eth-task-run-log__viewport",
1638
+ role: "log",
1639
+ "aria-label": "Task execution log entries",
1640
+ "aria-live": streaming ? "polite" : "off",
1641
+ "aria-relevant": "additions text",
1642
+ children: [
1643
+ /* @__PURE__ */ jsxs16("div", { className: "eth-task-run-log__columns", "aria-hidden": "true", children: [
1644
+ /* @__PURE__ */ jsx17("span", { children: "Time" }),
1645
+ /* @__PURE__ */ jsx17("span", { children: "Level" }),
1646
+ /* @__PURE__ */ jsx17("span", { children: "Message" })
1647
+ ] }),
1648
+ visibleEntries.map((entry) => /* @__PURE__ */ jsxs16(
1649
+ "div",
1650
+ {
1651
+ className: clsx16("eth-task-run-log__entry", `eth-task-run-log__entry--${entry.level}`),
1652
+ children: [
1653
+ /* @__PURE__ */ jsx17("time", { className: "eth-task-run-log__time", dateTime: String(entry.timestamp), children: formatDateTime(entry.timestamp) }),
1654
+ /* @__PURE__ */ jsx17(
1655
+ "span",
1656
+ {
1657
+ className: clsx16(
1658
+ "eth-task-run-log__level",
1659
+ `eth-task-run-log__level--${entry.level}`
1660
+ ),
1661
+ children: entry.level.toUpperCase()
1662
+ }
1663
+ ),
1664
+ /* @__PURE__ */ jsx17(
1665
+ "span",
1666
+ {
1667
+ className: clsx16(
1668
+ "eth-task-run-log__message",
1669
+ entry.redacted && "eth-task-run-log__message--redacted"
1670
+ ),
1671
+ children: entry.redacted ? "[redacted]" : entry.message
1672
+ }
1673
+ )
1674
+ ]
1675
+ },
1676
+ entry.id
1677
+ ))
1678
+ ]
1679
+ }
1680
+ ) : /* @__PURE__ */ jsx17(EmptyState7, { title: "No log entries" })
1681
+ ]
1682
+ }
1683
+ );
1684
+ }
1685
+
1686
+ // src/components/TaskTable.tsx
1687
+ import * as React10 from "react";
1688
+ import clsx17 from "clsx";
1689
+ import {
1690
+ ActionGroup as ActionGroup4,
1691
+ Badge as Badge11,
1692
+ EmptyState as EmptyState8,
1693
+ Select,
1694
+ StatusDot as StatusDot2
1695
+ } from "@echothink-ui/core";
1696
+ import { BulkActionTable, DataTable } from "@echothink-ui/data";
1697
+ import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
1698
+ var statusFilterOrder = [
1699
+ "not-started",
1700
+ "queued",
1701
+ "in-progress",
1702
+ "running",
1703
+ "approval-required",
1704
+ "pending-approval",
1705
+ "blocked",
1706
+ "failed",
1707
+ "completed",
1708
+ "succeeded",
1709
+ "synced"
1710
+ ];
1711
+ var priorityLabels = {
1712
+ critical: "Critical",
1713
+ high: "High",
1714
+ low: "Low",
1715
+ medium: "Medium"
1716
+ };
1717
+ function taskRowsFromItems(items) {
1718
+ return items?.map((item) => ({
1719
+ id: item.id,
1720
+ title: String(item.label),
1721
+ status: item.status ?? "not-started"
1722
+ })) ?? [];
1723
+ }
1724
+ function taskTableLabel(title) {
1725
+ return typeof title === "string" && title.trim() ? `${title} table` : "Task table";
1726
+ }
1727
+ function priorityCell(priority) {
1728
+ if (!priority) return /* @__PURE__ */ jsx18("span", { className: "eth-task-table__empty-value", children: "-" });
1729
+ return /* @__PURE__ */ jsx18(
1730
+ Badge11,
1731
+ {
1732
+ severity: severityForPriority(priority),
1733
+ className: `eth-task-table__priority eth-task-table__priority--${priority}`,
1734
+ children: priorityLabels[priority]
1735
+ }
1736
+ );
1737
+ }
1738
+ function emptyCell(value) {
1739
+ if (value === void 0 || value === null || value === "") {
1740
+ return /* @__PURE__ */ jsx18("span", { className: "eth-task-table__empty-value", children: "-" });
1741
+ }
1742
+ return value;
1743
+ }
1744
+ function taskStatusOptions(rows, activeStatus) {
1745
+ const counts = /* @__PURE__ */ new Map();
1746
+ for (const task of rows) {
1747
+ counts.set(task.status, (counts.get(task.status) ?? 0) + 1);
1748
+ }
1749
+ const activeOperationalStatus = activeStatus === "all" ? null : activeStatus;
1750
+ const activeStatusWithoutRows = Boolean(
1751
+ activeOperationalStatus && !counts.has(activeOperationalStatus)
1752
+ );
1753
+ const extraStatuses = activeOperationalStatus && activeStatusWithoutRows && !statusFilterOrder.includes(activeOperationalStatus) ? [activeOperationalStatus] : [];
1754
+ const sortedStatuses = [
1755
+ ...statusFilterOrder.filter(
1756
+ (status) => counts.has(status) || activeStatusWithoutRows && status === activeOperationalStatus
1757
+ ),
1758
+ ...Array.from(counts.keys()).filter((status) => !statusFilterOrder.includes(status)),
1759
+ ...extraStatuses
1760
+ ];
1761
+ return [
1762
+ { value: "all", label: `All statuses (${rows.length})` },
1763
+ ...sortedStatuses.map((status) => ({
1764
+ value: status,
1765
+ label: `${labelForStatus(status)} (${counts.get(status) ?? 0})`
1766
+ }))
1767
+ ];
1768
+ }
1769
+ var defaultColumns = [
1770
+ { key: "id", header: "ID", width: "5rem", sortable: true },
1771
+ {
1772
+ key: "title",
1773
+ header: "Task",
1774
+ width: "18rem",
1775
+ sortable: true,
1776
+ render: (task) => /* @__PURE__ */ jsxs17("span", { className: "eth-task-table__task", children: [
1777
+ /* @__PURE__ */ jsx18("strong", { className: "eth-task-table__task-title", children: task.title }),
1778
+ task.description ? /* @__PURE__ */ jsx18("span", { className: "eth-task-table__task-description", children: task.description }) : null
1779
+ ] })
1780
+ },
1781
+ {
1782
+ key: "status",
1783
+ header: "Status",
1784
+ width: "12rem",
1785
+ sortable: true,
1786
+ render: (task) => /* @__PURE__ */ jsx18(TaskStatusBadge, { status: task.status })
1787
+ },
1788
+ {
1789
+ key: "priority",
1790
+ header: "Priority",
1791
+ width: "8rem",
1792
+ sortable: true,
1793
+ render: (task) => priorityCell(task.priority)
1794
+ },
1795
+ {
1796
+ key: "assignee",
1797
+ header: "Assignee",
1798
+ width: "9rem",
1799
+ render: (task) => emptyCell(task.assignee)
1800
+ },
1801
+ {
1802
+ key: "dueAt",
1803
+ header: "Due",
1804
+ width: "13rem",
1805
+ sortable: true,
1806
+ render: (task) => /* @__PURE__ */ jsx18("span", { className: task.dueAt ? "eth-task-table__date" : "eth-task-table__empty-value", children: formatDateTime(task.dueAt) })
1807
+ }
1808
+ ];
1809
+ function TaskTable({
1810
+ tasks,
1811
+ items,
1812
+ density = "default",
1813
+ selectable,
1814
+ selectedRows,
1815
+ onSelectionChange,
1816
+ rowActions,
1817
+ bulkActions,
1818
+ columns,
1819
+ showFilters,
1820
+ statusFilter,
1821
+ onStatusFilterChange,
1822
+ className,
1823
+ title,
1824
+ description,
1825
+ actions,
1826
+ subtitle: _subtitle,
1827
+ eyebrow: _eyebrow,
1828
+ status: _status,
1829
+ severity: _severity,
1830
+ loading: _loading,
1831
+ empty: _empty,
1832
+ error: _error,
1833
+ metadata: _metadata,
1834
+ footer: _footer,
1835
+ "aria-label": ariaLabel,
1836
+ ...sectionProps
1837
+ }) {
1838
+ const rows = tasks ?? taskRowsFromItems(items);
1839
+ const [internalStatusFilter, setInternalStatusFilter] = React10.useState("all");
1840
+ const effectiveStatusFilter = statusFilter ?? internalStatusFilter;
1841
+ const filterVisible = Boolean(showFilters) || statusFilter !== void 0 || Boolean(onStatusFilterChange);
1842
+ const filteredRows = filterVisible && effectiveStatusFilter !== "all" ? rows.filter((task) => task.status === effectiveStatusFilter) : rows;
1843
+ const statusOptions = React10.useMemo(
1844
+ () => taskStatusOptions(rows, effectiveStatusFilter),
1845
+ [effectiveStatusFilter, rows]
1846
+ );
1847
+ const resolvedColumns = columns ?? defaultColumns;
1848
+ const label = ariaLabel ?? taskTableLabel(title);
1849
+ const emptyTitle = rows.length && filterVisible ? "No matching tasks" : title ?? "No tasks";
1850
+ const emptyDescription = rows.length && filterVisible ? "No tasks match the selected status filter." : description ?? "There are no tasks to display.";
1851
+ const selectEnabled = selectable ?? Boolean(bulkActions?.length);
1852
+ const handleStatusFilterChange = (event) => {
1853
+ const nextStatus = event.currentTarget.value;
1854
+ if (statusFilter === void 0) setInternalStatusFilter(nextStatus);
1855
+ onStatusFilterChange?.(nextStatus);
1856
+ };
1857
+ const table = bulkActions?.length ? /* @__PURE__ */ jsx18(
1858
+ BulkActionTable,
1859
+ {
1860
+ className: clsx17("eth-task-table", className),
1861
+ rows: filteredRows,
1862
+ columns: resolvedColumns,
1863
+ rowKey: "id",
1864
+ density,
1865
+ selectable: selectEnabled,
1866
+ selectedRows,
1867
+ onSelectionChange,
1868
+ rowActions,
1869
+ bulkActions,
1870
+ emptyState: /* @__PURE__ */ jsx18(EmptyState8, { title: emptyTitle, description: emptyDescription }),
1871
+ "aria-label": label,
1872
+ "data-eth-component": "TaskTable"
1873
+ }
1874
+ ) : /* @__PURE__ */ jsx18(
1875
+ DataTable,
1876
+ {
1877
+ className: clsx17("eth-task-table", className),
1878
+ rows: filteredRows,
1879
+ columns: resolvedColumns,
1880
+ rowKey: "id",
1881
+ density,
1882
+ selectable,
1883
+ selectedRows,
1884
+ onSelectionChange,
1885
+ rowActions,
1886
+ emptyState: /* @__PURE__ */ jsx18(EmptyState8, { title: emptyTitle, description: emptyDescription }),
1887
+ "aria-label": label,
1888
+ "data-eth-component": "TaskTable"
1889
+ }
1890
+ );
1891
+ const hasHeader = Boolean(title || description || actions?.length);
1892
+ if (!hasHeader && !filterVisible) return table;
1893
+ return /* @__PURE__ */ jsxs17(
1894
+ "section",
1895
+ {
1896
+ ...sectionProps,
1897
+ className: "eth-task-table-shell",
1898
+ "aria-label": typeof title === "string" ? title : "Tasks",
1899
+ children: [
1900
+ hasHeader ? /* @__PURE__ */ jsxs17("header", { className: "eth-task-table-shell__header", children: [
1901
+ /* @__PURE__ */ jsxs17("div", { className: "eth-task-table-shell__heading", children: [
1902
+ title ? /* @__PURE__ */ jsx18("h2", { children: title }) : null,
1903
+ description ? /* @__PURE__ */ jsx18("p", { children: description }) : null
1904
+ ] }),
1905
+ /* @__PURE__ */ jsx18(ActionGroup4, { actions })
1906
+ ] }) : null,
1907
+ filterVisible ? /* @__PURE__ */ jsxs17("div", { className: "eth-task-table-shell__toolbar", "aria-label": "Task table filters", children: [
1908
+ /* @__PURE__ */ jsx18("div", { className: "eth-task-table-shell__filters", children: /* @__PURE__ */ jsx18(
1909
+ Select,
1910
+ {
1911
+ className: "eth-task-table-shell__filter",
1912
+ density: "compact",
1913
+ labelText: "Status",
1914
+ options: statusOptions,
1915
+ value: effectiveStatusFilter,
1916
+ onChange: handleStatusFilterChange
1917
+ }
1918
+ ) }),
1919
+ /* @__PURE__ */ jsx18("span", { className: "eth-task-table-shell__summary", children: filteredRows.length === rows.length ? `${rows.length} ${rows.length === 1 ? "task" : "tasks"} shown` : `${filteredRows.length} of ${rows.length} tasks shown` })
1920
+ ] }) : null,
1921
+ table
1922
+ ]
1923
+ }
1924
+ );
1925
+ }
1926
+
1927
+ // src/components/TaskWaveDAG.tsx
1928
+ import * as React11 from "react";
1929
+ import clsx18 from "clsx";
1930
+ import { EmptyState as EmptyState9, Surface as Surface9 } from "@echothink-ui/core";
1931
+ import { Fragment as Fragment3, jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
1932
+ var rankSpacing = DAG_NODE_WIDTH + 76;
1933
+ var rowSpacing = DAG_NODE_HEIGHT + 48;
1934
+ var originX = DAG_NODE_WIDTH / 2 + 28;
1935
+ var originY = DAG_NODE_HEIGHT / 2 + 48;
1936
+ var framePaddingX = 32;
1937
+ var framePaddingY = 28;
1938
+ function layoutNodes(nodes, edges, layout) {
1939
+ const ids = new Set(nodes.map((node) => node.id));
1940
+ const ranks = new Map(nodes.map((node) => [node.id, 0]));
1941
+ const validEdges = edges.filter((edge) => ids.has(edge.from) && ids.has(edge.to));
1942
+ for (let pass = 0; pass < nodes.length; pass += 1) {
1943
+ for (const edge of validEdges) {
1944
+ const fromRank = ranks.get(edge.from) ?? 0;
1945
+ const toRank = ranks.get(edge.to) ?? 0;
1946
+ if (toRank < fromRank + 1) ranks.set(edge.to, fromRank + 1);
1947
+ }
1948
+ }
1949
+ const groups = /* @__PURE__ */ new Map();
1950
+ for (const node of nodes) {
1951
+ const rank = ranks.get(node.id) ?? 0;
1952
+ groups.set(rank, [...groups.get(rank) ?? [], node]);
1953
+ }
1954
+ return nodes.map((node) => {
1955
+ if (typeof node.x === "number" && typeof node.y === "number") {
1956
+ return { ...node, x: node.x, y: node.y };
1957
+ }
1958
+ const rank = ranks.get(node.id) ?? 0;
1959
+ const group = groups.get(rank) ?? [];
1960
+ const index = group.findIndex((candidate) => candidate.id === node.id);
1961
+ const spread = Math.max(0, index) * rowSpacing + originY;
1962
+ const ranked = rank * rankSpacing + originX;
1963
+ return layout === "lr" ? { ...node, x: ranked, y: spread } : { ...node, x: spread, y: ranked };
1964
+ });
1965
+ }
1966
+ function frameFor(nodes) {
1967
+ if (!nodes.length) {
1968
+ return { x: 0, y: 0, width: 640, height: 180, viewBox: "0 0 640 180" };
1969
+ }
1970
+ const minX = Math.min(...nodes.map((node) => node.x - DAG_NODE_WIDTH / 2)) - framePaddingX;
1971
+ const minY = Math.min(...nodes.map((node) => node.y - DAG_NODE_HEIGHT / 2)) - framePaddingY;
1972
+ const maxX = Math.max(...nodes.map((node) => node.x + DAG_NODE_WIDTH / 2)) + framePaddingX;
1973
+ const maxY = Math.max(...nodes.map((node) => node.y + DAG_NODE_HEIGHT / 2)) + framePaddingY;
1974
+ const width = Math.max(360, maxX - minX);
1975
+ const height = Math.max(120, maxY - minY);
1976
+ return { x: minX, y: minY, width, height, viewBox: `${minX} ${minY} ${width} ${height}` };
1977
+ }
1978
+ function edgeEndpoints(from, to, layout) {
1979
+ if (layout === "lr") {
1980
+ return {
1981
+ from: { x: from.x + DAG_NODE_WIDTH / 2, y: from.y },
1982
+ to: { x: to.x - DAG_NODE_WIDTH / 2, y: to.y }
1983
+ };
1984
+ }
1985
+ return {
1986
+ from: { x: from.x, y: from.y + DAG_NODE_HEIGHT / 2 },
1987
+ to: { x: to.x, y: to.y - DAG_NODE_HEIGHT / 2 }
1988
+ };
1989
+ }
1990
+ function TaskWaveDAG({
1991
+ nodes = [],
1992
+ edges = [],
1993
+ layout = "lr",
1994
+ onNodeSelect,
1995
+ selectedNodeId,
1996
+ className,
1997
+ title,
1998
+ description,
1999
+ ...props
2000
+ }) {
2001
+ const positionedNodes = React11.useMemo(
2002
+ () => layoutNodes(nodes, edges, layout),
2003
+ [nodes, edges, layout]
2004
+ );
2005
+ const nodeMap = React11.useMemo(
2006
+ () => new Map(positionedNodes.map((node) => [node.id, node])),
2007
+ [positionedNodes]
2008
+ );
2009
+ const visibleStatuses = React11.useMemo(
2010
+ () => Array.from(new Set(positionedNodes.map((node) => node.status))),
2011
+ [positionedNodes]
2012
+ );
2013
+ const graphFrame = React11.useMemo(() => frameFor(positionedNodes), [positionedNodes]);
2014
+ const graphTitleId = React11.useId();
2015
+ const graphDescriptionId = React11.useId();
2016
+ const graphTitle = typeof title === "string" && title.trim() ? title : "Task wave dependency graph";
2017
+ const graphDescription = `${positionedNodes.length} nodes and ${edges.length} dependencies. Statuses: ${visibleStatuses.map((status) => status.replace(/-/g, " ")).join(", ") || "none"}.`;
2018
+ return /* @__PURE__ */ jsx19(
2019
+ Surface9,
2020
+ {
2021
+ ...props,
2022
+ className: clsx18("eth-task-wave-dag", className),
2023
+ title: title ?? "Wave DAG",
2024
+ description,
2025
+ "data-eth-component": "TaskWaveDAG",
2026
+ children: positionedNodes.length ? /* @__PURE__ */ jsxs18(Fragment3, { children: [
2027
+ /* @__PURE__ */ jsx19("div", { className: "eth-task-wave-dag__viewport", children: /* @__PURE__ */ jsxs18(
2028
+ "svg",
2029
+ {
2030
+ className: "eth-dag",
2031
+ role: "img",
2032
+ "aria-labelledby": graphTitleId,
2033
+ "aria-describedby": graphDescriptionId,
2034
+ viewBox: graphFrame.viewBox,
2035
+ children: [
2036
+ /* @__PURE__ */ jsx19("title", { id: graphTitleId, children: graphTitle }),
2037
+ /* @__PURE__ */ jsx19("desc", { id: graphDescriptionId, children: graphDescription }),
2038
+ /* @__PURE__ */ jsx19(
2039
+ "rect",
2040
+ {
2041
+ className: "eth-dag__canvas",
2042
+ x: graphFrame.x,
2043
+ y: graphFrame.y,
2044
+ width: graphFrame.width,
2045
+ height: graphFrame.height,
2046
+ "aria-hidden": "true"
2047
+ }
2048
+ ),
2049
+ edges.map((edge, index) => {
2050
+ const from = nodeMap.get(edge.from);
2051
+ const to = nodeMap.get(edge.to);
2052
+ if (!from || !to) return null;
2053
+ const endpoints = edgeEndpoints(from, to, layout);
2054
+ return /* @__PURE__ */ jsx19(
2055
+ DAGEdge,
2056
+ {
2057
+ from: endpoints.from,
2058
+ to: endpoints.to,
2059
+ status: edge.status ?? to.status,
2060
+ label: edge.label
2061
+ },
2062
+ `${edge.from}-${edge.to}-${index}`
2063
+ );
2064
+ }),
2065
+ positionedNodes.map((node) => /* @__PURE__ */ jsx19(
2066
+ DAGNode,
2067
+ {
2068
+ node,
2069
+ selected: node.id === selectedNodeId,
2070
+ onSelect: onNodeSelect
2071
+ },
2072
+ node.id
2073
+ ))
2074
+ ]
2075
+ }
2076
+ ) }),
2077
+ /* @__PURE__ */ jsx19(DAGLegend, { statuses: visibleStatuses.length ? visibleStatuses : void 0 }),
2078
+ /* @__PURE__ */ jsx19("ol", { className: "eth-task-wave-dag__fallback", children: positionedNodes.map((node) => /* @__PURE__ */ jsxs18("li", { children: [
2079
+ node.label,
2080
+ ", ",
2081
+ node.status.replace(/-/g, " ")
2082
+ ] }, node.id)) })
2083
+ ] }) : /* @__PURE__ */ jsx19(EmptyState9, { title: "No DAG nodes", description: "Add nodes and edges to render a wave graph." })
2084
+ }
2085
+ );
2086
+ }
2087
+
2088
+ // src/components/TaskWaveHeader.tsx
2089
+ import clsx19 from "clsx";
2090
+ import { Badge as Badge12, Surface as Surface10 } from "@echothink-ui/core";
2091
+ import { Fragment as Fragment4, jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
2092
+ var statusOrder = [
2093
+ "queued",
2094
+ "running",
2095
+ "in-progress",
2096
+ "pending-approval",
2097
+ "approval-required",
2098
+ "blocked",
2099
+ "failed",
2100
+ "completed",
2101
+ "succeeded"
2102
+ ];
2103
+ function TaskWaveHeader({
2104
+ wave,
2105
+ actions,
2106
+ className,
2107
+ title,
2108
+ description,
2109
+ ...props
2110
+ }) {
2111
+ const progress = waveProgress(wave);
2112
+ return /* @__PURE__ */ jsx20(
2113
+ Surface10,
2114
+ {
2115
+ ...props,
2116
+ className: clsx19("eth-task-wave-header", className),
2117
+ title: wave?.label ?? title ?? "Task wave",
2118
+ subtitle: wave?.id,
2119
+ description,
2120
+ actions,
2121
+ "data-eth-component": "TaskWaveHeader",
2122
+ children: wave ? /* @__PURE__ */ jsxs19(Fragment4, { children: [
2123
+ /* @__PURE__ */ jsxs19("div", { className: "eth-task-wave-header__summary", children: [
2124
+ /* @__PURE__ */ jsxs19("strong", { children: [
2125
+ wave.totalTasks,
2126
+ " tasks"
2127
+ ] }),
2128
+ wave.startedAt ? /* @__PURE__ */ jsxs19("span", { children: [
2129
+ "Started ",
2130
+ formatDateTime(wave.startedAt)
2131
+ ] }) : null,
2132
+ wave.eta ? /* @__PURE__ */ jsxs19("span", { children: [
2133
+ "ETA ",
2134
+ formatDateTime(wave.eta)
2135
+ ] }) : null
2136
+ ] }),
2137
+ /* @__PURE__ */ jsx20(TaskProgressIndicator, { value: progress, label: "Wave progress" }),
2138
+ /* @__PURE__ */ jsx20("div", { className: "eth-task-wave-header__counts", "aria-label": "Task status counts", children: statusOrder.filter((status) => wave.statuses[status]).map((status) => {
2139
+ const label = labelForStatus(status);
2140
+ const count = wave.statuses[status] ?? 0;
2141
+ return /* @__PURE__ */ jsxs19(
2142
+ Badge12,
2143
+ {
2144
+ severity: severityForStatus(status),
2145
+ className: clsx19(
2146
+ "eth-task-wave-header__count",
2147
+ `eth-task-wave-header__count--${status}`
2148
+ ),
2149
+ "aria-label": `${label}: ${count}`,
2150
+ children: [
2151
+ label,
2152
+ ": ",
2153
+ /* @__PURE__ */ jsx20("span", { className: "eth-task-wave-header__count-value", children: count })
2154
+ ]
2155
+ },
2156
+ status
2157
+ );
2158
+ }) })
2159
+ ] }) : /* @__PURE__ */ jsx20(TaskProgressIndicator, { value: 0, label: "Wave progress" })
2160
+ }
2161
+ );
2162
+ }
2163
+
2164
+ // src/components/TaskWaveTable.tsx
2165
+ import * as React12 from "react";
2166
+ import clsx20 from "clsx";
2167
+ import { Badge as Badge13, EmptyState as EmptyState10, StatusDot as StatusDot3, Surface as Surface11 } from "@echothink-ui/core";
2168
+ import { DataTable as DataTable2 } from "@echothink-ui/data";
2169
+ import { jsx as jsx21 } from "react/jsx-runtime";
2170
+ function taskRowsFromItems2(items) {
2171
+ return items?.map((item, index) => ({
2172
+ id: item.id,
2173
+ title: String(item.label),
2174
+ status: item.status ?? "not-started",
2175
+ order: index + 1
2176
+ })) ?? [];
2177
+ }
2178
+ function dependencyCount(task) {
2179
+ if (typeof task.dependencyCount === "number") return task.dependencyCount;
2180
+ if (task.dependencyIds?.length) return task.dependencyIds.length;
2181
+ return task.dependencies?.length ?? 0;
2182
+ }
2183
+ function EmptyCell() {
2184
+ return /* @__PURE__ */ jsx21("span", { className: "eth-task-wave-table__empty", children: "-" });
2185
+ }
2186
+ function TaskWaveTable({
2187
+ wave,
2188
+ tasks,
2189
+ items,
2190
+ onTaskSelect,
2191
+ rowActions,
2192
+ className,
2193
+ title,
2194
+ description,
2195
+ ..._surfaceProps
2196
+ }) {
2197
+ const rows = tasks ?? taskRowsFromItems2(items);
2198
+ const columns = React12.useMemo(
2199
+ () => [
2200
+ {
2201
+ key: "order",
2202
+ header: "Order",
2203
+ width: "4.5rem",
2204
+ render: (task, index) => /* @__PURE__ */ jsx21("span", { className: "eth-task-wave-table__order", children: task.order ?? index + 1 })
2205
+ },
2206
+ {
2207
+ key: "title",
2208
+ header: "Task",
2209
+ width: "22rem",
2210
+ render: (task) => onTaskSelect ? /* @__PURE__ */ jsx21(
2211
+ "button",
2212
+ {
2213
+ className: "eth-task-wave-table__task-button",
2214
+ type: "button",
2215
+ onClick: () => onTaskSelect(task),
2216
+ children: /* @__PURE__ */ jsx21("span", { className: "eth-task-wave-table__task-title", children: task.title })
2217
+ }
2218
+ ) : /* @__PURE__ */ jsx21("span", { className: "eth-task-wave-table__task-title", children: task.title })
2219
+ },
2220
+ {
2221
+ key: "status",
2222
+ header: "Status",
2223
+ width: "12rem",
2224
+ render: (task) => /* @__PURE__ */ jsx21(
2225
+ StatusDot3,
2226
+ {
2227
+ className: "eth-task-wave-table__status",
2228
+ status: task.status,
2229
+ label: labelForStatus(task.status)
2230
+ }
2231
+ )
2232
+ },
2233
+ {
2234
+ key: "assignee",
2235
+ header: "Assignee",
2236
+ width: "8rem",
2237
+ render: (task) => task.assignee ? /* @__PURE__ */ jsx21("span", { className: "eth-task-wave-table__assignee", children: task.assignee }) : /* @__PURE__ */ jsx21(EmptyCell, {})
2238
+ },
2239
+ {
2240
+ key: "dependencies",
2241
+ header: "Dependencies",
2242
+ width: "7rem",
2243
+ align: "center",
2244
+ render: (task) => {
2245
+ const count = dependencyCount(task);
2246
+ return /* @__PURE__ */ jsx21(
2247
+ Badge13,
2248
+ {
2249
+ className: "eth-task-wave-table__dependency-count",
2250
+ severity: count > 0 ? "info" : "neutral",
2251
+ children: count
2252
+ }
2253
+ );
2254
+ }
2255
+ },
2256
+ {
2257
+ key: "durationMs",
2258
+ header: "Duration",
2259
+ width: "7rem",
2260
+ render: (task) => /* @__PURE__ */ jsx21(
2261
+ "span",
2262
+ {
2263
+ className: clsx20(
2264
+ "eth-task-wave-table__duration",
2265
+ task.durationMs == null && "eth-task-wave-table__empty"
2266
+ ),
2267
+ children: formatDuration(task.durationMs)
2268
+ }
2269
+ )
2270
+ }
2271
+ ],
2272
+ [onTaskSelect]
2273
+ );
2274
+ return /* @__PURE__ */ jsx21(
2275
+ Surface11,
2276
+ {
2277
+ className: clsx20("eth-task-wave-table", className),
2278
+ title: wave?.label ?? title ?? "Task wave",
2279
+ subtitle: wave?.id,
2280
+ description,
2281
+ "data-eth-component": "TaskWaveTable",
2282
+ children: /* @__PURE__ */ jsx21(
2283
+ DataTable2,
2284
+ {
2285
+ "aria-label": typeof (wave?.label ?? title) === "string" ? `${wave?.label ?? title} tasks` : "Wave tasks",
2286
+ className: "eth-task-wave-table__table",
2287
+ rows,
2288
+ columns,
2289
+ rowKey: "id",
2290
+ density: "compact",
2291
+ rowActions,
2292
+ emptyState: /* @__PURE__ */ jsx21(EmptyState10, { title: "No wave tasks" })
2293
+ }
2294
+ )
2295
+ }
2296
+ );
2297
+ }
2298
+
2299
+ // src/components/MobileTaskShell.tsx
2300
+ import clsx21 from "clsx";
2301
+ import { jsx as jsx22, jsxs as jsxs20 } from "react/jsx-runtime";
2302
+ function MobileTaskShell({
2303
+ title,
2304
+ subtitle,
2305
+ progress,
2306
+ main,
2307
+ footer,
2308
+ identity,
2309
+ children,
2310
+ className
2311
+ }) {
2312
+ const content = main ?? children;
2313
+ const hasHeader = title || subtitle || progress;
2314
+ const hasFooter = footer || identity;
2315
+ return /* @__PURE__ */ jsxs20(
2316
+ "section",
2317
+ {
2318
+ className: clsx21("eth-mobile-task-shell", className),
2319
+ "data-eth-component": "MobileTaskShell",
2320
+ children: [
2321
+ hasHeader ? /* @__PURE__ */ jsxs20("header", { className: "eth-mobile-task-shell__header", children: [
2322
+ /* @__PURE__ */ jsxs20("div", { className: "eth-mobile-task-shell__title-block", children: [
2323
+ title ? /* @__PURE__ */ jsx22("h1", { className: "eth-mobile-task-shell__title", children: title }) : null,
2324
+ subtitle ? /* @__PURE__ */ jsx22("p", { className: "eth-mobile-task-shell__subtitle", children: subtitle }) : null
2325
+ ] }),
2326
+ progress ? /* @__PURE__ */ jsx22("div", { className: "eth-mobile-task-shell__progress", children: progress }) : null
2327
+ ] }) : null,
2328
+ /* @__PURE__ */ jsx22("main", { className: "eth-mobile-task-shell__main", children: content }),
2329
+ hasFooter ? /* @__PURE__ */ jsxs20("footer", { className: "eth-mobile-task-shell__footer", children: [
2330
+ footer ? /* @__PURE__ */ jsx22("div", { className: "eth-mobile-task-shell__footer-main", children: footer }) : null,
2331
+ identity ? /* @__PURE__ */ jsx22("div", { className: "eth-mobile-task-shell__identity", children: identity }) : null
2332
+ ] }) : null
2333
+ ]
2334
+ }
2335
+ );
2336
+ }
2337
+
2338
+ // src/index.tsx
2339
+ var TaskComponentNames = [
2340
+ "TaskCard",
2341
+ "TaskTable",
2342
+ "TaskDetailPanel",
2343
+ "TaskStatusBadge",
2344
+ "TaskProgressIndicator",
2345
+ "TaskDependencyList",
2346
+ "TaskTimeline",
2347
+ "TaskWaveTable",
2348
+ "TaskWaveHeader",
2349
+ "TaskWaveDAG",
2350
+ "DAGNode",
2351
+ "DAGEdge",
2352
+ "DAGLegend",
2353
+ "TaskApprovalPanel",
2354
+ "DecisionRequiredPanel",
2355
+ "BackendThinkingChain",
2356
+ "HumanInterventionPanel",
2357
+ "BlockingReasonPanel",
2358
+ "TaskHandoffPanel",
2359
+ "TaskRunLog",
2360
+ "TaskRetryPanel",
2361
+ "MobileTaskShell"
2362
+ ];
2363
+ export {
2364
+ BackendThinkingChain,
2365
+ BlockingReasonPanel,
2366
+ DAGEdge,
2367
+ DAGLegend,
2368
+ DAGNode,
2369
+ DecisionRequiredPanel,
2370
+ HumanInterventionPanel,
2371
+ MobileTaskShell,
2372
+ TaskApprovalPanel,
2373
+ TaskCard,
2374
+ TaskComponentNames,
2375
+ TaskDependencyList,
2376
+ TaskDetailPanel,
2377
+ TaskHandoffPanel,
2378
+ TaskProgressIndicator,
2379
+ TaskRetryPanel,
2380
+ TaskRunLog,
2381
+ TaskStatusBadge,
2382
+ TaskTable,
2383
+ TaskTimeline,
2384
+ TaskWaveDAG,
2385
+ TaskWaveHeader,
2386
+ TaskWaveTable
2387
+ };
2388
+ //# sourceMappingURL=index.js.map