@codemation/next-host 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/README.md +25 -0
  2. package/app/(shell)/credentials/page.tsx +5 -0
  3. package/app/(shell)/dashboard/page.tsx +14 -0
  4. package/app/(shell)/layout.tsx +11 -0
  5. package/app/(shell)/page.tsx +5 -0
  6. package/app/(shell)/users/page.tsx +5 -0
  7. package/app/(shell)/workflows/[workflowId]/page.tsx +19 -0
  8. package/app/(shell)/workflows/page.tsx +5 -0
  9. package/app/api/[[...path]]/route.ts +40 -0
  10. package/app/api/auth/[...nextauth]/route.ts +3 -0
  11. package/app/globals.css +997 -0
  12. package/app/invite/[token]/page.tsx +10 -0
  13. package/app/layout.tsx +65 -0
  14. package/app/login/layout.tsx +25 -0
  15. package/app/login/page.tsx +22 -0
  16. package/components.json +21 -0
  17. package/docs/FORMS.md +46 -0
  18. package/docs/TAILWIND_SHADCN_MIGRATION.md +89 -0
  19. package/eslint.config.mjs +56 -0
  20. package/middleware.ts +29 -0
  21. package/next-env.d.ts +6 -0
  22. package/next.config.ts +34 -0
  23. package/package.json +76 -0
  24. package/postcss.config.mjs +7 -0
  25. package/public/canvas-icons/builtin/openai.svg +5 -0
  26. package/src/api/CodemationApiClient.ts +107 -0
  27. package/src/api/CodemationApiHttpError.ts +17 -0
  28. package/src/auth/CodemationNextAuthConfigResolver.ts +14 -0
  29. package/src/auth/CodemationNextAuthOAuthProviderDescriptorMapper.ts +30 -0
  30. package/src/auth/CodemationNextAuthOAuthProviderSnapshotResolver.ts +17 -0
  31. package/src/auth/CodemationNextAuthProviderCatalog.ts +107 -0
  32. package/src/auth/codemationEdgeAuth.ts +25 -0
  33. package/src/auth/codemationNextAuth.ts +32 -0
  34. package/src/components/Codemation.tsx +6 -0
  35. package/src/components/CodemationDataTable.tsx +37 -0
  36. package/src/components/CodemationDialog.tsx +137 -0
  37. package/src/components/CodemationFormattedDateTime.tsx +46 -0
  38. package/src/components/GoogleColorGIcon.tsx +39 -0
  39. package/src/components/OauthProviderIcon.tsx +33 -0
  40. package/src/components/PasswordStrengthMeter.tsx +59 -0
  41. package/src/components/forms/index.ts +28 -0
  42. package/src/components/json/JsonMonacoEditor.tsx +75 -0
  43. package/src/components/oauthProviderIconData.ts +17 -0
  44. package/src/components/ui/alert.tsx +56 -0
  45. package/src/components/ui/badge.tsx +40 -0
  46. package/src/components/ui/button.tsx +64 -0
  47. package/src/components/ui/card.tsx +70 -0
  48. package/src/components/ui/collapsible.tsx +26 -0
  49. package/src/components/ui/dialog.tsx +137 -0
  50. package/src/components/ui/dropdown-menu.tsx +238 -0
  51. package/src/components/ui/form.tsx +147 -0
  52. package/src/components/ui/input.tsx +19 -0
  53. package/src/components/ui/label.tsx +26 -0
  54. package/src/components/ui/scroll-area.tsx +47 -0
  55. package/src/components/ui/select.tsx +169 -0
  56. package/src/components/ui/separator.tsx +28 -0
  57. package/src/components/ui/switch.tsx +28 -0
  58. package/src/components/ui/table.tsx +72 -0
  59. package/src/components/ui/tabs.tsx +76 -0
  60. package/src/components/ui/textarea.tsx +18 -0
  61. package/src/components/ui/toggle.tsx +41 -0
  62. package/src/features/credentials/components/CredentialConfirmDialog.tsx +58 -0
  63. package/src/features/credentials/components/CredentialDialog.tsx +252 -0
  64. package/src/features/credentials/components/CredentialDialogFeedback.tsx +36 -0
  65. package/src/features/credentials/components/CredentialDialogFieldRows.tsx +257 -0
  66. package/src/features/credentials/components/CredentialDialogFormSections.tsx +230 -0
  67. package/src/features/credentials/components/CredentialEnvFieldStatusRow.tsx +64 -0
  68. package/src/features/credentials/components/CredentialFieldCopyButton.tsx +48 -0
  69. package/src/features/credentials/components/CredentialsScreenHealthBadge.tsx +21 -0
  70. package/src/features/credentials/components/CredentialsScreenInstancesTable.tsx +108 -0
  71. package/src/features/credentials/components/CredentialsScreenTestFailureAlert.tsx +33 -0
  72. package/src/features/credentials/hooks/useCredentialCreateDialog.ts +33 -0
  73. package/src/features/credentials/hooks/useCredentialDialogSession.ts +616 -0
  74. package/src/features/credentials/hooks/useCredentialsScreen.ts +213 -0
  75. package/src/features/credentials/lib/credentialFieldHelpers.ts +35 -0
  76. package/src/features/credentials/lib/credentialFormTypes.ts +1 -0
  77. package/src/features/credentials/lib/credentialInstanceTestPayloadParser.ts +10 -0
  78. package/src/features/credentials/screens/CredentialsScreen.tsx +187 -0
  79. package/src/features/invite/screens/InviteAcceptScreen.tsx +190 -0
  80. package/src/features/users/components/UsersInviteDialog.tsx +121 -0
  81. package/src/features/users/components/UsersRegenerateDialog.tsx +81 -0
  82. package/src/features/users/components/UsersScreenUserStatusBadge.tsx +19 -0
  83. package/src/features/users/schemas/usersInviteFormSchema.ts +7 -0
  84. package/src/features/users/screens/UsersScreen.tsx +240 -0
  85. package/src/features/workflows/components/WorkflowListFolderSection.tsx +91 -0
  86. package/src/features/workflows/components/WorkflowListItemCard.tsx +67 -0
  87. package/src/features/workflows/components/WorkflowListRoot.tsx +39 -0
  88. package/src/features/workflows/components/WorkflowsListTree.tsx +28 -0
  89. package/src/features/workflows/components/canvas/CanvasNodeChromeTooltip.tsx +96 -0
  90. package/src/features/workflows/components/canvas/CanvasNodeIconSlot.tsx +25 -0
  91. package/src/features/workflows/components/canvas/VisibleNodeStatusResolver.tsx +84 -0
  92. package/src/features/workflows/components/canvas/WorkflowCanvas.tsx +248 -0
  93. package/src/features/workflows/components/canvas/WorkflowCanvasCodemationNode.tsx +182 -0
  94. package/src/features/workflows/components/canvas/WorkflowCanvasCodemationNodeAccents.tsx +73 -0
  95. package/src/features/workflows/components/canvas/WorkflowCanvasCodemationNodeAgentBottomSourceHandles.tsx +43 -0
  96. package/src/features/workflows/components/canvas/WorkflowCanvasCodemationNodeAgentLabels.tsx +47 -0
  97. package/src/features/workflows/components/canvas/WorkflowCanvasCodemationNodeCard.tsx +202 -0
  98. package/src/features/workflows/components/canvas/WorkflowCanvasCodemationNodeHandles.tsx +77 -0
  99. package/src/features/workflows/components/canvas/WorkflowCanvasCodemationNodeLabelBelow.tsx +51 -0
  100. package/src/features/workflows/components/canvas/WorkflowCanvasCodemationNodeMainGlyph.tsx +64 -0
  101. package/src/features/workflows/components/canvas/WorkflowCanvasCodemationNodeToolbar.tsx +95 -0
  102. package/src/features/workflows/components/canvas/WorkflowCanvasLoadingPlaceholder.tsx +69 -0
  103. package/src/features/workflows/components/canvas/WorkflowCanvasNodeIcon.tsx +102 -0
  104. package/src/features/workflows/components/canvas/WorkflowCanvasSimpleIconGlyph.tsx +21 -0
  105. package/src/features/workflows/components/canvas/WorkflowCanvasStraightCountEdge.tsx +33 -0
  106. package/src/features/workflows/components/canvas/WorkflowCanvasStructureSignature.tsx +7 -0
  107. package/src/features/workflows/components/canvas/WorkflowCanvasSymmetricForkEdge.tsx +32 -0
  108. package/src/features/workflows/components/canvas/WorkflowCanvasToolbarIconButton.tsx +95 -0
  109. package/src/features/workflows/components/canvas/lib/WorkflowCanvasBuiltinIconRegistry.ts +26 -0
  110. package/src/features/workflows/components/canvas/lib/WorkflowCanvasEdgeCountResolver.ts +51 -0
  111. package/src/features/workflows/components/canvas/lib/WorkflowCanvasEdgeStyleResolver.ts +35 -0
  112. package/src/features/workflows/components/canvas/lib/WorkflowCanvasLabelLayoutEstimator.ts +42 -0
  113. package/src/features/workflows/components/canvas/lib/WorkflowCanvasOverlapResolver.ts +78 -0
  114. package/src/features/workflows/components/canvas/lib/WorkflowCanvasPortOrderResolver.ts +25 -0
  115. package/src/features/workflows/components/canvas/lib/WorkflowCanvasRoundedOrthogonalPathPlanner.ts +56 -0
  116. package/src/features/workflows/components/canvas/lib/WorkflowCanvasSiIconRegistry.ts +18 -0
  117. package/src/features/workflows/components/canvas/lib/WorkflowCanvasSymmetricForkPathPlanner.ts +43 -0
  118. package/src/features/workflows/components/canvas/lib/layoutWorkflow.ts +315 -0
  119. package/src/features/workflows/components/canvas/lib/workflowCanvasEdgeGeometry.ts +3 -0
  120. package/src/features/workflows/components/canvas/lib/workflowCanvasEmbeddedStyles.ts +62 -0
  121. package/src/features/workflows/components/canvas/lib/workflowCanvasFlowTypes.ts +10 -0
  122. package/src/features/workflows/components/canvas/lib/workflowCanvasNodeData.ts +41 -0
  123. package/src/features/workflows/components/canvas/lib/workflowCanvasNodeGeometry.ts +99 -0
  124. package/src/features/workflows/components/canvas/workflowCanvasNodeChrome.tsx +46 -0
  125. package/src/features/workflows/components/realtime/RealtimeContext.tsx +14 -0
  126. package/src/features/workflows/components/realtime/WorkflowRealtimeProvider.tsx +15 -0
  127. package/src/features/workflows/components/workflowDetail/NodeCredentialBindingRow.tsx +209 -0
  128. package/src/features/workflows/components/workflowDetail/NodeCredentialBindingsSection.tsx +227 -0
  129. package/src/features/workflows/components/workflowDetail/NodePropertiesConfigSection.tsx +51 -0
  130. package/src/features/workflows/components/workflowDetail/NodePropertiesPanelHeader.tsx +50 -0
  131. package/src/features/workflows/components/workflowDetail/NodePropertiesSlidePanel.tsx +134 -0
  132. package/src/features/workflows/components/workflowDetail/WorkflowActivationErrorDialog.tsx +71 -0
  133. package/src/features/workflows/components/workflowDetail/WorkflowActivationHeaderControl.tsx +64 -0
  134. package/src/features/workflows/components/workflowDetail/WorkflowDetailIcons.tsx +52 -0
  135. package/src/features/workflows/components/workflowDetail/WorkflowExecutionInspector.tsx +110 -0
  136. package/src/features/workflows/components/workflowDetail/WorkflowExecutionInspectorDetailBody.tsx +213 -0
  137. package/src/features/workflows/components/workflowDetail/WorkflowExecutionInspectorPanes.tsx +239 -0
  138. package/src/features/workflows/components/workflowDetail/WorkflowExecutionInspectorSidebarResizer.tsx +31 -0
  139. package/src/features/workflows/components/workflowDetail/WorkflowExecutionInspectorTreePanel.tsx +133 -0
  140. package/src/features/workflows/components/workflowDetail/WorkflowInspectorAttachmentGroupingPresenter.tsx +31 -0
  141. package/src/features/workflows/components/workflowDetail/WorkflowInspectorAttachmentList.tsx +118 -0
  142. package/src/features/workflows/components/workflowDetail/WorkflowInspectorBinaryView.tsx +15 -0
  143. package/src/features/workflows/components/workflowDetail/WorkflowInspectorErrorView.tsx +107 -0
  144. package/src/features/workflows/components/workflowDetail/WorkflowInspectorJsonView.tsx +114 -0
  145. package/src/features/workflows/components/workflowDetail/WorkflowInspectorPrettyTreePresenter.tsx +132 -0
  146. package/src/features/workflows/components/workflowDetail/WorkflowInspectorPrettyTreeViewRenderer.tsx +147 -0
  147. package/src/features/workflows/components/workflowDetail/WorkflowInspectorPrettyView.tsx +65 -0
  148. package/src/features/workflows/components/workflowDetail/WorkflowInspectorViews.tsx +5 -0
  149. package/src/features/workflows/components/workflowDetail/WorkflowJsonEditorBinaryAttachmentRow.tsx +74 -0
  150. package/src/features/workflows/components/workflowDetail/WorkflowJsonEditorBinaryUploadRow.tsx +69 -0
  151. package/src/features/workflows/components/workflowDetail/WorkflowJsonEditorDialog.tsx +254 -0
  152. package/src/features/workflows/components/workflowDetail/WorkflowRunsList.tsx +89 -0
  153. package/src/features/workflows/components/workflowDetail/WorkflowRunsSidebar.tsx +50 -0
  154. package/src/features/workflows/hooks/canvas/useWorkflowCanvasVisibleNodeStatuses.ts +14 -0
  155. package/src/features/workflows/hooks/realtime/realtime.tsx +271 -0
  156. package/src/features/workflows/hooks/realtime/runQueryPolling.ts +34 -0
  157. package/src/features/workflows/hooks/realtime/useWorkflowRealtimeInfrastructure.ts +541 -0
  158. package/src/features/workflows/hooks/realtime/useWorkflowRealtimeShowDisconnectedBadge.ts +9 -0
  159. package/src/features/workflows/hooks/workflowDetail/useWorkflowDetailController.tsx +1300 -0
  160. package/src/features/workflows/lib/realtime/realtimeApi.ts +78 -0
  161. package/src/features/workflows/lib/realtime/realtimeClientBridge.ts +52 -0
  162. package/src/features/workflows/lib/realtime/realtimeDomainTypes.ts +191 -0
  163. package/src/features/workflows/lib/realtime/realtimeQueryKeys.ts +15 -0
  164. package/src/features/workflows/lib/realtime/realtimeRunMutations.ts +167 -0
  165. package/src/features/workflows/lib/realtime/workflowTypes.ts +5 -0
  166. package/src/features/workflows/lib/workflowDetail/PersistedWorkflowSnapshotMapper.ts +205 -0
  167. package/src/features/workflows/lib/workflowDetail/WorkflowActivationHttpErrorFormat.ts +32 -0
  168. package/src/features/workflows/lib/workflowDetail/WorkflowDetailPresenter.ts +1017 -0
  169. package/src/features/workflows/lib/workflowDetail/WorkflowDetailUrlCodec.ts +70 -0
  170. package/src/features/workflows/lib/workflowDetail/workflowDetailTypes.ts +152 -0
  171. package/src/features/workflows/lib/workflowDetailTreeStyles.ts +65 -0
  172. package/src/features/workflows/screens/WorkflowDetailScreen.tsx +236 -0
  173. package/src/features/workflows/screens/WorkflowDetailScreenInspectorPanel.tsx +55 -0
  174. package/src/features/workflows/screens/WorkflowsList.tsx +35 -0
  175. package/src/features/workflows/screens/WorkflowsScreen.tsx +31 -0
  176. package/src/index.ts +1 -0
  177. package/src/lib/utils.ts +6 -0
  178. package/src/middleware/CodemationNextHostMiddlewarePathRules.ts +31 -0
  179. package/src/providers/CodemationSessionProvider.tsx +23 -0
  180. package/src/providers/Providers.tsx +36 -0
  181. package/src/providers/RealtimeBoundary.tsx +17 -0
  182. package/src/providers/WhitelabelProvider.tsx +22 -0
  183. package/src/server/CodemationAuthPrismaClient.ts +21 -0
  184. package/src/server/CodemationNextHost.ts +379 -0
  185. package/src/shell/AppLayout.tsx +141 -0
  186. package/src/shell/AppLayoutNavItems.tsx +129 -0
  187. package/src/shell/AppLayoutPageHeader.tsx +79 -0
  188. package/src/shell/AppLayoutSidebarBrand.tsx +33 -0
  189. package/src/shell/AppMainContent.tsx +17 -0
  190. package/src/shell/AppShellHeaderActions.tsx +12 -0
  191. package/src/shell/AppShellHeaderActionsAuthenticated.tsx +51 -0
  192. package/src/shell/CodemationNextClientShell.tsx +17 -0
  193. package/src/shell/CredentialsSignInRedirectResolver.ts +21 -0
  194. package/src/shell/LoginPageClient.tsx +231 -0
  195. package/src/shell/WorkflowDetailChromeContext.tsx +42 -0
  196. package/src/shell/WorkflowFolderTreeBuilder.ts +62 -0
  197. package/src/shell/WorkflowFolderUi.ts +42 -0
  198. package/src/shell/WorkflowSidebarNavFolder.tsx +112 -0
  199. package/src/shell/WorkflowSidebarNavTree.tsx +68 -0
  200. package/src/shell/appLayoutPageTitle.ts +16 -0
  201. package/src/shell/appLayoutSidebarIcons.tsx +108 -0
  202. package/src/whitelabel/CodemationWhitelabelSnapshot.ts +4 -0
  203. package/src/whitelabel/CodemationWhitelabelSnapshotFactory.ts +18 -0
  204. package/tsconfig.json +40 -0
  205. package/tsconfig.tsbuildinfo +1 -0
  206. package/vitest.config.ts +34 -0
