@revenexx/studio-integrations 0.1.4 → 0.3.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 (188) hide show
  1. package/README.md +31 -0
  2. package/dist/module.d.mts +2 -2
  3. package/dist/module.json +1 -1
  4. package/dist/runtime/components/Integrations/CredentialCreateDialog.d.vue.ts +13 -0
  5. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +279 -0
  6. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue.d.ts +13 -0
  7. package/dist/runtime/components/Integrations/CredentialDetailDialog.d.vue.ts +20 -0
  8. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +342 -0
  9. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue.d.ts +20 -0
  10. package/dist/runtime/components/Integrations/CredentialLogo.d.vue.ts +14 -0
  11. package/dist/runtime/components/Integrations/CredentialLogo.vue +37 -0
  12. package/dist/runtime/components/Integrations/CredentialLogo.vue.d.ts +14 -0
  13. package/dist/runtime/components/Integrations/CredentialRevenexxSource.d.vue.ts +12 -0
  14. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +364 -0
  15. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue.d.ts +12 -0
  16. package/dist/runtime/components/Integrations/CredentialTypePicker.d.vue.ts +15 -0
  17. package/dist/runtime/components/Integrations/CredentialTypePicker.vue +71 -0
  18. package/dist/runtime/components/Integrations/CredentialTypePicker.vue.d.ts +15 -0
  19. package/dist/runtime/components/Integrations/DeadLettersPanel.d.vue.ts +5 -0
  20. package/dist/runtime/components/Integrations/DeadLettersPanel.vue +151 -0
  21. package/dist/runtime/components/Integrations/DeadLettersPanel.vue.d.ts +5 -0
  22. package/dist/runtime/components/Integrations/EditorTip.d.vue.ts +17 -0
  23. package/dist/runtime/components/Integrations/EditorTip.vue +46 -0
  24. package/dist/runtime/components/Integrations/EditorTip.vue.d.ts +17 -0
  25. package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +9 -0
  26. package/dist/runtime/components/Integrations/EventTypeCombobox.vue +64 -0
  27. package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +9 -0
  28. package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +19 -0
  29. package/dist/runtime/components/Integrations/ExpressionInput.vue +236 -0
  30. package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +19 -0
  31. package/dist/runtime/components/Integrations/GroupFrameView.d.vue.ts +28 -0
  32. package/dist/runtime/components/Integrations/GroupFrameView.vue +222 -0
  33. package/dist/runtime/components/Integrations/GroupFrameView.vue.d.ts +28 -0
  34. package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +14 -0
  35. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +184 -0
  36. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +14 -0
  37. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.d.vue.ts +16 -0
  38. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +102 -0
  39. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue.d.ts +16 -0
  40. package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +23 -0
  41. package/dist/runtime/components/Integrations/NodeConfigField.vue +587 -0
  42. package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +23 -0
  43. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.d.vue.ts +14 -0
  44. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue +80 -0
  45. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue.d.ts +14 -0
  46. package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +22 -3
  47. package/dist/runtime/components/Integrations/NodeInspector.vue +1074 -294
  48. package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +22 -3
  49. package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +9 -0
  50. package/dist/runtime/components/Integrations/NodeLogo.vue +38 -0
  51. package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +9 -0
  52. package/dist/runtime/components/Integrations/NodePalette.d.vue.ts +22 -1
  53. package/dist/runtime/components/Integrations/NodePalette.vue +411 -90
  54. package/dist/runtime/components/Integrations/NodePalette.vue.d.ts +22 -1
  55. package/dist/runtime/components/Integrations/QueryBuilder.d.vue.ts +9 -0
  56. package/dist/runtime/components/Integrations/QueryBuilder.vue +239 -0
  57. package/dist/runtime/components/Integrations/QueryBuilder.vue.d.ts +9 -0
  58. package/dist/runtime/components/Integrations/{RunDetailDialog.d.vue.ts → RunActionsMenu.d.vue.ts} +3 -4
  59. package/dist/runtime/components/Integrations/RunActionsMenu.vue +86 -0
  60. package/dist/runtime/components/Integrations/{RunDetailDialog.vue.d.ts → RunActionsMenu.vue.d.ts} +3 -4
  61. package/dist/runtime/components/Integrations/RunDetailPanel.d.vue.ts +14 -0
  62. package/dist/runtime/components/Integrations/RunDetailPanel.vue +777 -0
  63. package/dist/runtime/components/Integrations/RunDetailPanel.vue.d.ts +14 -0
  64. package/dist/runtime/components/Integrations/RunPayloadInput.d.vue.ts +13 -0
  65. package/dist/runtime/components/Integrations/RunPayloadInput.vue +175 -0
  66. package/dist/runtime/components/Integrations/RunPayloadInput.vue.d.ts +13 -0
  67. package/dist/runtime/components/Integrations/RunResponseModal.d.vue.ts +27 -0
  68. package/dist/runtime/components/Integrations/RunResponseModal.vue +84 -0
  69. package/dist/runtime/components/Integrations/RunResponseModal.vue.d.ts +27 -0
  70. package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +21 -0
  71. package/dist/runtime/components/Integrations/RunsTable.vue +114 -0
  72. package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +21 -0
  73. package/dist/runtime/components/Integrations/SftpAccountImportDialog.d.vue.ts +11 -0
  74. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +91 -0
  75. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue.d.ts +11 -0
  76. package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +28 -0
  77. package/dist/runtime/components/Integrations/StickyNoteView.vue +208 -0
  78. package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +28 -0
  79. package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +29 -0
  80. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +201 -0
  81. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +29 -0
  82. package/dist/runtime/components/Integrations/WorkflowContextMenu.d.vue.ts +32 -0
  83. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +138 -0
  84. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue.d.ts +32 -0
  85. package/dist/runtime/components/Integrations/WorkflowCreateDialog.d.vue.ts +9 -0
  86. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +322 -0
  87. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue.d.ts +9 -0
  88. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.d.vue.ts +18 -0
  89. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +63 -0
  90. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue.d.ts +18 -0
  91. package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +12 -1
  92. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +68 -6
  93. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +12 -1
  94. package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +8 -1
  95. package/dist/runtime/components/Integrations/WorkflowEditor.vue +1424 -144
  96. package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +8 -1
  97. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.d.vue.ts +12 -0
  98. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +417 -0
  99. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +12 -0
  100. package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +10 -0
  101. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +89 -0
  102. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +10 -0
  103. package/dist/runtime/components/Integrations/WorkflowIconView.d.vue.ts +10 -0
  104. package/dist/runtime/components/Integrations/WorkflowIconView.vue +35 -0
  105. package/dist/runtime/components/Integrations/WorkflowIconView.vue.d.ts +10 -0
  106. package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +5 -1
  107. package/dist/runtime/components/Integrations/WorkflowNodeView.vue +114 -84
  108. package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +5 -1
  109. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.d.vue.ts +12 -0
  110. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +654 -0
  111. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue.d.ts +12 -0
  112. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +36 -0
  113. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +136 -0
  114. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +36 -0
  115. package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +14 -0
  116. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +63 -0
  117. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +14 -0
  118. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +35 -15
  119. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.d.vue.ts +13 -0
  120. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +265 -0
  121. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue.d.ts +13 -0
  122. package/dist/runtime/components/Integrations/WorkflowsTable.d.vue.ts +4 -1
  123. package/dist/runtime/components/Integrations/WorkflowsTable.vue +122 -39
  124. package/dist/runtime/components/Integrations/WorkflowsTable.vue.d.ts +4 -1
  125. package/dist/runtime/components/Integrations/workflowLayout.d.ts +5 -0
  126. package/dist/runtime/components/Integrations/workflowLayout.js +5 -3
  127. package/dist/runtime/composables/integrationsApi.d.ts +6 -7
  128. package/dist/runtime/composables/integrationsApi.js +6 -12
  129. package/dist/runtime/composables/useCanvasShortcuts.d.ts +37 -0
  130. package/dist/runtime/composables/useCanvasShortcuts.js +62 -0
  131. package/dist/runtime/composables/useCredentialTypes.js +2 -1
  132. package/dist/runtime/composables/useIntegrationsAudit.js +2 -1
  133. package/dist/runtime/composables/useIntegrationsCredentials.js +2 -1
  134. package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +22 -0
  135. package/dist/runtime/composables/useIntegrationsDeadLetters.js +50 -0
  136. package/dist/runtime/composables/useIntegrationsNodes.d.ts +4 -0
  137. package/dist/runtime/composables/useIntegrationsNodes.js +8 -4
  138. package/dist/runtime/composables/useIntegrationsRevisions.d.ts +40 -0
  139. package/dist/runtime/composables/useIntegrationsRevisions.js +52 -0
  140. package/dist/runtime/composables/useIntegrationsRuns.d.ts +21 -1
  141. package/dist/runtime/composables/useIntegrationsRuns.js +94 -5
  142. package/dist/runtime/composables/useIntegrationsSecrets.js +2 -1
  143. package/dist/runtime/composables/useIntegrationsTemplates.d.ts +1 -0
  144. package/dist/runtime/composables/useIntegrationsTemplates.js +34 -5
  145. package/dist/runtime/composables/useIntegrationsTriggers.js +2 -1
  146. package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +1 -0
  147. package/dist/runtime/composables/useIntegrationsWorkflows.js +59 -2
  148. package/dist/runtime/composables/useMiniMapLayout.d.ts +34 -0
  149. package/dist/runtime/composables/useMiniMapLayout.js +119 -0
  150. package/dist/runtime/composables/useNodeConfigResolve.d.ts +43 -0
  151. package/dist/runtime/composables/useNodeConfigResolve.js +124 -0
  152. package/dist/runtime/composables/useNodeDrag.d.ts +22 -0
  153. package/dist/runtime/composables/useNodeDrag.js +10 -0
  154. package/dist/runtime/composables/usePaletteLayout.d.ts +41 -0
  155. package/dist/runtime/composables/usePaletteLayout.js +113 -0
  156. package/dist/runtime/data/expressionEditor.d.ts +22 -0
  157. package/dist/runtime/data/expressionEditor.js +40 -0
  158. package/dist/runtime/data/integrationNodeCatalog.js +8 -8
  159. package/dist/runtime/data/nodeInputData.d.ts +37 -0
  160. package/dist/runtime/data/nodeInputData.js +62 -0
  161. package/dist/runtime/data/nodeRunData.d.ts +56 -0
  162. package/dist/runtime/data/nodeRunData.js +91 -0
  163. package/dist/runtime/data/payloadForm.d.ts +44 -0
  164. package/dist/runtime/data/payloadForm.js +126 -0
  165. package/dist/runtime/data/workflowIconCatalog.d.ts +34 -0
  166. package/dist/runtime/data/workflowIconCatalog.js +116 -0
  167. package/dist/runtime/pages/integrations/audit.vue +25 -35
  168. package/dist/runtime/pages/integrations/credentials.vue +117 -122
  169. package/dist/runtime/pages/integrations/index.vue +133 -36
  170. package/dist/runtime/pages/integrations/runs.vue +125 -75
  171. package/dist/runtime/pages/integrations/schedules.vue +5 -16
  172. package/dist/runtime/pages/integrations/secrets.vue +1 -3
  173. package/dist/runtime/pages/integrations/workflows/[id].vue +599 -194
  174. package/dist/runtime/pages/integrations/workflows/index.vue +10 -8
  175. package/dist/runtime/types/integrations.d.ts +190 -4
  176. package/dist/runtime/utils/apiError.d.ts +17 -0
  177. package/dist/runtime/utils/apiError.js +8 -0
  178. package/dist/runtime/utils/credentialState.d.ts +28 -0
  179. package/dist/runtime/utils/credentialState.js +29 -0
  180. package/dist/runtime/utils/localize.d.ts +3 -1
  181. package/dist/runtime/utils/localize.js +1 -2
  182. package/dist/runtime/utils/statusStyle.d.ts +6 -0
  183. package/dist/runtime/utils/statusStyle.js +15 -0
  184. package/package.json +20 -3
  185. package/dist/runtime/components/Integrations/RunDetailDialog.vue +0 -169
  186. package/dist/runtime/pages/integrations/workflows/new.d.vue.ts +0 -2
  187. package/dist/runtime/pages/integrations/workflows/new.vue +0 -153
  188. package/dist/runtime/pages/integrations/workflows/new.vue.d.ts +0 -2
