@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,73 @@
1
+ import type { CSSProperties } from "react";
2
+
3
+ export function WorkflowCanvasCodemationNodeAccents(
4
+ props: Readonly<{
5
+ isActive: boolean;
6
+ isRunning: boolean;
7
+ activityColor: string;
8
+ activityRingStyle: CSSProperties;
9
+ isPropertiesTarget: boolean;
10
+ isActiveForProperties: boolean;
11
+ isSelected: boolean;
12
+ isActiveForSelected: boolean;
13
+ }>,
14
+ ) {
15
+ const {
16
+ activityColor,
17
+ activityRingStyle,
18
+ isActive,
19
+ isActiveForProperties,
20
+ isActiveForSelected,
21
+ isPropertiesTarget,
22
+ isRunning,
23
+ isSelected,
24
+ } = props;
25
+ return (
26
+ <>
27
+ {isActive ? (
28
+ <>
29
+ <div
30
+ style={{
31
+ position: "absolute",
32
+ inset: -8,
33
+ borderRadius: 9,
34
+ pointerEvents: "none",
35
+ boxShadow: `0 0 14px ${activityColor}33, 0 0 28px ${activityColor}22`,
36
+ opacity: isRunning ? 0.85 : 0.48,
37
+ animation: isRunning ? "codemationNodeBreath 2.2s ease-in-out infinite" : "none",
38
+ }}
39
+ />
40
+ <div aria-hidden style={activityRingStyle} />
41
+ </>
42
+ ) : null}
43
+ {isPropertiesTarget ? (
44
+ <div
45
+ aria-hidden
46
+ style={{
47
+ position: "absolute",
48
+ inset: 2,
49
+ borderRadius: 7,
50
+ pointerEvents: "none",
51
+ border: "2px solid #7c3aed",
52
+ boxShadow: "inset 0 0 0 1px rgba(255,255,255,0.78)",
53
+ opacity: isActiveForProperties ? 0.92 : 1,
54
+ }}
55
+ />
56
+ ) : null}
57
+ {isSelected ? (
58
+ <div
59
+ aria-hidden
60
+ style={{
61
+ position: "absolute",
62
+ inset: 4,
63
+ borderRadius: 6,
64
+ pointerEvents: "none",
65
+ border: "2px dashed #f59e0b",
66
+ boxShadow: "inset 0 0 0 1px rgba(255,255,255,0.85)",
67
+ opacity: isActiveForSelected ? 0.95 : 1,
68
+ }}
69
+ />
70
+ ) : null}
71
+ </>
72
+ );
73
+ }
@@ -0,0 +1,43 @@
1
+ import { Handle, Position } from "@xyflow/react";
2
+
3
+ const HANDLE_STYLE = {
4
+ width: 8,
5
+ height: 8,
6
+ border: "1px solid white",
7
+ } as const;
8
+
9
+ /**
10
+ * Bottom LLM/tools sources aligned with the bottom of the main card square (not the full node height).
11
+ * Attachment edges may overlap the agent badge row and node title; users can pan if needed.
12
+ */
13
+ export function WorkflowCanvasCodemationNodeAgentBottomSourceHandles(
14
+ props: Readonly<{ offsetFromNodeBottomPx: number }>,
15
+ ) {
16
+ const { offsetFromNodeBottomPx } = props;
17
+ return (
18
+ <>
19
+ <Handle
20
+ type="source"
21
+ position={Position.Bottom}
22
+ id="attachment-llm-source"
23
+ style={{
24
+ ...HANDLE_STYLE,
25
+ left: "34%",
26
+ background: "#2563eb",
27
+ bottom: offsetFromNodeBottomPx,
28
+ }}
29
+ />
30
+ <Handle
31
+ type="source"
32
+ position={Position.Bottom}
33
+ id="attachment-tools-source"
34
+ style={{
35
+ ...HANDLE_STYLE,
36
+ left: "66%",
37
+ background: "#7c3aed",
38
+ bottom: offsetFromNodeBottomPx,
39
+ }}
40
+ />
41
+ </>
42
+ );
43
+ }
@@ -0,0 +1,47 @@
1
+ import { WORKFLOW_CANVAS_AGENT_BADGE_ROW_PX } from "./lib/workflowCanvasNodeGeometry";
2
+
3
+ export function WorkflowCanvasCodemationNodeAgentLabels() {
4
+ const chip = (color: string, border: string, bg: string, text: string) => (
5
+ <div
6
+ style={{
7
+ padding: "2px 6px",
8
+ fontSize: 10,
9
+ fontWeight: 800,
10
+ letterSpacing: 0.35,
11
+ textTransform: "uppercase",
12
+ color,
13
+ background: bg,
14
+ border: `1px dotted ${border}`,
15
+ whiteSpace: "nowrap",
16
+ borderRadius: 4,
17
+ }}
18
+ >
19
+ {text}
20
+ </div>
21
+ );
22
+ return (
23
+ <div
24
+ style={{
25
+ display: "flex",
26
+ flexDirection: "row",
27
+ alignItems: "center",
28
+ justifyContent: "center",
29
+ gap: 8,
30
+ width: "100%",
31
+ minHeight: WORKFLOW_CANVAS_AGENT_BADGE_ROW_PX,
32
+ paddingLeft: 4,
33
+ paddingRight: 4,
34
+ boxSizing: "border-box",
35
+ pointerEvents: "none",
36
+ }}
37
+ aria-hidden
38
+ >
39
+ <div style={{ flex: "1 1 0", display: "flex", justifyContent: "center", minWidth: 0 }}>
40
+ {chip("#1d4ed8", "#93c5fd", "#eff6ff", "LLM")}
41
+ </div>
42
+ <div style={{ flex: "1 1 0", display: "flex", justifyContent: "center", minWidth: 0 }}>
43
+ {chip("#6d28d9", "#c4b5fd", "#f5f3ff", "Tools")}
44
+ </div>
45
+ </div>
46
+ );
47
+ }
@@ -0,0 +1,202 @@
1
+ import { AlertCircle, FastForward, RefreshCw, ShieldAlert } from "lucide-react";
2
+
3
+ import type { WorkflowCanvasNodeData } from "./lib/workflowCanvasNodeData";
4
+ import {
5
+ WORKFLOW_CANVAS_ATTACHMENT_NODE_ICON_PX,
6
+ WORKFLOW_CANVAS_MAIN_NODE_ICON_PX,
7
+ } from "./lib/workflowCanvasNodeGeometry";
8
+ import { CanvasNodeChromeTooltip } from "./CanvasNodeChromeTooltip";
9
+ import { trailingIconForNode, trailingIconKindForNode } from "./workflowCanvasNodeChrome";
10
+ import { WorkflowCanvasCodemationNodeMainGlyph } from "./WorkflowCanvasCodemationNodeMainGlyph";
11
+
12
+ /** Unified glyph size so policy badges (retry / continue / error) match visually. */
13
+ const POLICY_BADGE_ICON_PX = 10;
14
+
15
+ export function WorkflowCanvasCodemationNodeCard(
16
+ props: Readonly<{ data: WorkflowCanvasNodeData; cardWidthPx: number; cardHeightPx: number }>,
17
+ ) {
18
+ const { cardHeightPx, cardWidthPx, data } = props;
19
+ const isAttachment = data.isAttachment;
20
+ const isAgentInlineTitle = !isAttachment && data.role === "agent";
21
+ const isActive = data.status === "queued" || data.status === "running";
22
+ const isSelected = data.selected;
23
+ const isPropertiesTarget = data.propertiesTarget;
24
+ const isPinned = data.isPinned;
25
+ const iconPx = isAttachment ? WORKFLOW_CANVAS_ATTACHMENT_NODE_ICON_PX : WORKFLOW_CANVAS_MAIN_NODE_ICON_PX;
26
+ const trailing = trailingIconForNode({ status: data.status, isPinned });
27
+ const hasTopBadges =
28
+ Boolean(data.retryPolicySummary) ||
29
+ Boolean(data.hasNodeErrorHandler) ||
30
+ Boolean(data.continueWhenEmptyOutput) ||
31
+ Boolean(data.credentialAttentionTooltip) ||
32
+ Boolean(trailing);
33
+ return (
34
+ <div
35
+ style={{
36
+ position: "relative",
37
+ width: cardWidthPx,
38
+ height: cardHeightPx,
39
+ borderRadius: 7,
40
+ overflow: "visible",
41
+ boxSizing: "border-box",
42
+ }}
43
+ data-testid={`canvas-node-card-${data.nodeId}`}
44
+ data-codemation-node-id={data.nodeId}
45
+ data-codemation-properties-target={isPropertiesTarget ? "true" : "false"}
46
+ data-codemation-node-status={data.status ?? "pending"}
47
+ data-codemation-node-role={data.role ?? "workflowNode"}
48
+ aria-label={`${data.label} (${data.status ?? "pending"})`}
49
+ >
50
+ <div
51
+ style={{
52
+ position: "relative",
53
+ borderRadius: 7,
54
+ overflow: "hidden",
55
+ height: "100%",
56
+ width: "100%",
57
+ border: isActive
58
+ ? "1px solid transparent"
59
+ : isPinned
60
+ ? "1px solid #6d28d9"
61
+ : isSelected
62
+ ? "1px solid #111827"
63
+ : isPropertiesTarget
64
+ ? "1px solid #7c3aed"
65
+ : "1px solid #e2e8f0",
66
+ background: isSelected
67
+ ? isAttachment
68
+ ? "#fffaf0"
69
+ : "#fffdf5"
70
+ : isPropertiesTarget
71
+ ? "#faf5ff"
72
+ : isPinned
73
+ ? "#f5f3ff"
74
+ : isAttachment
75
+ ? "#f8fafc"
76
+ : "#ffffff",
77
+ boxShadow: isActive
78
+ ? "0 1px 2px rgba(15,23,42,0.06), 0 4px 14px rgba(15,23,42,0.06)"
79
+ : isSelected
80
+ ? "0 0 0 1px rgba(245,158,11,0.4) inset, 0 4px 16px rgba(15,23,42,0.1)"
81
+ : isPropertiesTarget || isPinned
82
+ ? "0 0 0 1px rgba(124,58,237,0.18) inset, 0 4px 14px rgba(91,33,182,0.08)"
83
+ : "0 1px 2px rgba(15,23,42,0.05), 0 3px 10px rgba(15,23,42,0.06)",
84
+ }}
85
+ >
86
+ <WorkflowCanvasCodemationNodeMainGlyph data={data} iconPx={iconPx} isAgentInlineTitle={isAgentInlineTitle} />
87
+
88
+ {hasTopBadges ? (
89
+ <div
90
+ style={{
91
+ position: "absolute",
92
+ top: 0,
93
+ left: 0,
94
+ right: 0,
95
+ display: "flex",
96
+ flexDirection: "row",
97
+ justifyContent: "space-between",
98
+ alignItems: "flex-start",
99
+ paddingLeft: 5,
100
+ paddingRight: 5,
101
+ paddingTop: 5,
102
+ gap: 4,
103
+ zIndex: 2,
104
+ pointerEvents: "none",
105
+ }}
106
+ >
107
+ <div
108
+ style={{
109
+ display: "flex",
110
+ flexDirection: "row",
111
+ flexWrap: "wrap",
112
+ justifyContent: "flex-start",
113
+ alignItems: "center",
114
+ gap: 4,
115
+ minHeight: 0,
116
+ pointerEvents: "auto",
117
+ }}
118
+ >
119
+ {data.retryPolicySummary ? (
120
+ <CanvasNodeChromeTooltip
121
+ testId={`canvas-node-policy-retry-${data.nodeId}`}
122
+ ariaLabel="Retry policy"
123
+ tooltip={data.retryPolicySummary}
124
+ >
125
+ <span
126
+ data-testid={`canvas-node-policy-retry-icon-${data.nodeId}`}
127
+ className="inline-flex h-[22px] w-[22px] shrink-0 items-center justify-center rounded border border-teal-200 bg-teal-50 text-teal-700"
128
+ >
129
+ <RefreshCw size={POLICY_BADGE_ICON_PX} strokeWidth={2.1} />
130
+ </span>
131
+ </CanvasNodeChromeTooltip>
132
+ ) : null}
133
+ {data.hasNodeErrorHandler ? (
134
+ <CanvasNodeChromeTooltip
135
+ testId={`canvas-node-policy-error-handler-${data.nodeId}`}
136
+ ariaLabel="Node error handler"
137
+ tooltip="Node error handler configured"
138
+ >
139
+ <span
140
+ data-testid={`canvas-node-policy-error-handler-icon-${data.nodeId}`}
141
+ className="inline-flex h-[22px] w-[22px] shrink-0 items-center justify-center rounded border border-violet-200 bg-violet-50 text-violet-700"
142
+ >
143
+ <ShieldAlert size={POLICY_BADGE_ICON_PX} strokeWidth={2.1} />
144
+ </span>
145
+ </CanvasNodeChromeTooltip>
146
+ ) : null}
147
+ {data.continueWhenEmptyOutput ? (
148
+ <CanvasNodeChromeTooltip
149
+ testId={`canvas-node-policy-continue-empty-${data.nodeId}`}
150
+ ariaLabel="Continue when empty"
151
+ tooltip="Downstream continues even when this node emits no items on main output."
152
+ >
153
+ <span
154
+ data-testid={`canvas-node-continue-empty-icon-${data.nodeId}`}
155
+ className="inline-flex h-[22px] w-[22px] shrink-0 items-center justify-center rounded border border-sky-200 bg-sky-50 text-sky-800"
156
+ >
157
+ <FastForward size={POLICY_BADGE_ICON_PX} strokeWidth={2.1} />
158
+ </span>
159
+ </CanvasNodeChromeTooltip>
160
+ ) : null}
161
+ </div>
162
+ <div
163
+ style={{
164
+ display: "flex",
165
+ flexDirection: "row",
166
+ flexWrap: "wrap",
167
+ justifyContent: "flex-end",
168
+ alignItems: "center",
169
+ gap: 4,
170
+ pointerEvents: "auto",
171
+ }}
172
+ >
173
+ {data.credentialAttentionTooltip ? (
174
+ <CanvasNodeChromeTooltip
175
+ testId={`canvas-node-credential-attention-${data.nodeId}`}
176
+ ariaLabel="Credential required"
177
+ tooltip={data.credentialAttentionTooltip}
178
+ >
179
+ <span
180
+ data-testid={`canvas-node-credential-attention-icon-${data.nodeId}`}
181
+ className="inline-flex h-[22px] w-[22px] shrink-0 items-center justify-center rounded border border-amber-300 bg-amber-50 text-amber-900"
182
+ >
183
+ <AlertCircle size={POLICY_BADGE_ICON_PX} strokeWidth={2.1} />
184
+ </span>
185
+ </CanvasNodeChromeTooltip>
186
+ ) : null}
187
+ {trailing ? (
188
+ <div
189
+ data-testid={`canvas-node-trailing-icon-${data.nodeId}`}
190
+ data-icon-kind={trailingIconKindForNode({ status: data.status, isPinned })}
191
+ style={{ display: "grid", placeItems: "center", color: "#111827" }}
192
+ >
193
+ {trailing}
194
+ </div>
195
+ ) : null}
196
+ </div>
197
+ </div>
198
+ ) : null}
199
+ </div>
200
+ </div>
201
+ );
202
+ }
@@ -0,0 +1,77 @@
1
+ import { Handle, Position } from "@xyflow/react";
2
+
3
+ const HANDLE_BOX_STYLE = {
4
+ width: 8,
5
+ height: 8,
6
+ background: "#111827",
7
+ border: "1px solid white",
8
+ } as const;
9
+
10
+ /** Single centered handle on an edge so true/false (or merge inputs) share one symmetric opening. */
11
+ const HANDLE_CENTERED_STYLE = {
12
+ ...HANDLE_BOX_STYLE,
13
+ top: "50%",
14
+ } as const;
15
+
16
+ export function WorkflowCanvasCodemationNodeHandles(
17
+ props: Readonly<{
18
+ kind: string;
19
+ isAttachment: boolean;
20
+ isAgent: boolean;
21
+ /** When true, agent bottom LLM/tools handles are rendered on the shell (see WorkflowCanvasCodemationNodeAgentBottomSourceHandles). */
22
+ omitAgentBottomSourceHandles: boolean;
23
+ sourceOutputPorts: readonly string[];
24
+ targetInputPorts: readonly string[];
25
+ }>,
26
+ ) {
27
+ const { isAgent, isAttachment, omitAgentBottomSourceHandles, sourceOutputPorts, targetInputPorts, kind } = props;
28
+
29
+ if (isAttachment) {
30
+ return (
31
+ <Handle
32
+ type="target"
33
+ position={Position.Top}
34
+ id="attachment-target"
35
+ style={{ width: 8, height: 8, background: "#64748b", border: "1px solid white" }}
36
+ />
37
+ );
38
+ }
39
+
40
+ const isTrigger = kind === "trigger";
41
+
42
+ const targetHandles = isTrigger ? null : targetInputPorts.length <= 1 ? (
43
+ <Handle type="target" position={Position.Left} id={targetInputPorts[0] ?? "in"} style={HANDLE_BOX_STYLE} />
44
+ ) : (
45
+ <Handle type="target" position={Position.Left} style={HANDLE_CENTERED_STYLE} />
46
+ );
47
+
48
+ const sourceHandlesRight =
49
+ sourceOutputPorts.length <= 1 ? (
50
+ <Handle type="source" position={Position.Right} id={sourceOutputPorts[0] ?? "main"} style={HANDLE_BOX_STYLE} />
51
+ ) : (
52
+ <Handle type="source" position={Position.Right} style={HANDLE_CENTERED_STYLE} />
53
+ );
54
+
55
+ return (
56
+ <>
57
+ {targetHandles}
58
+ {sourceHandlesRight}
59
+ {isAgent && !omitAgentBottomSourceHandles ? (
60
+ <>
61
+ <Handle
62
+ type="source"
63
+ position={Position.Bottom}
64
+ id="attachment-llm-source"
65
+ style={{ left: "34%", width: 8, height: 8, background: "#2563eb", border: "1px solid white" }}
66
+ />
67
+ <Handle
68
+ type="source"
69
+ position={Position.Bottom}
70
+ id="attachment-tools-source"
71
+ style={{ left: "66%", width: 8, height: 8, background: "#7c3aed", border: "1px solid white" }}
72
+ />
73
+ </>
74
+ ) : null}
75
+ </>
76
+ );
77
+ }
@@ -0,0 +1,51 @@
1
+ import type { WorkflowCanvasNodeData } from "./lib/workflowCanvasNodeData";
2
+ import {
3
+ WORKFLOW_CANVAS_ATTACHMENT_NODE_LABEL_FONT_PX,
4
+ WORKFLOW_CANVAS_ATTACHMENT_NODE_LABEL_GAP_PX,
5
+ WORKFLOW_CANVAS_ATTACHMENT_NODE_LABEL_LINE_HEIGHT,
6
+ WORKFLOW_CANVAS_MAIN_NODE_LABEL_FONT_PX,
7
+ WORKFLOW_CANVAS_MAIN_NODE_LABEL_GAP_PX,
8
+ WORKFLOW_CANVAS_MAIN_NODE_LABEL_LINE_HEIGHT,
9
+ } from "./lib/workflowCanvasNodeGeometry";
10
+
11
+ export function WorkflowCanvasCodemationNodeLabelBelow(
12
+ props: Readonly<{ data: WorkflowCanvasNodeData; maxWidthPx: number }>,
13
+ ) {
14
+ const { data, maxWidthPx } = props;
15
+ const isAttachment = data.isAttachment;
16
+ const isMainAgent = !isAttachment && data.role === "agent";
17
+ if (isMainAgent) {
18
+ return null;
19
+ }
20
+ const fontSize = isAttachment
21
+ ? WORKFLOW_CANVAS_ATTACHMENT_NODE_LABEL_FONT_PX
22
+ : WORKFLOW_CANVAS_MAIN_NODE_LABEL_FONT_PX;
23
+ const lineHeight = isAttachment
24
+ ? WORKFLOW_CANVAS_ATTACHMENT_NODE_LABEL_LINE_HEIGHT
25
+ : WORKFLOW_CANVAS_MAIN_NODE_LABEL_LINE_HEIGHT;
26
+ const marginTopPx = isAttachment
27
+ ? WORKFLOW_CANVAS_ATTACHMENT_NODE_LABEL_GAP_PX
28
+ : WORKFLOW_CANVAS_MAIN_NODE_LABEL_GAP_PX;
29
+ return (
30
+ <div
31
+ data-testid={`canvas-node-label-${data.nodeId}`}
32
+ style={{
33
+ width: "100%",
34
+ maxWidth: maxWidthPx,
35
+ marginTop: marginTopPx,
36
+ paddingLeft: 4,
37
+ paddingRight: 4,
38
+ textAlign: "center",
39
+ fontWeight: 700,
40
+ fontSize,
41
+ lineHeight,
42
+ color: "#0f172a",
43
+ whiteSpace: "normal",
44
+ overflowWrap: "anywhere",
45
+ wordBreak: "break-word",
46
+ }}
47
+ >
48
+ {data.label}
49
+ </div>
50
+ );
51
+ }
@@ -0,0 +1,64 @@
1
+ import type { WorkflowCanvasNodeData } from "./lib/workflowCanvasNodeData";
2
+ import {
3
+ WORKFLOW_CANVAS_MAIN_NODE_LABEL_FONT_PX,
4
+ WORKFLOW_CANVAS_NODE_ICON_STROKE_WIDTH,
5
+ } from "./lib/workflowCanvasNodeGeometry";
6
+ import { WorkflowCanvasNodeIcon } from "./WorkflowCanvasNodeIcon";
7
+
8
+ export function WorkflowCanvasCodemationNodeMainGlyph(
9
+ props: Readonly<{ data: WorkflowCanvasNodeData; iconPx: number; isAgentInlineTitle: boolean }>,
10
+ ) {
11
+ const { data, iconPx, isAgentInlineTitle } = props;
12
+ return (
13
+ <div
14
+ style={{
15
+ position: "absolute",
16
+ inset: 0,
17
+ display: "flex",
18
+ flexDirection: "row",
19
+ alignItems: "center",
20
+ justifyContent: isAgentInlineTitle ? "flex-start" : "center",
21
+ paddingLeft: isAgentInlineTitle ? 12 : 0,
22
+ paddingRight: isAgentInlineTitle ? 12 : 0,
23
+ gap: isAgentInlineTitle ? 8 : 0,
24
+ pointerEvents: "none",
25
+ zIndex: 1,
26
+ backgroundColor: "transparent",
27
+ }}
28
+ >
29
+ <div
30
+ style={{
31
+ position: "relative",
32
+ display: "flex",
33
+ flexShrink: 0,
34
+ alignItems: "center",
35
+ justifyContent: "center",
36
+ width: isAgentInlineTitle ? undefined : "100%",
37
+ height: isAgentInlineTitle ? undefined : "100%",
38
+ flex: isAgentInlineTitle ? undefined : "1 1 auto",
39
+ }}
40
+ >
41
+ <WorkflowCanvasNodeIcon icon={data.icon} sizePx={iconPx} strokeWidth={WORKFLOW_CANVAS_NODE_ICON_STROKE_WIDTH} />
42
+ </div>
43
+ {isAgentInlineTitle ? (
44
+ <span
45
+ data-testid={`canvas-node-inline-title-${data.nodeId}`}
46
+ style={{
47
+ flex: 1,
48
+ minWidth: 0,
49
+ fontSize: WORKFLOW_CANVAS_MAIN_NODE_LABEL_FONT_PX,
50
+ fontWeight: 700,
51
+ lineHeight: 1.2,
52
+ color: "#0f172a",
53
+ overflow: "hidden",
54
+ textOverflow: "ellipsis",
55
+ whiteSpace: "nowrap",
56
+ textAlign: "left",
57
+ }}
58
+ >
59
+ {data.label}
60
+ </span>
61
+ ) : null}
62
+ </div>
63
+ );
64
+ }
@@ -0,0 +1,95 @@
1
+ import { KeyRound, Pencil, Pin, PinOff, Play } from "lucide-react";
2
+
3
+ import type { WorkflowCanvasNodeData } from "./lib/workflowCanvasNodeData";
4
+ import { WorkflowCanvasToolbarIconButton } from "./WorkflowCanvasToolbarIconButton";
5
+
6
+ export function WorkflowCanvasCodemationNodeToolbar(
7
+ props: Readonly<{
8
+ data: WorkflowCanvasNodeData;
9
+ isPinned: boolean;
10
+ isToolbarVisible: boolean;
11
+ setHasToolbarFocus: (value: boolean) => void;
12
+ }>,
13
+ ) {
14
+ const { data, isPinned, isToolbarVisible, setHasToolbarFocus } = props;
15
+ return (
16
+ <div
17
+ data-testid={`canvas-node-toolbar-${data.nodeId}`}
18
+ style={{
19
+ position: "absolute",
20
+ top: -34,
21
+ right: 0,
22
+ display: "flex",
23
+ alignItems: "center",
24
+ gap: 4,
25
+ opacity: isToolbarVisible ? 1 : 0,
26
+ transform: isToolbarVisible ? "translateY(0)" : "translateY(3px)",
27
+ transition: "opacity 90ms ease-out, transform 90ms ease-out",
28
+ pointerEvents: isToolbarVisible ? "auto" : "none",
29
+ padding: 4,
30
+ background: "rgba(255,255,255,0.96)",
31
+ boxShadow: "0 8px 18px rgba(15,23,42,0.12)",
32
+ zIndex: 30,
33
+ }}
34
+ >
35
+ {data.showCredentialEditToolbar && data.onOpenCredentialEditFromCanvas ? (
36
+ <WorkflowCanvasToolbarIconButton
37
+ testId={`canvas-node-credential-edit-button-${data.nodeId}`}
38
+ ariaLabel={`Edit credential for ${data.label}`}
39
+ tooltip="Edit credential"
40
+ onAfterClick={() => setHasToolbarFocus(false)}
41
+ onClick={(event) => {
42
+ event.stopPropagation();
43
+ data.onSelectNode(data.nodeId);
44
+ data.onOpenCredentialEditFromCanvas?.();
45
+ }}
46
+ accentColor="#b45309"
47
+ >
48
+ <KeyRound size={12} strokeWidth={2.1} />
49
+ </WorkflowCanvasToolbarIconButton>
50
+ ) : null}
51
+ <WorkflowCanvasToolbarIconButton
52
+ testId={`canvas-node-run-button-${data.nodeId}`}
53
+ ariaLabel={`Run to ${data.label}`}
54
+ tooltip={data.isRunning ? "Run disabled while workflow is running" : "Run from here"}
55
+ onAfterClick={() => setHasToolbarFocus(false)}
56
+ onClick={(event) => {
57
+ event.stopPropagation();
58
+ data.onSelectNode(data.nodeId);
59
+ data.onRunNode(data.nodeId);
60
+ }}
61
+ disabled={data.isRunning}
62
+ >
63
+ <Play size={12} strokeWidth={2.1} />
64
+ </WorkflowCanvasToolbarIconButton>
65
+ <WorkflowCanvasToolbarIconButton
66
+ testId={`${isPinned ? "canvas-node-unpin-button" : "canvas-node-pin-button"}-${data.nodeId}`}
67
+ ariaLabel={`${isPinned ? "Unpin" : "Pin"} ${data.label}`}
68
+ tooltip={!data.hasOutputData ? "No output to pin yet" : isPinned ? "Unpin output" : "Pin current output"}
69
+ onAfterClick={() => setHasToolbarFocus(false)}
70
+ onClick={(event) => {
71
+ event.stopPropagation();
72
+ data.onSelectNode(data.nodeId);
73
+ data.onTogglePinnedOutput(data.nodeId);
74
+ }}
75
+ disabled={!data.hasOutputData}
76
+ accentColor="#6d28d9"
77
+ >
78
+ {isPinned ? <PinOff size={12} strokeWidth={2.3} fill="currentColor" /> : <Pin size={12} strokeWidth={2} />}
79
+ </WorkflowCanvasToolbarIconButton>
80
+ <WorkflowCanvasToolbarIconButton
81
+ testId={`canvas-node-edit-button-${data.nodeId}`}
82
+ ariaLabel={`Edit ${data.label}`}
83
+ tooltip="Edit output"
84
+ onAfterClick={() => setHasToolbarFocus(false)}
85
+ onClick={(event) => {
86
+ event.stopPropagation();
87
+ data.onSelectNode(data.nodeId);
88
+ data.onEditNodeOutput(data.nodeId);
89
+ }}
90
+ >
91
+ <Pencil size={12} strokeWidth={2} />
92
+ </WorkflowCanvasToolbarIconButton>
93
+ </div>
94
+ );
95
+ }