@@ -0,0 +1,239 @@
1
+ import { Pencil } from "lucide-react";
2
+ import {
3
+ WorkflowInspectorBinaryView,
4
+ WorkflowInspectorErrorView,
5
+ WorkflowInspectorJsonView,
6
+ WorkflowInspectorPrettyView,
7
+ } from "./WorkflowInspectorViews";
8
+ import type {
9
+ WorkflowExecutionInspectorActions,
10
+ WorkflowExecutionInspectorFormatting,
11
+ WorkflowExecutionInspectorModel,
12
+ WorkflowExecutionInspectorPaneModel,
13
+ } from "../../lib/workflowDetail/workflowDetailTypes";
14
+
15
+ export function WorkflowExecutionInspectorPanes(
16
+ props: Readonly<{
17
+ panes: ReadonlyArray<WorkflowExecutionInspectorPaneModel>;
18
+ nodeActions: WorkflowExecutionInspectorModel["nodeActions"];
19
+ selectedPinnedOutput: WorkflowExecutionInspectorModel["selectedPinnedOutput"];
20
+ selectedNodeError: WorkflowExecutionInspectorModel["selectedNodeError"];
21
+ actions: Pick<
22
+ WorkflowExecutionInspectorActions,
23
+ "onClearPinnedOutput" | "onEditSelectedOutput" | "onSelectFormat" | "onSelectInputPort" | "onSelectOutputPort"
24
+ >;
25
+ formatting: Pick<
26
+ WorkflowExecutionInspectorFormatting,
27
+ "getErrorClipboardText" | "getErrorHeadline" | "getErrorStack"
28
+ >;
29
+ }>,
30
+ ) {
31
+ const { actions, formatting, nodeActions, panes, selectedNodeError, selectedPinnedOutput } = props;
32
+ const { onClearPinnedOutput, onEditSelectedOutput, onSelectFormat, onSelectInputPort, onSelectOutputPort } = actions;
33
+ const { getErrorClipboardText, getErrorHeadline, getErrorStack } = formatting;
34
+
35
+ return (
36
+ <div
37
+ style={{
38
+ display: "grid",
39
+ gridTemplateColumns: panes.length === 2 ? "minmax(0, 1fr) minmax(0, 1fr)" : "minmax(0, 1fr)",
40
+ minHeight: 0,
41
+ }}
42
+ >
43
+ {panes.map((pane, index) => {
44
+ const isOutputPane = pane.tab === "output";
45
+ const availableFormats =
46
+ pane.attachments.length > 0 ? (["json", "pretty", "binary"] as const) : (["json", "pretty"] as const);
47
+ return (
48
+ <section
49
+ key={pane.tab}
50
+ data-testid={`workflow-inspector-pane-${pane.tab}`}
51
+ style={{
52
+ display: "grid",
53
+ gridTemplateRows: "auto auto 1fr",
54
+ minWidth: 0,
55
+ minHeight: 0,
56
+ borderLeft: index > 0 ? "1px solid #e5e7eb" : "none",
57
+ }}
58
+ >
59
+ <div
60
+ style={{
61
+ display: "flex",
62
+ alignItems: "center",
63
+ justifyContent: "space-between",
64
+ flexWrap: "wrap",
65
+ gap: 12,
66
+ padding: "10px 12px",
67
+ borderBottom: "1px solid #e5e7eb",
68
+ background: "#f8fafc",
69
+ }}
70
+ >
71
+ <div style={{ display: "flex", alignItems: "center", gap: 8, minWidth: 0 }}>
72
+ <div
73
+ style={{
74
+ fontSize: 11,
75
+ fontWeight: 800,
76
+ letterSpacing: 0.45,
77
+ textTransform: "uppercase",
78
+ opacity: 0.72,
79
+ }}
80
+ >
81
+ {pane.tab}
82
+ </div>
83
+ {isOutputPane && nodeActions.viewContext === "live-workflow" ? (
84
+ <button
85
+ type="button"
86
+ data-testid="edit-output-button"
87
+ onClick={onEditSelectedOutput}
88
+ disabled={!nodeActions.canEditOutput}
89
+ style={{
90
+ display: "inline-flex",
91
+ alignItems: "center",
92
+ gap: 6,
93
+ border: "1px solid #d1d5db",
94
+ background: "white",
95
+ color: "#111827",
96
+ padding: "5px 8px",
97
+ fontWeight: 700,
98
+ fontSize: 12,
99
+ cursor: !nodeActions.canEditOutput ? "not-allowed" : "pointer",
100
+ opacity: !nodeActions.canEditOutput ? 0.6 : 1,
101
+ }}
102
+ >
103
+ <Pencil size={12} strokeWidth={2} />
104
+ Edit
105
+ </button>
106
+ ) : null}
107
+ {isOutputPane && nodeActions.viewContext === "live-workflow" && selectedPinnedOutput ? (
108
+ <button
109
+ type="button"
110
+ onClick={onClearPinnedOutput}
111
+ disabled={!nodeActions.canClearPinnedOutput}
112
+ style={{
113
+ border: "1px solid #d1d5db",
114
+ background: "white",
115
+ color: "#111827",
116
+ padding: "5px 8px",
117
+ fontWeight: 700,
118
+ fontSize: 12,
119
+ cursor: !nodeActions.canClearPinnedOutput ? "not-allowed" : "pointer",
120
+ opacity: !nodeActions.canClearPinnedOutput ? 0.6 : 1,
121
+ }}
122
+ >
123
+ Clear pin
124
+ </button>
125
+ ) : null}
126
+ {isOutputPane && pane.showsError ? (
127
+ <span
128
+ style={{
129
+ border: "1px solid #fecaca",
130
+ background: "#fef2f2",
131
+ color: "#991b1b",
132
+ fontSize: 10,
133
+ fontWeight: 800,
134
+ letterSpacing: 0.3,
135
+ textTransform: "uppercase",
136
+ padding: "1px 5px",
137
+ }}
138
+ >
139
+ Error
140
+ </span>
141
+ ) : null}
142
+ </div>
143
+ <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
144
+ {availableFormats.map((format) => (
145
+ <button
146
+ key={format}
147
+ data-testid={`inspector-format-${pane.tab}-${format}`}
148
+ onClick={() => onSelectFormat(pane.tab, format)}
149
+ aria-pressed={pane.format === format}
150
+ style={{
151
+ border: pane.format === format ? "1px solid #111827" : "1px solid #d1d5db",
152
+ background: pane.format === format ? "#111827" : "white",
153
+ color: pane.format === format ? "white" : "#111827",
154
+ padding: "6px 10px",
155
+ fontWeight: 700,
156
+ fontSize: 12,
157
+ cursor: "pointer",
158
+ }}
159
+ >
160
+ {format[0]!.toUpperCase()}
161
+ {format.slice(1)}
162
+ </button>
163
+ ))}
164
+ </div>
165
+ </div>
166
+
167
+ {pane.portEntries.length > 1 ? (
168
+ <div
169
+ style={{
170
+ display: "flex",
171
+ flexWrap: "wrap",
172
+ gap: 8,
173
+ padding: "10px 12px",
174
+ borderBottom: "1px solid #e5e7eb",
175
+ background: "#ffffff",
176
+ overflowX: "hidden",
177
+ overflowY: "auto",
178
+ }}
179
+ >
180
+ {pane.portEntries.map(([portName]) => {
181
+ const isSelected = pane.selectedPort === portName;
182
+ return (
183
+ <button
184
+ key={portName}
185
+ data-testid={`inspector-port-${pane.tab}-${portName}`}
186
+ onClick={() => {
187
+ if (pane.tab === "input") onSelectInputPort(portName);
188
+ if (pane.tab === "output") onSelectOutputPort(portName);
189
+ }}
190
+ style={{
191
+ whiteSpace: "nowrap",
192
+ border: isSelected ? "1px solid #111827" : "1px solid #d1d5db",
193
+ background: isSelected ? "#111827" : "white",
194
+ color: isSelected ? "white" : "#111827",
195
+ padding: "6px 10px",
196
+ fontWeight: 700,
197
+ fontSize: 12,
198
+ cursor: "pointer",
199
+ }}
200
+ >
201
+ {portName}
202
+ </button>
203
+ );
204
+ })}
205
+ </div>
206
+ ) : (
207
+ <div style={{ borderBottom: "1px solid #e5e7eb" }} />
208
+ )}
209
+
210
+ <div style={{ minWidth: 0, overflowX: "hidden", overflowY: "auto", padding: 12 }}>
211
+ {pane.showsError ? (
212
+ pane.format === "pretty" ? (
213
+ <WorkflowInspectorErrorView
214
+ error={selectedNodeError}
215
+ emptyLabel={pane.emptyLabel}
216
+ getErrorClipboardText={getErrorClipboardText}
217
+ getErrorHeadline={getErrorHeadline}
218
+ getErrorStack={getErrorStack}
219
+ />
220
+ ) : (
221
+ <WorkflowInspectorJsonView value={selectedNodeError} emptyLabel={pane.emptyLabel} />
222
+ )
223
+ ) : pane.format === "binary" ? (
224
+ <WorkflowInspectorBinaryView
225
+ attachments={pane.attachments}
226
+ emptyLabel="No binary attachments captured yet."
227
+ />
228
+ ) : pane.format === "pretty" ? (
229
+ <WorkflowInspectorPrettyView value={pane.value} emptyLabel={pane.emptyLabel} />
230
+ ) : (
231
+ <WorkflowInspectorJsonView value={pane.value} emptyLabel={pane.emptyLabel} />
232
+ )}
233
+ </div>
234
+ </section>
235
+ );
236
+ })}
237
+ </div>
238
+ );
239
+ }
@@ -0,0 +1,31 @@
1
+ export function WorkflowExecutionInspectorSidebarResizer(
2
+ props: Readonly<{
3
+ widthPx: number;
4
+ isResizing: boolean;
5
+ onResizeStart: (clientX: number, currentWidth: number) => void;
6
+ }>,
7
+ ) {
8
+ const { isResizing, onResizeStart, widthPx } = props;
9
+ const TREE_RESIZE_HANDLE_WIDTH_PX = 8;
10
+ return (
11
+ <div
12
+ data-testid="workflow-execution-tree-resizer"
13
+ role="separator"
14
+ aria-orientation="vertical"
15
+ aria-label="Resize execution tree"
16
+ onMouseDown={(event) => {
17
+ event.preventDefault();
18
+ onResizeStart(event.clientX, widthPx);
19
+ }}
20
+ style={{
21
+ position: "relative",
22
+ zIndex: 10,
23
+ width: TREE_RESIZE_HANDLE_WIDTH_PX,
24
+ cursor: "col-resize",
25
+ background: isResizing ? "#bfdbfe" : "#e5e7eb",
26
+ borderLeft: "1px solid #d1d5db",
27
+ borderRight: "1px solid #d1d5db",
28
+ }}
29
+ />
30
+ );
31
+ }
@@ -0,0 +1,133 @@
1
+ import Tree from "rc-tree";
2
+ import { WorkflowNodeIconResolver, WorkflowStatusIcon } from "./WorkflowDetailIcons";
3
+ import type {
4
+ ExecutionTreeNode,
5
+ WorkflowExecutionInspectorFormatting,
6
+ WorkflowExecutionInspectorModel,
7
+ } from "../../lib/workflowDetail/workflowDetailTypes";
8
+
9
+ export function WorkflowExecutionInspectorTreePanel(
10
+ props: Readonly<{
11
+ model: Pick<
12
+ WorkflowExecutionInspectorModel,
13
+ "executionTreeData" | "executionTreeExpandedKeys" | "selectedExecutionTreeKey" | "viewContext"
14
+ >;
15
+ formatting: Pick<WorkflowExecutionInspectorFormatting, "formatDurationLabel" | "getNodeDisplayName">;
16
+ onSelectNode: (nodeId: string) => void;
17
+ }>,
18
+ ) {
19
+ const { executionTreeData, executionTreeExpandedKeys, selectedExecutionTreeKey, viewContext } = props.model;
20
+ const { formatDurationLabel, getNodeDisplayName } = props.formatting;
21
+ const { onSelectNode } = props;
22
+ return (
23
+ <div
24
+ data-testid="workflow-execution-tree-panel"
25
+ style={{
26
+ borderRight: "1px solid #d1d5db",
27
+ minWidth: 0,
28
+ overflowX: "hidden",
29
+ overflowY: "auto",
30
+ padding: 12,
31
+ }}
32
+ >
33
+ <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: 0.45, opacity: 0.64, textTransform: "uppercase" }}>
34
+ {viewContext === "live-workflow" ? "Workflow nodes" : "Execution tree"}
35
+ </div>
36
+ <div style={{ marginTop: 10 }}>
37
+ {executionTreeData.length === 0 ? (
38
+ <div style={{ fontSize: 12, opacity: 0.7 }}>
39
+ {viewContext === "live-workflow"
40
+ ? "No workflow nodes available yet."
41
+ : "No node events yet for this execution."}
42
+ </div>
43
+ ) : (
44
+ <Tree<ExecutionTreeNode>
45
+ className="codemation-execution-tree"
46
+ treeData={executionTreeData as ExecutionTreeNode[]}
47
+ showLine
48
+ showIcon={false}
49
+ defaultExpandAll
50
+ expandedKeys={[...executionTreeExpandedKeys]}
51
+ selectable
52
+ selectedKeys={selectedExecutionTreeKey ? [selectedExecutionTreeKey] : []}
53
+ onSelect={(_keys, info) => {
54
+ const workflowNode = (info.node as ExecutionTreeNode).workflowNode;
55
+ onSelectNode(workflowNode?.id ?? String(info.node.key));
56
+ }}
57
+ titleRender={(treeNode) => {
58
+ const isSelected = treeNode.key === selectedExecutionTreeKey;
59
+ const snapshot = treeNode.snapshot;
60
+ const node = treeNode.workflowNode;
61
+ const status = snapshot?.status ?? "pending";
62
+ const durationLabel = formatDurationLabel(snapshot);
63
+ const FallbackIcon = WorkflowNodeIconResolver.resolveFallback(node?.type ?? "", node?.role, node?.icon);
64
+ return (
65
+ <div
66
+ data-testid={`execution-tree-node-${String(treeNode.key)}`}
67
+ onClick={() => {
68
+ onSelectNode(node?.id ?? String(treeNode.key));
69
+ }}
70
+ style={{
71
+ background: isSelected ? "#eff6ff" : "transparent",
72
+ padding: "6px 10px",
73
+ display: "flex",
74
+ alignItems: "center",
75
+ justifyContent: "space-between",
76
+ gap: 10,
77
+ minWidth: 0,
78
+ boxShadow: isSelected ? "inset 2px 0 0 #2563eb" : "none",
79
+ }}
80
+ >
81
+ <div style={{ display: "flex", alignItems: "center", gap: 8, minWidth: 0, flex: "1 1 auto" }}>
82
+ <div
83
+ style={{
84
+ width: 20,
85
+ height: 20,
86
+ display: "grid",
87
+ placeItems: "center",
88
+ color: "#111827",
89
+ background: "#f8fafc",
90
+ flex: "0 0 auto",
91
+ }}
92
+ >
93
+ <FallbackIcon size={14} strokeWidth={1.9} />
94
+ </div>
95
+ <WorkflowStatusIcon status={status} size={15} />
96
+ <div
97
+ style={{
98
+ minWidth: 0,
99
+ fontSize: 13,
100
+ fontWeight: 700,
101
+ color: "#111827",
102
+ overflow: "hidden",
103
+ textOverflow: "ellipsis",
104
+ whiteSpace: "nowrap",
105
+ }}
106
+ >
107
+ {getNodeDisplayName(node, snapshot?.nodeId ?? null)}
108
+ </div>
109
+ </div>
110
+ {durationLabel ? (
111
+ <div
112
+ data-testid={`execution-tree-node-duration-${String(treeNode.key)}`}
113
+ style={{
114
+ flex: "0 0 auto",
115
+ fontSize: 12,
116
+ fontWeight: 700,
117
+ color: "#6b7280",
118
+ whiteSpace: "nowrap",
119
+ textAlign: "right",
120
+ }}
121
+ >
122
+ {durationLabel}
123
+ </div>
124
+ ) : null}
125
+ </div>
126
+ );
127
+ }}
128
+ />
129
+ )}
130
+ </div>
131
+ </div>
132
+ );
133
+ }
@@ -0,0 +1,31 @@
1
+ import type {
2
+ WorkflowExecutionInspectorAttachmentModel,
3
+ WorkflowInspectorAttachmentGroup,
4
+ } from "../../lib/workflowDetail/workflowDetailTypes";
5
+
6
+ export class WorkflowInspectorAttachmentGroupingPresenter {
7
+ static buildGroups(attachments: ReadonlyArray<WorkflowExecutionInspectorAttachmentModel>): Readonly<{
8
+ groups: ReadonlyArray<WorkflowInspectorAttachmentGroup>;
9
+ shouldShowGroupHeadings: boolean;
10
+ }> {
11
+ const groupsByItemIndex = new Map<number, WorkflowExecutionInspectorAttachmentModel[]>();
12
+ for (const attachment of attachments) {
13
+ const existingGroup = groupsByItemIndex.get(attachment.itemIndex);
14
+ if (existingGroup) {
15
+ existingGroup.push(attachment);
16
+ continue;
17
+ }
18
+ groupsByItemIndex.set(attachment.itemIndex, [attachment]);
19
+ }
20
+ const groups = [...groupsByItemIndex.entries()]
21
+ .sort(([leftIndex], [rightIndex]) => leftIndex - rightIndex)
22
+ .map(([itemIndex, itemAttachments]) => ({
23
+ itemIndex,
24
+ attachments: itemAttachments,
25
+ }));
26
+ return {
27
+ groups,
28
+ shouldShowGroupHeadings: groups.length > 1,
29
+ };
30
+ }
31
+ }
@@ -0,0 +1,118 @@
1
+ import { Download } from "lucide-react";
2
+
3
+ import { WorkflowInspectorAttachmentGroupingPresenter } from "./WorkflowInspectorAttachmentGroupingPresenter";
4
+ import type { WorkflowExecutionInspectorAttachmentModel } from "../../lib/workflowDetail/workflowDetailTypes";
5
+
6
+ export function WorkflowInspectorAttachmentList(
7
+ args: Readonly<{ attachments: ReadonlyArray<WorkflowExecutionInspectorAttachmentModel> }>,
8
+ ) {
9
+ if (args.attachments.length === 0) {
10
+ return null;
11
+ }
12
+
13
+ const groupedAttachments = WorkflowInspectorAttachmentGroupingPresenter.buildGroups(args.attachments);
14
+
15
+ return (
16
+ <div data-testid="workflow-inspector-attachments" style={{ display: "grid", gap: 10, marginBottom: 12 }}>
17
+ <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: 0.45, textTransform: "uppercase", opacity: 0.72 }}>
18
+ Attachments
19
+ </div>
20
+ {groupedAttachments.groups.map((group) => (
21
+ <div
22
+ key={`attachment-group-${group.itemIndex}`}
23
+ data-testid={`workflow-inspector-attachment-group-item-${group.itemIndex + 1}`}
24
+ style={{ display: "grid", gap: 10 }}
25
+ >
26
+ {groupedAttachments.shouldShowGroupHeadings ? (
27
+ <div
28
+ data-testid={`workflow-inspector-attachment-group-label-item-${group.itemIndex + 1}`}
29
+ style={{
30
+ fontSize: 11,
31
+ fontWeight: 800,
32
+ letterSpacing: 0.35,
33
+ textTransform: "uppercase",
34
+ color: "#475569",
35
+ }}
36
+ >
37
+ {`Item ${group.itemIndex + 1}`}
38
+ </div>
39
+ ) : null}
40
+ {group.attachments.map((entry) => (
41
+ <div
42
+ key={entry.key}
43
+ data-testid={`workflow-inspector-attachment-${entry.attachment.id}`}
44
+ style={{ border: "1px solid #d1d5db", background: "#ffffff", padding: 12, display: "grid", gap: 10 }}
45
+ >
46
+ <div
47
+ style={{
48
+ display: "flex",
49
+ alignItems: "center",
50
+ justifyContent: "space-between",
51
+ gap: 12,
52
+ flexWrap: "wrap",
53
+ }}
54
+ >
55
+ <div style={{ minWidth: 0 }}>
56
+ <div style={{ fontSize: 13, fontWeight: 700, color: "#111827" }}>{entry.name}</div>
57
+ <div style={{ marginTop: 4, fontSize: 12, color: "#6b7280" }}>
58
+ {`${groupedAttachments.shouldShowGroupHeadings ? "" : `Item ${entry.itemIndex + 1} · `}${entry.attachment.mimeType} · ${entry.attachment.size} bytes`}
59
+ </div>
60
+ </div>
61
+ <a
62
+ data-testid={`workflow-inspector-attachment-link-${entry.attachment.id}`}
63
+ href={entry.contentUrl}
64
+ target="_blank"
65
+ rel="noreferrer"
66
+ style={{
67
+ display: "inline-flex",
68
+ alignItems: "center",
69
+ gap: 6,
70
+ border: "1px solid #d1d5db",
71
+ background: "white",
72
+ color: "#111827",
73
+ padding: "6px 10px",
74
+ fontWeight: 700,
75
+ fontSize: 12,
76
+ textDecoration: "none",
77
+ }}
78
+ >
79
+ <Download size={14} strokeWidth={2.1} />
80
+ {entry.attachment.previewKind === "download" ? "Download" : "Open"}
81
+ </a>
82
+ </div>
83
+ {entry.attachment.previewKind === "image" ? (
84
+ <img
85
+ data-testid={`workflow-inspector-image-preview-${entry.attachment.id}`}
86
+ src={entry.contentUrl}
87
+ alt={entry.attachment.filename ?? entry.name}
88
+ style={{
89
+ maxWidth: "100%",
90
+ maxHeight: 260,
91
+ objectFit: "contain",
92
+ background: "#f8fafc",
93
+ border: "1px solid #e5e7eb",
94
+ }}
95
+ />
96
+ ) : null}
97
+ {entry.attachment.previewKind === "audio" ? (
98
+ <audio
99
+ data-testid={`workflow-inspector-audio-preview-${entry.attachment.id}`}
100
+ controls
101
+ src={entry.contentUrl}
102
+ />
103
+ ) : null}
104
+ {entry.attachment.previewKind === "video" ? (
105
+ <video
106
+ data-testid={`workflow-inspector-video-preview-${entry.attachment.id}`}
107
+ controls
108
+ src={entry.contentUrl}
109
+ style={{ maxWidth: "100%", maxHeight: 260, background: "#020617" }}
110
+ />
111
+ ) : null}
112
+ </div>
113
+ ))}
114
+ </div>
115
+ ))}
116
+ </div>
117
+ );
118
+ }
@@ -0,0 +1,15 @@
1
+ import { WorkflowInspectorAttachmentList } from "./WorkflowInspectorAttachmentList";
2
+ import type { WorkflowExecutionInspectorAttachmentModel } from "../../lib/workflowDetail/workflowDetailTypes";
3
+
4
+ export function WorkflowInspectorBinaryView(
5
+ args: Readonly<{ attachments: ReadonlyArray<WorkflowExecutionInspectorAttachmentModel>; emptyLabel: string }>,
6
+ ) {
7
+ if (args.attachments.length === 0) {
8
+ return (
9
+ <div data-testid="workflow-inspector-empty-state" className="text-sm text-muted-foreground opacity-80">
10
+ {args.emptyLabel}
11
+ </div>
12
+ );
13
+ }
14
+ return <WorkflowInspectorAttachmentList attachments={args.attachments} />;
15
+ }
@@ -0,0 +1,107 @@
1
+ import { Check, Copy } from "lucide-react";
2
+ import { useState } from "react";
3
+
4
+ import type { CopyState, NodeExecutionError } from "../../lib/workflowDetail/workflowDetailTypes";
5
+
6
+ export function WorkflowInspectorErrorView(
7
+ args: Readonly<{
8
+ error: NodeExecutionError | undefined;
9
+ emptyLabel: string;
10
+ getErrorHeadline: (error: NodeExecutionError | undefined) => string;
11
+ getErrorStack: (error: NodeExecutionError | undefined) => string | null;
12
+ getErrorClipboardText: (error: NodeExecutionError | undefined) => string;
13
+ }>,
14
+ ) {
15
+ const { error, emptyLabel, getErrorClipboardText, getErrorHeadline, getErrorStack } = args;
16
+ const [copyState, setCopyState] = useState<CopyState>("idle");
17
+
18
+ if (!error) {
19
+ return (
20
+ <div data-testid="workflow-inspector-empty-state" style={{ opacity: 0.62, fontSize: 13 }}>
21
+ {emptyLabel}
22
+ </div>
23
+ );
24
+ }
25
+
26
+ const headline = getErrorHeadline(error);
27
+ const stack = getErrorStack(error);
28
+
29
+ return (
30
+ <div style={{ height: "100%", minHeight: 0, display: "grid", gridTemplateRows: "auto auto 1fr", gap: 10 }}>
31
+ <div style={{ display: "grid", gap: 8, border: "1px solid #fecaca", background: "#fef2f2", padding: 12 }}>
32
+ <div
33
+ style={{ fontSize: 11, fontWeight: 800, letterSpacing: 0.45, textTransform: "uppercase", color: "#991b1b" }}
34
+ >
35
+ Error
36
+ </div>
37
+ <div
38
+ data-testid="workflow-inspector-error-headline"
39
+ style={{
40
+ fontSize: 13,
41
+ lineHeight: 1.55,
42
+ color: "#111827",
43
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
44
+ }}
45
+ >
46
+ {headline}
47
+ </div>
48
+ </div>
49
+ <div
50
+ style={{ display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: 10 }}
51
+ >
52
+ <div style={{ fontSize: 12, color: "#4b5563" }}>
53
+ {stack ? "Full stacktrace" : "No stacktrace was captured for this error."}
54
+ </div>
55
+ <button
56
+ onClick={() => {
57
+ const value = getErrorClipboardText(error);
58
+ if (!value) return;
59
+ void navigator.clipboard.writeText(value).then(() => {
60
+ setCopyState("copied");
61
+ window.setTimeout(() => setCopyState("idle"), 1500);
62
+ });
63
+ }}
64
+ style={{
65
+ display: "inline-flex",
66
+ alignItems: "center",
67
+ gap: 6,
68
+ border: "1px solid #d1d5db",
69
+ background: "white",
70
+ padding: "6px 10px",
71
+ cursor: "pointer",
72
+ fontWeight: 700,
73
+ fontSize: 12,
74
+ color: "#111827",
75
+ }}
76
+ >
77
+ {copyState === "copied" ? <Check size={14} strokeWidth={2.2} /> : <Copy size={14} strokeWidth={2.2} />}
78
+ {copyState === "copied" ? "Copied" : "Copy stacktrace"}
79
+ </button>
80
+ </div>
81
+ <div
82
+ style={{
83
+ minWidth: 0,
84
+ overflowX: "hidden",
85
+ overflowY: "auto",
86
+ border: "1px solid #d1d5db",
87
+ background: "#0f172a",
88
+ color: "#e2e8f0",
89
+ padding: 12,
90
+ }}
91
+ >
92
+ <pre
93
+ style={{
94
+ margin: 0,
95
+ fontSize: 12,
96
+ lineHeight: 1.65,
97
+ whiteSpace: "pre-wrap",
98
+ wordBreak: "break-word",
99
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
100
+ }}
101
+ >
102
+ {stack ?? headline}
103
+ </pre>
104
+ </div>
105
+ </div>
106
+ );
107
+ }