@@ -0,0 +1,777 @@
1
+ <script setup>
2
+ import { computed, onUnmounted, ref, watch } from "vue";
3
+ import { Download as DownloadIcon, RefreshCircle as RefreshIcon } from "#solar-icons";
4
+ import { Badge, Button, Dialog, DialogContent, DialogHeader, DialogTitle } from "@revenexx/studio";
5
+ import { runStatusLabel, statusClass } from "../../utils/statusStyle";
6
+ import { IntegrationsApiError } from "../../utils/apiError";
7
+ const props = defineProps({
8
+ run: { type: Object, required: true },
9
+ view: { type: String, required: false, default: "details" }
10
+ });
11
+ const emit = defineEmits(["changed"]);
12
+ const {
13
+ getRun,
14
+ getDetails,
15
+ getSteps,
16
+ getHistory,
17
+ getStackTrace,
18
+ cancelRun,
19
+ terminateRun,
20
+ retryRun,
21
+ resumeRun
22
+ } = useIntegrationsRuns();
23
+ const DETAILS_POLL_MS = 3e3;
24
+ const HISTORY_POLL_MS = 1e4;
25
+ const HISTORY_PAGE_SIZE = 50;
26
+ const COCKPIT_TERMINAL = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled"]);
27
+ const API_TERMINAL = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "terminated", "timed_out"]);
28
+ const runData = ref(null);
29
+ const summaryLoading = ref(false);
30
+ const summaryError = ref(null);
31
+ const details = ref(null);
32
+ const detailsLoading = ref(false);
33
+ const detailsError = ref(null);
34
+ const steps = ref([]);
35
+ const stepsLoading = ref(false);
36
+ const stepsError = ref(null);
37
+ const stepsWithPayloads = ref(false);
38
+ const expandedStep = ref(null);
39
+ const historyLoading = ref(false);
40
+ const historyError = ref(null);
41
+ const stackEnabled = ref(false);
42
+ const stackTrace = ref(null);
43
+ const stackLoading = ref(false);
44
+ const stackError = ref(null);
45
+ let summaryTimer = null;
46
+ let historyTimer = null;
47
+ function pick(obj, keys) {
48
+ if (!obj || typeof obj !== "object") return void 0;
49
+ const o = obj;
50
+ for (const k of keys) if (o[k] != null) return o[k];
51
+ return void 0;
52
+ }
53
+ function pickStr(obj, keys) {
54
+ const v = pick(obj, keys);
55
+ return v == null ? null : String(v);
56
+ }
57
+ function unwrapRun(res) {
58
+ if (!res || typeof res !== "object") return null;
59
+ const o = res;
60
+ if ("data" in o && o.data && typeof o.data === "object" && !("status" in o) && !("temporal_run_id" in o)) {
61
+ return o.data;
62
+ }
63
+ return o;
64
+ }
65
+ function isNonEmptyObject(v) {
66
+ return !!v && typeof v === "object" && !Array.isArray(v) && Object.keys(v).length > 0;
67
+ }
68
+ function fmt(iso) {
69
+ if (!iso) return "\u2014";
70
+ try {
71
+ return new Date(iso).toLocaleString();
72
+ } catch {
73
+ return String(iso);
74
+ }
75
+ }
76
+ function json(x) {
77
+ return JSON.stringify(x, null, 2);
78
+ }
79
+ const startedAt = computed(() => pickStr(runData.value, ["started_at", "startedAt"]) ?? props.run.startedAt ?? null);
80
+ const completedAt = computed(() => pickStr(runData.value, ["completed_at", "completedAt"]) ?? props.run.finishedAt ?? null);
81
+ const errorMessage = computed(() => pickStr(runData.value, ["error", "errorMessage"]) ?? props.run.errorMessage ?? null);
82
+ const temporalWorkflowId = computed(() => pickStr(runData.value, ["temporal_workflow_id", "temporalWorkflowId"]));
83
+ const temporalRunId = computed(() => pickStr(runData.value, ["temporal_run_id", "temporalRunId"]) ?? props.run.temporalRunId ?? null);
84
+ const hasTemporal = computed(() => !!temporalWorkflowId.value && !!temporalRunId.value);
85
+ const runResult = computed(() => pick(runData.value, ["result"]));
86
+ const resultLinks = computed(() => {
87
+ const out = [];
88
+ const seen = /* @__PURE__ */ new Set();
89
+ const walk = (value, path) => {
90
+ if (typeof value === "string") {
91
+ const key = path[path.length - 1] ?? "";
92
+ if (/url$/i.test(key) && /^https?:\/\//.test(value) && !seen.has(value)) {
93
+ seen.add(value);
94
+ out.push({ label: path.slice(-2).join("."), url: value });
95
+ }
96
+ return;
97
+ }
98
+ if (Array.isArray(value)) value.forEach((v, i) => walk(v, [...path, String(i)]));
99
+ else if (value !== null && typeof value === "object") {
100
+ for (const [k, v] of Object.entries(value)) walk(v, [...path, k]);
101
+ }
102
+ };
103
+ walk(runResult.value, []);
104
+ return out.slice(0, 8);
105
+ });
106
+ const hasResult = computed(() => runResult.value != null && (typeof runResult.value !== "object" || Object.keys(runResult.value).length > 0));
107
+ const isTerminal = computed(() => {
108
+ const apiStatus = pickStr(runData.value, ["status"]);
109
+ if (apiStatus) return API_TERMINAL.has(apiStatus);
110
+ return COCKPIT_TERMINAL.has(props.run.status);
111
+ });
112
+ const dStatus = computed(() => pickStr(details.value, ["status"]));
113
+ const dWorkflowType = computed(() => pickStr(details.value, ["workflow_type", "workflowType"]));
114
+ const dTaskQueue = computed(() => pickStr(details.value, ["task_queue", "taskQueue"]));
115
+ const dHistoryLength = computed(() => pick(details.value, ["history_length", "historyLength"]));
116
+ function eventId(ev) {
117
+ const v = pick(ev, ["event_id", "eventId"]);
118
+ return typeof v === "number" ? v : Number(v ?? 0);
119
+ }
120
+ function eventType(ev) {
121
+ return pickStr(ev, ["event_type", "eventType", "type"]) ?? "event";
122
+ }
123
+ function eventTime(ev) {
124
+ return fmt(pickStr(ev, ["event_time", "eventTime"]));
125
+ }
126
+ function eventAttributes(ev) {
127
+ return pick(ev, ["attributes"]);
128
+ }
129
+ function eventColor(type) {
130
+ const t = type.toLowerCase();
131
+ if (t.includes("failed") || t.includes("terminated")) return "text-rose-600";
132
+ if (t.includes("completed")) return "text-emerald-600";
133
+ return "text-foreground";
134
+ }
135
+ function statusOf(e) {
136
+ return e instanceof IntegrationsApiError ? e.status : null;
137
+ }
138
+ function genericMsg(e, fallback) {
139
+ if (e instanceof IntegrationsApiError) return e.message || fallback;
140
+ return e?.message ?? fallback;
141
+ }
142
+ function detailsErrMsg(e) {
143
+ const s = statusOf(e);
144
+ if (s === 410) return "History is no longer available (retention expired and no snapshot).";
145
+ if (s === 503) return "Temporal is currently unavailable.";
146
+ return genericMsg(e, "Could not load Temporal details.");
147
+ }
148
+ function historyErrMsg(e) {
149
+ const s = statusOf(e);
150
+ if (s === 410) return "History is no longer available.";
151
+ if (s === 422) return "Pagination cursor invalidated \u2014 reset.";
152
+ return genericMsg(e, "Could not load history.");
153
+ }
154
+ function stackErrMsg(e) {
155
+ const s = statusOf(e);
156
+ if (s === 409) return "Workflow is no longer running.";
157
+ if (s === 410) return "Unknown to Temporal.";
158
+ if (s === 503) return "Temporal unavailable.";
159
+ return genericMsg(e, "Could not load stack trace.");
160
+ }
161
+ function stepsErrMsg(e) {
162
+ const s = statusOf(e);
163
+ if (s === 409) return "Run has not been handed off to Temporal yet.";
164
+ if (s === 410) return "Execution history is no longer available.";
165
+ if (s === 503) return "Temporal is currently unavailable.";
166
+ return genericMsg(e, "Could not load steps.");
167
+ }
168
+ function fmtMs(ms) {
169
+ if (ms == null) return "\u2014";
170
+ if (ms < 1e3) return `${ms}ms`;
171
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(2)}s`;
172
+ return `${(ms / 6e4).toFixed(1)}m`;
173
+ }
174
+ function activityName(a) {
175
+ const at = a ? a.activity_type ?? a.activityType : void 0;
176
+ if (typeof at === "string") return at;
177
+ return pickStr(at, ["name"]) ?? "Activity";
178
+ }
179
+ const timeline = computed(() => {
180
+ const evs = timelineEvents.value;
181
+ if (!evs.length) return null;
182
+ const timeOf = (ev) => Date.parse(pickStr(ev, ["event_time", "eventTime"]) ?? "");
183
+ const attrsOf = (ev) => pick(ev, ["attributes"]);
184
+ const refOf = (ev, keys) => Number(pick(attrsOf(ev), keys) ?? Number.NaN);
185
+ const cycles = /* @__PURE__ */ new Map();
186
+ let wf = null;
187
+ for (const ev of evs) {
188
+ const type = eventType(ev);
189
+ const ts = timeOf(ev);
190
+ if (!Number.isFinite(ts)) continue;
191
+ if (type === "workflow_execution_started") {
192
+ wf = { label: "Workflow", kind: "workflow", sched: ts, start: ts, end: null, ok: true, events: [ev] };
193
+ } else if (type.startsWith("workflow_execution_")) {
194
+ if (wf) {
195
+ wf.end = ts;
196
+ wf.ok = type === "workflow_execution_completed";
197
+ wf.events.push(ev);
198
+ }
199
+ } else if (type === "activity_task_scheduled") {
200
+ cycles.set(eventId(ev), { label: activityName(attrsOf(ev)), kind: "activity", sched: ts, start: null, end: null, ok: true, events: [ev] });
201
+ } else if (type === "workflow_task_scheduled") {
202
+ cycles.set(eventId(ev), { label: "Workflow task", kind: "wf-task", sched: ts, start: null, end: null, ok: true, events: [ev] });
203
+ } else if (type === "activity_task_started" || type === "workflow_task_started") {
204
+ const c = cycles.get(refOf(ev, ["scheduled_event_id", "scheduledEventId"]));
205
+ if (c) {
206
+ c.start = ts;
207
+ c.events.push(ev);
208
+ }
209
+ } else if (type.startsWith("activity_task_") || type.startsWith("workflow_task_")) {
210
+ const c = cycles.get(refOf(ev, ["scheduled_event_id", "scheduledEventId"]));
211
+ if (c) {
212
+ c.end = ts;
213
+ c.ok = type.endsWith("_completed");
214
+ c.events.push(ev);
215
+ }
216
+ } else if (type === "timer_started") {
217
+ cycles.set(eventId(ev), { label: "Timer", kind: "timer", sched: ts, start: ts, end: null, ok: true, events: [ev] });
218
+ } else if (type === "timer_fired" || type === "timer_canceled") {
219
+ const c = cycles.get(refOf(ev, ["started_event_id", "startedEventId"]));
220
+ if (c) {
221
+ c.end = ts;
222
+ c.ok = type === "timer_fired";
223
+ c.events.push(ev);
224
+ }
225
+ }
226
+ }
227
+ const all = [...wf ? [wf] : [], ...cycles.values()].sort((a, b) => a.sched - b.sched);
228
+ if (!all.length) return null;
229
+ const t0 = Math.min(...all.map((c) => c.sched));
230
+ const tEnd = Math.max(t0 + 1, ...all.map((c) => c.end ?? c.start ?? c.sched));
231
+ const span = tEnd - t0;
232
+ const rows = all.map((c, i) => {
233
+ const start = c.start ?? c.sched;
234
+ const end = c.end ?? start;
235
+ const leftPct = (start - t0) / span * 100;
236
+ const widthPct = Math.max((end - start) / span * 100, 0.6);
237
+ const spaceRight = 100 - (leftPct + widthPct);
238
+ return {
239
+ key: `${i}-${c.label}`,
240
+ label: c.label,
241
+ kind: c.kind,
242
+ ok: c.ok,
243
+ durationMs: c.end != null ? c.end - start : null,
244
+ events: c.events,
245
+ waitLeftPct: (c.sched - t0) / span * 100,
246
+ waitWidthPct: Math.max((start - c.sched) / span * 100, 0),
247
+ leftPct,
248
+ widthPct,
249
+ labelMode: spaceRight >= 25 ? "after" : leftPct >= 25 ? "before" : "inside"
250
+ };
251
+ });
252
+ return { t0, tEnd, span, rows };
253
+ });
254
+ function timelineBarClass(row) {
255
+ if (!row.ok) return "bg-rose-500 group-hover:bg-rose-600";
256
+ switch (row.kind) {
257
+ case "workflow":
258
+ return "bg-emerald-600 group-hover:bg-emerald-700";
259
+ case "wf-task":
260
+ return "bg-slate-300 group-hover:bg-slate-400";
261
+ case "timer":
262
+ return "bg-amber-500 group-hover:bg-amber-600";
263
+ default:
264
+ return "bg-emerald-500 group-hover:bg-emerald-600";
265
+ }
266
+ }
267
+ const timelineDetail = ref(null);
268
+ function timelineRowStatus(row) {
269
+ if (row.durationMs == null) return "running";
270
+ return row.ok ? "succeeded" : "failed";
271
+ }
272
+ function fmtClock(ms) {
273
+ try {
274
+ return new Date(ms).toLocaleTimeString();
275
+ } catch {
276
+ return "";
277
+ }
278
+ }
279
+ async function loadSummary() {
280
+ summaryLoading.value = true;
281
+ summaryError.value = null;
282
+ try {
283
+ const res = await getRun(props.run.workflowId, props.run.id);
284
+ runData.value = unwrapRun(res);
285
+ } catch (e) {
286
+ summaryError.value = genericMsg(e, "Could not load run.");
287
+ } finally {
288
+ summaryLoading.value = false;
289
+ }
290
+ }
291
+ async function loadDetails() {
292
+ if (!hasTemporal.value) {
293
+ details.value = null;
294
+ detailsError.value = null;
295
+ return;
296
+ }
297
+ detailsLoading.value = true;
298
+ detailsError.value = null;
299
+ try {
300
+ const res = await getDetails(props.run.workflowId, props.run.id);
301
+ details.value = res?.data ?? null;
302
+ } catch (e) {
303
+ detailsError.value = detailsErrMsg(e);
304
+ details.value = null;
305
+ } finally {
306
+ detailsLoading.value = false;
307
+ }
308
+ }
309
+ async function loadSteps() {
310
+ if (!hasTemporal.value) {
311
+ steps.value = [];
312
+ stepsError.value = null;
313
+ return;
314
+ }
315
+ stepsLoading.value = true;
316
+ stepsError.value = null;
317
+ try {
318
+ const res = await getSteps(props.run.workflowId, props.run.id, stepsWithPayloads.value);
319
+ steps.value = res?.steps ?? [];
320
+ } catch (e) {
321
+ stepsError.value = stepsErrMsg(e);
322
+ steps.value = [];
323
+ } finally {
324
+ stepsLoading.value = false;
325
+ }
326
+ }
327
+ function toggleStepPayloads() {
328
+ stepsWithPayloads.value = !stepsWithPayloads.value;
329
+ expandedStep.value = null;
330
+ void loadSteps();
331
+ }
332
+ function toggleStep(i) {
333
+ expandedStep.value = expandedStep.value === i ? null : i;
334
+ }
335
+ async function loadStack() {
336
+ stackEnabled.value = true;
337
+ stackLoading.value = true;
338
+ stackError.value = null;
339
+ try {
340
+ stackTrace.value = await getStackTrace(props.run.workflowId, props.run.id);
341
+ } catch (e) {
342
+ stackError.value = stackErrMsg(e);
343
+ stackTrace.value = null;
344
+ } finally {
345
+ stackLoading.value = false;
346
+ }
347
+ }
348
+ const timelineEvents = ref([]);
349
+ const TIMELINE_MAX_PAGES = 40;
350
+ async function loadTimelineEvents() {
351
+ if (!hasTemporal.value) {
352
+ timelineEvents.value = [];
353
+ historyError.value = null;
354
+ return;
355
+ }
356
+ if (!timelineEvents.value.length) historyLoading.value = true;
357
+ historyError.value = null;
358
+ try {
359
+ const out = [];
360
+ let token;
361
+ for (let i = 0; i < TIMELINE_MAX_PAGES; i++) {
362
+ const res = await getHistory(props.run.workflowId, props.run.id, { page_token: token, page_size: HISTORY_PAGE_SIZE });
363
+ out.push(...res?.data ?? []);
364
+ token = res?.next_page_token ?? void 0;
365
+ if (!token) break;
366
+ }
367
+ timelineEvents.value = out;
368
+ } catch (e) {
369
+ historyError.value = historyErrMsg(e);
370
+ } finally {
371
+ historyLoading.value = false;
372
+ }
373
+ }
374
+ async function loadAll() {
375
+ await loadSummary();
376
+ await Promise.allSettled([loadDetails(), loadSteps(), loadTimelineEvents()]);
377
+ }
378
+ function stopPolling() {
379
+ if (summaryTimer) {
380
+ clearInterval(summaryTimer);
381
+ summaryTimer = null;
382
+ }
383
+ if (historyTimer) {
384
+ clearInterval(historyTimer);
385
+ historyTimer = null;
386
+ }
387
+ }
388
+ function startPollingIfNeeded() {
389
+ stopPolling();
390
+ if (isTerminal.value) return;
391
+ summaryTimer = setInterval(() => {
392
+ void loadSummary().then(() => {
393
+ if (isTerminal.value) {
394
+ stopPolling();
395
+ return;
396
+ }
397
+ void loadDetails();
398
+ void loadSteps();
399
+ });
400
+ }, DETAILS_POLL_MS);
401
+ historyTimer = setInterval(() => {
402
+ void loadTimelineEvents();
403
+ }, HISTORY_POLL_MS);
404
+ }
405
+ function resetState() {
406
+ runData.value = null;
407
+ summaryError.value = null;
408
+ details.value = null;
409
+ detailsError.value = null;
410
+ steps.value = [];
411
+ stepsError.value = null;
412
+ stepsWithPayloads.value = false;
413
+ expandedStep.value = null;
414
+ historyError.value = null;
415
+ timelineEvents.value = [];
416
+ timelineDetail.value = null;
417
+ stackEnabled.value = false;
418
+ stackTrace.value = null;
419
+ stackError.value = null;
420
+ armed.value = null;
421
+ actionBusy.value = false;
422
+ actionError.value = null;
423
+ actionNotice.value = null;
424
+ }
425
+ const ACTION_LABELS = {
426
+ cancel: "Cancel",
427
+ terminate: "Terminate",
428
+ retry: "Retry",
429
+ resume: "Resume"
430
+ };
431
+ const apiStatusRaw = computed(() => pickStr(runData.value, ["status"]));
432
+ const isActive = computed(() => !isTerminal.value);
433
+ const canStop = computed(() => isActive.value && hasTemporal.value);
434
+ const canResume = computed(() => {
435
+ const failed = apiStatusRaw.value ? apiStatusRaw.value === "failed" : props.run.status === "failed";
436
+ return failed && hasTemporal.value;
437
+ });
438
+ const hasActions = computed(() => canStop.value || canResume.value);
439
+ const armed = ref(null);
440
+ const actionBusy = ref(false);
441
+ const actionError = ref(null);
442
+ const actionNotice = ref(null);
443
+ watch(() => props.run.id, () => {
444
+ stopPolling();
445
+ resetState();
446
+ void loadAll().then(startPollingIfNeeded);
447
+ }, { immediate: true });
448
+ onUnmounted(stopPolling);
449
+ function arm(a) {
450
+ armed.value = a;
451
+ actionError.value = null;
452
+ actionNotice.value = null;
453
+ }
454
+ function disarm() {
455
+ armed.value = null;
456
+ }
457
+ async function confirmAction() {
458
+ if (!armed.value) return;
459
+ const a = armed.value;
460
+ actionBusy.value = true;
461
+ actionError.value = null;
462
+ try {
463
+ let res = null;
464
+ switch (a) {
465
+ case "cancel":
466
+ res = await cancelRun(props.run.workflowId, props.run.id);
467
+ break;
468
+ case "terminate":
469
+ res = await terminateRun(props.run.workflowId, props.run.id);
470
+ break;
471
+ case "retry":
472
+ res = await retryRun(props.run.workflowId, props.run.id);
473
+ break;
474
+ case "resume":
475
+ res = await resumeRun(props.run.workflowId, props.run.id);
476
+ break;
477
+ }
478
+ const newId = pickStr(res, ["id"]);
479
+ actionNotice.value = (a === "retry" || a === "resume") && newId && newId !== String(props.run.id) ? `Started run #${newId}.` : `${ACTION_LABELS[a]} requested.`;
480
+ armed.value = null;
481
+ await loadAll();
482
+ startPollingIfNeeded();
483
+ emit("changed");
484
+ } catch (e) {
485
+ actionError.value = genericMsg(e, `Could not ${ACTION_LABELS[a].toLowerCase()} the run.`);
486
+ } finally {
487
+ actionBusy.value = false;
488
+ }
489
+ }
490
+ </script>
491
+
492
+ <template>
493
+ <div class="space-y-4 text-sm">
494
+ <!-- ── Details view: overview strip + result. ─────────────────────────── -->
495
+ <template v-if="view === 'details'">
496
+ <!-- Toolbar — only the status-gated stop/resume actions; hidden entirely
497
+ for a terminal run (Retry sits in the page header, data auto-polls). -->
498
+ <div v-if="hasActions" class="flex items-center gap-2">
499
+ <!-- confirm step: one click arms, second confirms -->
500
+ <template v-if="armed">
501
+ <span class="text-sm text-muted-foreground">{{ ACTION_LABELS[armed] }} this run?</span>
502
+ <Button
503
+ size="sm"
504
+ :class="armed === 'terminate' ? 'bg-rose-600 hover:bg-rose-700 text-white' : ''"
505
+ :disabled="actionBusy"
506
+ @click="confirmAction"
507
+ >
508
+ <RefreshIcon v-if="actionBusy" :size="14" class="mr-1 animate-spin" />
509
+ Confirm
510
+ </Button>
511
+ <Button variant="ghost" size="sm" :disabled="actionBusy" @click="disarm">Back</Button>
512
+ </template>
513
+ <template v-else>
514
+ <Button v-if="canStop" variant="outline" size="sm" @click="arm('cancel')">Cancel run</Button>
515
+ <Button v-if="canStop" variant="outline" size="sm" class="text-rose-600 hover:bg-rose-500/10" @click="arm('terminate')">Terminate</Button>
516
+ <Button v-if="canResume" variant="outline" size="sm" @click="arm('resume')">Resume</Button>
517
+ </template>
518
+ </div>
519
+
520
+ <!-- Action feedback -->
521
+ <div v-if="actionError" class="rounded-md border border-rose-500/20 bg-rose-500/5 px-3 py-2 text-sm text-rose-700">
522
+ {{ actionError }}
523
+ </div>
524
+ <div v-else-if="actionNotice" class="rounded-md border border-emerald-500/20 bg-emerald-500/5 px-3 py-2 text-sm text-emerald-700">
525
+ {{ actionNotice }}
526
+ </div>
527
+
528
+ <!-- Overview — summary + the Temporal facts merged into one strip. -->
529
+ <section class="rounded-md border border-black/[0.06] bg-white p-4 space-y-3">
530
+ <div class="grid gap-x-6 gap-y-3 sm:grid-cols-2 xl:grid-cols-4">
531
+ <div>
532
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">Status</div>
533
+ <div class="mt-0.5">
534
+ <Badge variant="outline" :class="['border text-xs font-medium uppercase', statusClass(dStatus ?? run.status)]">
535
+ {{ runStatusLabel(dStatus ?? run.status) }}
536
+ </Badge>
537
+ </div>
538
+ </div>
539
+ <div>
540
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">Started</div>
541
+ <div class="tabular-nums">{{ fmt(startedAt) }}</div>
542
+ </div>
543
+ <div>
544
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">Completed</div>
545
+ <div class="tabular-nums">{{ fmt(completedAt) }}</div>
546
+ </div>
547
+ <div>
548
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">History Length</div>
549
+ <div class="tabular-nums">{{ dHistoryLength ?? "\u2014" }}</div>
550
+ </div>
551
+ <div class="min-w-0">
552
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">Workflow</div>
553
+ <div class="truncate font-mono text-xs leading-5" :title="temporalWorkflowId ?? '\u2014'">{{ temporalWorkflowId ?? "\u2014" }}</div>
554
+ </div>
555
+ <div class="min-w-0">
556
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">Run ID</div>
557
+ <div class="truncate font-mono text-xs leading-5" :title="temporalRunId ?? '\u2014'">{{ temporalRunId ?? "\u2014" }}</div>
558
+ </div>
559
+ <div class="min-w-0">
560
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">Workflow Type</div>
561
+ <div class="truncate font-mono text-xs leading-5">{{ dWorkflowType ?? "\u2014" }}</div>
562
+ </div>
563
+ <div class="min-w-0">
564
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">Task Queue</div>
565
+ <div class="truncate font-mono text-xs leading-5">{{ dTaskQueue ?? "\u2014" }}</div>
566
+ </div>
567
+ </div>
568
+ <div v-if="errorMessage" class="rounded-md border border-rose-500/20 bg-rose-500/5 px-3 py-2 text-rose-700">
569
+ {{ errorMessage }}
570
+ </div>
571
+ <div v-if="summaryError" class="text-rose-700">
572
+ {{ summaryError }}
573
+ </div>
574
+ <div v-if="detailsError" class="text-rose-700">
575
+ {{ detailsError }}
576
+ </div>
577
+ </section>
578
+
579
+ <!-- Result — full width below the overview. -->
580
+ <section v-if="hasResult" class="rounded-md border border-black/[0.06] bg-white p-4 space-y-2">
581
+ <h4 class="font-mono text-xs uppercase tracking-wide text-muted-foreground">Result</h4>
582
+ <div v-if="resultLinks.length" class="flex flex-wrap gap-1.5">
583
+ <a
584
+ v-for="link in resultLinks"
585
+ :key="link.url"
586
+ :href="link.url"
587
+ target="_blank"
588
+ rel="noopener"
589
+ class="inline-flex items-center gap-1 rounded-md border border-emerald-500/30 bg-emerald-500/[0.06] px-2 py-1 text-xs font-medium text-emerald-700 transition hover:bg-emerald-500/[0.12]"
590
+ >
591
+ <DownloadIcon :size="11" />
592
+ {{ link.label }}
593
+ </a>
594
+ </div>
595
+ <pre class="rounded-md bg-muted/40 p-3 text-xs overflow-x-auto max-h-[28rem]">{{ json(runResult) }}</pre>
596
+ </section>
597
+
598
+ <!-- Stack Trace (non-terminal runs only, lazy) -->
599
+ <section v-if="!isTerminal" class="rounded-md border border-black/[0.06] bg-white p-4 space-y-3">
600
+ <div class="flex items-center gap-2 border-b border-black/[0.06] pb-2">
601
+ <h4 class="font-mono text-xs uppercase tracking-wide text-muted-foreground">Stack Trace</h4>
602
+ <div class="flex-1" />
603
+ <Button variant="outline" size="sm" :disabled="stackLoading" @click="loadStack">
604
+ {{ stackEnabled ? "\u21BB Reload" : "Load" }}
605
+ </Button>
606
+ </div>
607
+
608
+ <p v-if="!stackEnabled" class="text-xs text-muted-foreground">
609
+ Load to query the running worker for its current call stack.
610
+ </p>
611
+ <div v-else-if="stackLoading" class="flex items-center gap-2 text-muted-foreground">
612
+ <RefreshIcon :size="14" class="animate-spin" /> Querying worker…
613
+ </div>
614
+ <p v-else-if="stackError" class="text-rose-700">{{ stackError }}</p>
615
+ <pre
616
+ v-else-if="stackTrace"
617
+ class="rounded-md bg-slate-900 text-slate-100 p-3 text-xs overflow-auto max-h-56 whitespace-pre-wrap leading-relaxed"
618
+ >{{ stackTrace }}</pre>
619
+ </section>
620
+ </template>
621
+
622
+ <!-- ── Timeline view: steps + the stacked history. ─────────────────────── -->
623
+ <template v-else>
624
+ <!-- Steps (per-node projection) -->
625
+ <section v-if="hasTemporal" class="rounded-md border border-black/[0.06] bg-white p-4 space-y-3">
626
+ <div class="flex items-center gap-2 border-b border-black/[0.06] pb-2">
627
+ <h4 class="font-mono text-xs uppercase tracking-wide text-muted-foreground">Steps</h4>
628
+ <div class="flex-1" />
629
+ <Button variant="outline" size="sm" :disabled="stepsLoading" @click="toggleStepPayloads">
630
+ {{ stepsWithPayloads ? "Hide payloads" : "Show payloads" }}
631
+ </Button>
632
+ </div>
633
+
634
+ <p v-if="stepsError" class="text-rose-700">{{ stepsError }}</p>
635
+ <div v-else-if="stepsLoading && !steps.length" class="flex items-center gap-2 text-muted-foreground">
636
+ <RefreshIcon :size="14" class="animate-spin" /> Loading steps…
637
+ </div>
638
+ <p v-else-if="!steps.length" class="text-muted-foreground">No steps projected yet.</p>
639
+ <ol v-else class="space-y-1">
640
+ <li
641
+ v-for="(st, i) in steps"
642
+ :key="`${st.node_id}-${st.scheduled_event_id ?? i}`"
643
+ class="rounded-md border border-black/[0.05] bg-white overflow-hidden"
644
+ >
645
+ <button
646
+ type="button"
647
+ class="w-full flex items-center gap-3 px-3 py-2 text-left"
648
+ @click="toggleStep(i)"
649
+ >
650
+ <Badge variant="outline" :class="['border text-xs font-medium uppercase shrink-0', statusClass(st.status)]">{{ runStatusLabel(st.status) }}</Badge>
651
+ <div class="min-w-0 flex-1">
652
+ <div class="font-mono text-xs truncate">{{ st.node_slug || st.node_id }}</div>
653
+ <div class="text-[10px] text-muted-foreground truncate">
654
+ {{ st.node_id }}<template v-if="st.branch"> · branch {{ st.branch }}</template><template v-if="st.attempt"> · attempt {{ st.attempt + 1 }}</template>
655
+ </div>
656
+ </div>
657
+ <span class="text-xs text-muted-foreground tabular-nums shrink-0">{{ fmtMs(st.duration_ms) }}</span>
658
+ <span class="text-[10px] text-muted-foreground shrink-0">{{ expandedStep === i ? "\u25BC" : "\u25B6" }}</span>
659
+ </button>
660
+ <div v-if="st.error" class="px-3 pb-2 -mt-1">
661
+ <div class="rounded-md border border-rose-500/20 bg-rose-500/5 px-2 py-1 text-xs text-rose-700 break-words">{{ st.error }}</div>
662
+ </div>
663
+ <div v-if="expandedStep === i" class="px-3 pb-3 space-y-2">
664
+ <template v-if="stepsWithPayloads">
665
+ <div class="space-y-1">
666
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">Input</div>
667
+ <pre class="rounded-md bg-muted/40 p-3 text-xs overflow-x-auto max-h-40">{{ st.input === void 0 ? "\u2014" : json(st.input) }}</pre>
668
+ </div>
669
+ <div class="space-y-1">
670
+ <div class="text-[10px] uppercase tracking-wide text-muted-foreground">Output</div>
671
+ <pre class="rounded-md bg-muted/40 p-3 text-xs overflow-x-auto max-h-40">{{ st.output === void 0 ? "\u2014" : json(st.output) }}</pre>
672
+ </div>
673
+ </template>
674
+ <p v-else class="text-xs text-muted-foreground">
675
+ Enable “Show payloads” to load this step's input and output.
676
+ </p>
677
+ </div>
678
+ </li>
679
+ </ol>
680
+ </section>
681
+
682
+ <!-- History — the stacked timeline. Hover recolors a span, clicking it
683
+ opens the underlying events in a lightbox. -->
684
+ <section v-if="hasTemporal" class="rounded-md border border-black/[0.06] bg-white p-4 space-y-3">
685
+ <div class="flex items-center gap-2 border-b border-black/[0.06] pb-2">
686
+ <h4 class="font-mono text-xs uppercase tracking-wide text-muted-foreground">History</h4>
687
+ </div>
688
+
689
+ <p v-if="historyError" class="text-rose-700">{{ historyError }}</p>
690
+ <div v-else-if="historyLoading && !timeline" class="flex items-center gap-2 text-muted-foreground">
691
+ <RefreshIcon :size="14" class="animate-spin" /> Loading history…
692
+ </div>
693
+ <p v-else-if="!timeline" class="text-muted-foreground">No history events.</p>
694
+ <div v-else class="rounded-md border border-black/[0.05] bg-slate-50/60 p-2">
695
+ <div class="relative">
696
+ <div class="pointer-events-none absolute inset-y-0 left-1/4 border-l border-black/[0.05]" />
697
+ <div class="pointer-events-none absolute inset-y-0 left-2/4 border-l border-black/[0.05]" />
698
+ <div class="pointer-events-none absolute inset-y-0 left-3/4 border-l border-black/[0.05]" />
699
+ <div class="relative space-y-0.5">
700
+ <button
701
+ v-for="row in timeline.rows"
702
+ :key="`tl-${row.key}`"
703
+ type="button"
704
+ class="group relative block h-7 w-full cursor-pointer rounded text-left transition hover:bg-black/[0.03]"
705
+ @click="timelineDetail = row"
706
+ >
707
+ <div
708
+ v-if="row.waitWidthPct > 0.2"
709
+ class="absolute top-[7px] h-3.5 rounded-full bg-amber-400/50"
710
+ :style="{ left: `${row.waitLeftPct}%`, width: `${row.waitWidthPct}%` }"
711
+ />
712
+ <div
713
+ class="absolute top-[7px] h-3.5 rounded-full transition-colors"
714
+ :class="timelineBarClass(row)"
715
+ :style="{ left: `${row.leftPct}%`, width: `${row.widthPct}%` }"
716
+ :title="`${row.label} \xB7 ${fmtMs(row.durationMs)}`"
717
+ />
718
+ <div
719
+ class="absolute top-0 flex h-7 items-center gap-1.5 whitespace-nowrap text-xs"
720
+ :class="row.labelMode === 'after' ? '' : '-translate-x-full'"
721
+ :style="row.labelMode === 'after' ? { left: `calc(${row.leftPct + row.widthPct}% + 6px)` } : row.labelMode === 'before' ? { left: `calc(${row.leftPct}% - 6px)` } : { left: `calc(${row.leftPct + row.widthPct}% - 8px)` }"
722
+ >
723
+ <span class="font-mono" :class="row.labelMode === 'inside' ? 'text-white' : ''">{{ row.label }}</span>
724
+ <span class="tabular-nums" :class="row.labelMode === 'inside' ? 'text-white/80' : 'text-muted-foreground'">{{ fmtMs(row.durationMs) }}</span>
725
+ </div>
726
+ </button>
727
+ </div>
728
+ </div>
729
+ <div class="mt-1 flex justify-between font-mono text-[10px] tabular-nums text-muted-foreground">
730
+ <span>{{ fmtClock(timeline.t0) }}</span>
731
+ <span>{{ fmtClock(timeline.t0 + timeline.span / 2) }}</span>
732
+ <span>{{ fmtClock(timeline.tEnd) }}</span>
733
+ </div>
734
+ </div>
735
+ </section>
736
+
737
+ <p v-if="!hasTemporal" class="text-muted-foreground">
738
+ Run has not been handed off to Temporal yet.
739
+ </p>
740
+ </template>
741
+
742
+ <!-- Span lightbox — the paired events behind a timeline bar. -->
743
+ <Dialog :open="!!timelineDetail" @update:open="(v) => {
744
+ if (!v) timelineDetail = null;
745
+ }">
746
+ <DialogContent class="sm:max-w-xl">
747
+ <DialogHeader>
748
+ <DialogTitle class="flex items-center gap-2">
749
+ <span class="truncate font-mono text-base">{{ timelineDetail?.label }}</span>
750
+ <Badge
751
+ v-if="timelineDetail"
752
+ variant="outline"
753
+ :class="['border text-xs font-medium uppercase', statusClass(timelineRowStatus(timelineDetail))]"
754
+ >{{ timelineRowStatus(timelineDetail) }}</Badge>
755
+ <span v-if="timelineDetail" class="text-sm tabular-nums text-muted-foreground">{{ fmtMs(timelineDetail.durationMs) }}</span>
756
+ </DialogTitle>
757
+ </DialogHeader>
758
+ <ol v-if="timelineDetail" class="max-h-[65vh] space-y-1 overflow-y-auto pr-1 text-sm">
759
+ <li
760
+ v-for="ev in timelineDetail.events"
761
+ :key="eventId(ev)"
762
+ class="overflow-hidden rounded-md border border-black/[0.05]"
763
+ >
764
+ <div class="flex items-center gap-3 px-3 py-2">
765
+ <span class="w-10 shrink-0 font-mono text-xs text-muted-foreground">#{{ eventId(ev) }}</span>
766
+ <span class="min-w-0 flex-1 truncate font-mono text-xs" :class="eventColor(eventType(ev))">{{ eventType(ev) }}</span>
767
+ <span class="shrink-0 text-[10px] text-muted-foreground">{{ eventTime(ev) }}</span>
768
+ </div>
769
+ <div v-if="isNonEmptyObject(eventAttributes(ev))" class="px-3 pb-3">
770
+ <pre class="max-h-56 overflow-x-auto rounded-md bg-muted/40 p-3 text-xs">{{ json(eventAttributes(ev)) }}</pre>
771
+ </div>
772
+ </li>
773
+ </ol>
774
+ </DialogContent>
775
+ </Dialog>
776
+ </div>
777
+ </template>