@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,70 @@
1
+ const RUN_PARAM = "run";
2
+ const PANE_PARAM = "pane";
3
+ const NODE_PARAM = "node";
4
+
5
+ export type WorkflowDetailUrlLocation = Readonly<{
6
+ selectedRunId: string | null;
7
+ isRunsPaneVisible: boolean;
8
+ nodeId: string | null;
9
+ }>;
10
+
11
+ /** Minimal surface for `URLSearchParams` / Next `ReadonlyURLSearchParams`. */
12
+ export type WorkflowDetailSearchParamsInput = Readonly<{
13
+ get(name: string): string | null;
14
+ toString(): string;
15
+ }>;
16
+
17
+ /**
18
+ * Encodes and decodes workflow detail shareable query params (`run`, `pane`, `node`).
19
+ */
20
+ export class WorkflowDetailUrlCodec {
21
+ static parseSearchParams(searchParams: WorkflowDetailSearchParamsInput): WorkflowDetailUrlLocation {
22
+ const runRaw = searchParams.get(RUN_PARAM);
23
+ const selectedRunId = runRaw !== null && runRaw.trim() !== "" ? runRaw.trim() : null;
24
+ const paneRaw = searchParams.get(PANE_PARAM);
25
+ const pane: "live" | "executions" | null = paneRaw === "live" || paneRaw === "executions" ? paneRaw : null;
26
+ const nodeRaw = searchParams.get(NODE_PARAM);
27
+ const nodeId = nodeRaw !== null && nodeRaw.trim() !== "" ? nodeRaw.trim() : null;
28
+ const isRunsPaneVisible = selectedRunId !== null || pane === "executions";
29
+ return {
30
+ selectedRunId,
31
+ isRunsPaneVisible,
32
+ nodeId,
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Copies `base`, removes workflow-detail keys, then applies `location`.
38
+ * Preserves unrelated query keys (e.g. future filters).
39
+ */
40
+ static mergeLocationIntoSearchParams(base: URLSearchParams, location: WorkflowDetailUrlLocation): URLSearchParams {
41
+ const next = new URLSearchParams(base.toString());
42
+ next.delete(RUN_PARAM);
43
+ next.delete(PANE_PARAM);
44
+ next.delete(NODE_PARAM);
45
+ if (location.selectedRunId) {
46
+ next.set(RUN_PARAM, location.selectedRunId);
47
+ } else if (location.isRunsPaneVisible) {
48
+ next.set(PANE_PARAM, "executions");
49
+ }
50
+ if (location.nodeId) {
51
+ next.set(NODE_PARAM, location.nodeId);
52
+ }
53
+ return next;
54
+ }
55
+
56
+ static toQueryString(searchParams: URLSearchParams): string {
57
+ const s = searchParams.toString();
58
+ return s;
59
+ }
60
+
61
+ static buildHref(
62
+ pathname: string,
63
+ base: WorkflowDetailSearchParamsInput,
64
+ location: WorkflowDetailUrlLocation,
65
+ ): string {
66
+ const merged = WorkflowDetailUrlCodec.mergeLocationIntoSearchParams(new URLSearchParams(base.toString()), location);
67
+ const qs = merged.toString();
68
+ return qs.length > 0 ? `${pathname}?${qs}` : pathname;
69
+ }
70
+ }
@@ -0,0 +1,152 @@
1
+ import type { BinaryAttachment } from "@codemation/core/browser";
2
+ import type { FieldDataNode } from "rc-tree";
3
+ import type { ReactNode } from "react";
4
+ import type {
5
+ Items,
6
+ NodeExecutionSnapshot,
7
+ PersistedRunState,
8
+ RunSummary,
9
+ WorkflowDto,
10
+ } from "../../hooks/realtime/realtime";
11
+
12
+ export type InspectorTab = "input" | "output";
13
+ export type InspectorMode = InspectorTab | "split";
14
+ export type InspectorFormat = "json" | "pretty" | "binary";
15
+ export type CopyState = "idle" | "copied";
16
+ export type ViewedWorkflowContext = "live-workflow" | "historical-run";
17
+ export type PortEntries = ReadonlyArray<readonly [string, Items]>;
18
+ export type WorkflowNode = WorkflowDto["nodes"][number];
19
+ export type WorkflowDiagramNode = WorkflowDto["nodes"][number];
20
+ export type ExecutionNode = Readonly<{
21
+ node: WorkflowNode;
22
+ snapshot?: NodeExecutionSnapshot;
23
+ /** Stable workflow attachment id when `node.id` is a synthetic per-invocation id. */
24
+ workflowConnectionNodeId?: string;
25
+ }>;
26
+ export type NodeExecutionError = NonNullable<NodeExecutionSnapshot["error"]>;
27
+ export type JsonEditorMode = "pin-output" | "workflow-snapshot";
28
+ /** Per-output-item binary maps for the pin-output dialog (parallel to parsed JSON items). */
29
+ export type PinBinaryMapsByItemIndex = ReadonlyArray<Readonly<Record<string, BinaryAttachment>>>;
30
+ export type JsonEditorState = Readonly<
31
+ | {
32
+ mode: "workflow-snapshot";
33
+ title: string;
34
+ value: string;
35
+ }
36
+ | {
37
+ mode: "pin-output";
38
+ title: string;
39
+ value: string;
40
+ workflowId: string;
41
+ nodeId: string;
42
+ binaryMapsByItemIndex: PinBinaryMapsByItemIndex;
43
+ }
44
+ >;
45
+ export type ExecutionTreeNode = FieldDataNode<
46
+ Readonly<{
47
+ key: string;
48
+ title?: ReactNode;
49
+ workflowNode?: WorkflowNode;
50
+ snapshot?: NodeExecutionSnapshot;
51
+ }>
52
+ >;
53
+ export type WorkflowRunsSidebarSelectedRun =
54
+ | Pick<PersistedRunState, "workflowSnapshot" | "executionOptions">
55
+ | undefined;
56
+ export type WorkflowRunsSidebarRun = RunSummary;
57
+ export type WorkflowRunsSidebarModel = Readonly<{
58
+ workflowId: string;
59
+ displayedWorkflow: WorkflowDto | undefined;
60
+ workflow: WorkflowDto | undefined;
61
+ workflowError: string | null;
62
+ error: string | null;
63
+ displayedRuns: ReadonlyArray<RunSummary> | undefined;
64
+ runsError: string | null;
65
+ selectedRunId: string | null;
66
+ selectedRun: PersistedRunState | undefined;
67
+ }>;
68
+ export type WorkflowRunsSidebarFormatting = Readonly<{
69
+ formatDateTime: (value: string | undefined) => string;
70
+ formatRunListWhen: (value: string | undefined) => string;
71
+ formatRunListDurationLine: (run: Pick<RunSummary, "startedAt" | "finishedAt" | "status">) => string;
72
+ getExecutionModeLabel: (
73
+ run: Pick<RunSummary, "executionOptions"> | Pick<PersistedRunState, "executionOptions"> | undefined,
74
+ ) => string | null;
75
+ }>;
76
+ export type WorkflowRunsSidebarActions = Readonly<{
77
+ onSelectRun: (runId: string) => void;
78
+ }>;
79
+ export type WorkflowExecutionInspectorNodeActionsModel = Readonly<{
80
+ viewContext: ViewedWorkflowContext;
81
+ isRunning: boolean;
82
+ canEditOutput: boolean;
83
+ canClearPinnedOutput: boolean;
84
+ }>;
85
+ export type WorkflowExecutionInspectorPaneModel = Readonly<{
86
+ tab: InspectorTab;
87
+ format: InspectorFormat;
88
+ selectedPort: string | null;
89
+ portEntries: PortEntries;
90
+ value: unknown;
91
+ attachments: ReadonlyArray<WorkflowExecutionInspectorAttachmentModel>;
92
+ emptyLabel: string;
93
+ showsError: boolean;
94
+ }>;
95
+ export type WorkflowExecutionInspectorAttachmentModel = Readonly<{
96
+ key: string;
97
+ itemIndex: number;
98
+ name: string;
99
+ contentUrl: string;
100
+ attachment: BinaryAttachment;
101
+ }>;
102
+ export type PrettyJsonTreeNode = Readonly<{
103
+ key: string;
104
+ label: string;
105
+ isLeaf: boolean;
106
+ inlineValue?: ReactNode;
107
+ multilineValue?: string;
108
+ children?: ReadonlyArray<PrettyJsonTreeNode>;
109
+ }>;
110
+ export type WorkflowInspectorAttachmentGroup = Readonly<{
111
+ itemIndex: number;
112
+ attachments: ReadonlyArray<WorkflowExecutionInspectorAttachmentModel>;
113
+ }>;
114
+ export type WorkflowExecutionInspectorModel = Readonly<{
115
+ workflowId: string;
116
+ viewContext: ViewedWorkflowContext;
117
+ selectedRunId: string | null;
118
+ isLoading: boolean;
119
+ loadError: string | null;
120
+ selectedRun: PersistedRunState | undefined;
121
+ selectedNodeId: string | null;
122
+ selectedNodeSnapshot: NodeExecutionSnapshot | undefined;
123
+ selectedWorkflowNode: WorkflowNode | undefined;
124
+ selectedPinnedOutput: Items | undefined;
125
+ selectedNodeError: NodeExecutionError | undefined;
126
+ selectedMode: InspectorMode;
127
+ inputPane: WorkflowExecutionInspectorPaneModel;
128
+ outputPane: WorkflowExecutionInspectorPaneModel;
129
+ executionTreeData: ReadonlyArray<ExecutionTreeNode>;
130
+ executionTreeExpandedKeys: ReadonlyArray<string>;
131
+ /** rc-tree key for the selected execution row (may differ from {@link selectedNodeId} when keys are disambiguated). */
132
+ selectedExecutionTreeKey: string | null;
133
+ nodeActions: WorkflowExecutionInspectorNodeActionsModel;
134
+ }>;
135
+ export type WorkflowExecutionInspectorFormatting = Readonly<{
136
+ formatDateTime: (value: string | undefined) => string;
137
+ formatDurationLabel: (snapshot: NodeExecutionSnapshot | undefined) => string | null;
138
+ getNodeDisplayName: (node: WorkflowNode | undefined, fallback: string | null) => string;
139
+ getSnapshotTimestamp: (snapshot: NodeExecutionSnapshot | undefined) => string | undefined;
140
+ getErrorHeadline: (error: NodeExecutionError | undefined) => string;
141
+ getErrorStack: (error: NodeExecutionError | undefined) => string | null;
142
+ getErrorClipboardText: (error: NodeExecutionError | undefined) => string;
143
+ }>;
144
+ export type WorkflowExecutionInspectorActions = Readonly<{
145
+ onSelectNode: (nodeId: string) => void;
146
+ onEditSelectedOutput: () => void;
147
+ onClearPinnedOutput: () => void;
148
+ onSelectMode: (mode: InspectorMode) => void;
149
+ onSelectFormat: (tab: InspectorTab, format: InspectorFormat) => void;
150
+ onSelectInputPort: (portName: string) => void;
151
+ onSelectOutputPort: (portName: string) => void;
152
+ }>;
@@ -0,0 +1,65 @@
1
+ export const WORKFLOW_DETAIL_TREE_STYLES = `
2
+ @keyframes codemationSpin {
3
+ from { transform: rotate(0deg); }
4
+ to { transform: rotate(360deg); }
5
+ }
6
+
7
+ .codemation-execution-tree,
8
+ .codemation-json-tree {
9
+ background: transparent;
10
+ border: none;
11
+ font-family: inherit;
12
+ }
13
+
14
+ .codemation-execution-tree .rc-tree-node-content-wrapper,
15
+ .codemation-json-tree .rc-tree-node-content-wrapper {
16
+ display: inline-block;
17
+ width: calc(100% - 18px);
18
+ height: auto;
19
+ padding: 0;
20
+ line-height: 1.2;
21
+ vertical-align: top;
22
+ }
23
+
24
+ .codemation-execution-tree .rc-tree-switcher,
25
+ .codemation-json-tree .rc-tree-switcher {
26
+ width: 12px;
27
+ margin-right: 6px;
28
+ }
29
+
30
+ .codemation-execution-tree .rc-tree-treenode,
31
+ .codemation-json-tree .rc-tree-treenode {
32
+ padding: 0 0 4px;
33
+ line-height: normal;
34
+ }
35
+
36
+ .codemation-execution-tree .rc-tree-treenode {
37
+ white-space: nowrap;
38
+ }
39
+
40
+ .codemation-json-tree .rc-tree-treenode {
41
+ white-space: normal;
42
+ }
43
+
44
+ .codemation-execution-tree .rc-tree-title,
45
+ .codemation-json-tree .rc-tree-title {
46
+ display: block;
47
+ width: 100%;
48
+ }
49
+
50
+ .codemation-execution-tree .rc-tree-treenode ul,
51
+ .codemation-json-tree .rc-tree-treenode ul {
52
+ padding-left: 20px;
53
+ }
54
+
55
+ .codemation-execution-tree .rc-tree-node-selected {
56
+ background: transparent;
57
+ box-shadow: none;
58
+ opacity: 1;
59
+ }
60
+
61
+ .codemation-execution-tree .rc-tree-node-content-wrapper:hover,
62
+ .codemation-json-tree .rc-tree-node-content-wrapper:hover {
63
+ background: transparent;
64
+ }
65
+ `;
@@ -0,0 +1,236 @@
1
+ "use client";
2
+
3
+ import { useEffect, useMemo, useRef } from "react";
4
+
5
+ import { Button } from "@/components/ui/button";
6
+ import { cn } from "@/lib/utils";
7
+
8
+ import { WorkflowCanvas } from "../components/canvas/WorkflowCanvas";
9
+ import type { WorkflowDto } from "../hooks/realtime/realtime";
10
+ import { NodePropertiesSlidePanel } from "../components/workflowDetail/NodePropertiesSlidePanel";
11
+ import { useWorkflowDetailController } from "../hooks/workflowDetail/useWorkflowDetailController";
12
+ import { WorkflowJsonEditorDialog } from "../components/workflowDetail/WorkflowJsonEditorDialog";
13
+ import { WorkflowRunsSidebar } from "../components/workflowDetail/WorkflowRunsSidebar";
14
+ import { WORKFLOW_DETAIL_TREE_STYLES } from "../lib/workflowDetailTreeStyles";
15
+ import { WorkflowDetailScreenInspectorPanel } from "./WorkflowDetailScreenInspectorPanel";
16
+ import { useWorkflowDetailChromeDispatch } from "../../../shell/WorkflowDetailChromeContext";
17
+
18
+ export function WorkflowDetailScreen(args: Readonly<{ workflowId: string; initialWorkflow?: WorkflowDto }>) {
19
+ const controller = useWorkflowDetailController(args);
20
+ const setChrome = useWorkflowDetailChromeDispatch();
21
+ const controllerRef = useRef(controller);
22
+ controllerRef.current = controller;
23
+
24
+ const activationAlertKey = (controller.workflowActivationAlertLines ?? []).join("\u0000");
25
+ const credentialAttentionKey = controller.credentialAttentionSummaryLines.join("\u0000");
26
+
27
+ const chromeStateKey = useMemo(
28
+ () =>
29
+ [
30
+ controller.isLiveWorkflowView,
31
+ controller.workflowIsActive,
32
+ controller.isWorkflowActivationPending,
33
+ activationAlertKey,
34
+ credentialAttentionKey,
35
+ ].join("|"),
36
+ [
37
+ controller.isLiveWorkflowView,
38
+ controller.workflowIsActive,
39
+ controller.isWorkflowActivationPending,
40
+ activationAlertKey,
41
+ credentialAttentionKey,
42
+ ],
43
+ );
44
+
45
+ useEffect(() => {
46
+ if (!setChrome) {
47
+ return;
48
+ }
49
+ const c = controllerRef.current;
50
+ setChrome({
51
+ isLiveWorkflowView: c.isLiveWorkflowView,
52
+ workflowIsActive: c.workflowIsActive,
53
+ isWorkflowActivationPending: c.isWorkflowActivationPending,
54
+ setWorkflowActive: (active) => {
55
+ controllerRef.current.setWorkflowActive(active);
56
+ },
57
+ workflowActivationAlertLines: c.workflowActivationAlertLines,
58
+ dismissWorkflowActivationAlert: () => {
59
+ controllerRef.current.dismissWorkflowActivationAlert();
60
+ },
61
+ credentialAttentionSummaryLines: c.credentialAttentionSummaryLines,
62
+ });
63
+ }, [setChrome, chromeStateKey]);
64
+
65
+ useEffect(() => {
66
+ return () => {
67
+ setChrome?.(null);
68
+ };
69
+ }, [setChrome]);
70
+
71
+ const activeCanvasTab = controller.isRunsPaneVisible ? "executions" : "live";
72
+ const shouldShowRealtimeBadge = controller.isLiveWorkflowView && !controller.isRunsPaneVisible;
73
+ const realtimeBadge =
74
+ controller.workflowDevBuildState.state === "failed"
75
+ ? {
76
+ className: "border-destructive/40 bg-destructive/10 text-destructive shadow-md ring-1 ring-foreground/10",
77
+ label: "Rebuild failed. Latest code is not live yet.",
78
+ testId: "workflow-dev-build-failed-indicator",
79
+ }
80
+ : controller.showRealtimeDisconnectedBadge
81
+ ? {
82
+ className:
83
+ "border-amber-400/60 bg-amber-50 text-amber-950 shadow-md ring-1 ring-foreground/10 dark:bg-amber-950/20 dark:text-amber-100",
84
+ label: "Realtime disconnected. Workflow edits won't auto-refresh.",
85
+ testId: "workflow-realtime-disconnected-indicator",
86
+ }
87
+ : controller.workflowDevBuildState.state === "building"
88
+ ? {
89
+ className: "border-primary/40 bg-primary/10 text-primary shadow-md ring-1 ring-foreground/10",
90
+ label: "Rebuilding workflow...",
91
+ testId: "workflow-dev-build-started-indicator",
92
+ }
93
+ : null;
94
+
95
+ return (
96
+ <main className="h-full w-full min-h-0 overflow-hidden bg-muted/40">
97
+ <section
98
+ className={cn(
99
+ "relative grid h-full min-h-0 w-full min-w-0 overflow-hidden",
100
+ controller.isRunsPaneVisible ? "grid-cols-[minmax(0,320px)_minmax(0,1fr)]" : "grid-cols-1",
101
+ )}
102
+ >
103
+ {controller.isRunsPaneVisible ? (
104
+ <WorkflowRunsSidebar
105
+ model={controller.sidebarModel}
106
+ formatting={controller.sidebarFormatting}
107
+ actions={controller.sidebarActions}
108
+ />
109
+ ) : null}
110
+
111
+ <div
112
+ className="grid h-full min-h-0 min-w-0 bg-muted/40"
113
+ style={{
114
+ gridTemplateRows: controller.isPanelCollapsed
115
+ ? "minmax(0, 1fr) 36px"
116
+ : `minmax(0, 1fr) ${controller.inspectorHeight}px`,
117
+ }}
118
+ >
119
+ <div className="relative flex h-full min-h-0 min-w-0 flex-row overflow-hidden bg-muted/40">
120
+ {controller.displayedWorkflow ? (
121
+ <>
122
+ <div className="relative min-h-0 min-w-0 flex-1 overflow-hidden">
123
+ <WorkflowCanvas
124
+ workflow={controller.displayedWorkflow}
125
+ nodeSnapshotsByNodeId={controller.displayedNodeSnapshotsByNodeId}
126
+ connectionInvocations={controller.displayedConnectionInvocations}
127
+ credentialAttentionTooltipByNodeId={controller.credentialAttentionTooltipByNodeId}
128
+ pinnedNodeIds={controller.pinnedNodeIds}
129
+ selectedNodeId={controller.selectedNodeId}
130
+ propertiesTargetNodeId={controller.propertiesPanelNodeId}
131
+ isLiveWorkflowView={controller.isLiveWorkflowView}
132
+ isRunning={controller.isRunning}
133
+ onSelectNode={controller.selectCanvasNode}
134
+ onOpenPropertiesNode={controller.openPropertiesPanelForNode}
135
+ onRunNode={controller.runCanvasNode}
136
+ onTogglePinnedOutput={controller.toggleCanvasNodePin}
137
+ onEditNodeOutput={controller.editCanvasNodeOutput}
138
+ onClearPinnedOutput={controller.clearCanvasNodePin}
139
+ workflowNodeIdsWithBoundCredential={controller.workflowNodeIdsWithBoundCredential}
140
+ onRequestOpenCredentialEditForNode={controller.requestOpenCredentialEditForNode}
141
+ />
142
+ </div>
143
+ <NodePropertiesSlidePanel
144
+ workflowId={args.workflowId}
145
+ isOpen={controller.isPropertiesPanelOpen}
146
+ node={controller.selectedPropertiesWorkflowNode}
147
+ onClose={controller.closePropertiesPanel}
148
+ pendingCredentialEditForNodeId={controller.pendingCredentialEditForNodeId}
149
+ onConsumedPendingCredentialEdit={controller.consumePendingCredentialEditRequest}
150
+ />
151
+ </>
152
+ ) : (
153
+ <div className="p-4 text-sm text-muted-foreground">Loading diagram…</div>
154
+ )}
155
+ <div className="pointer-events-none absolute top-3 left-1/2 z-[6] flex -translate-x-1/2 items-center gap-2">
156
+ <div className="pointer-events-auto flex overflow-hidden rounded-lg border border-border bg-card/95 shadow-md ring-1 ring-foreground/10">
157
+ <Button
158
+ type="button"
159
+ data-testid="workflow-canvas-tab-live"
160
+ variant={activeCanvasTab === "live" ? "default" : "ghost"}
161
+ size="sm"
162
+ className="h-8 rounded-none border-r border-border px-3 text-xs font-extrabold"
163
+ onClick={controller.openLiveWorkflow}
164
+ aria-pressed={activeCanvasTab === "live"}
165
+ >
166
+ Live workflow
167
+ </Button>
168
+ <Button
169
+ type="button"
170
+ data-testid="workflow-canvas-tab-executions"
171
+ variant={activeCanvasTab === "executions" ? "default" : "ghost"}
172
+ size="sm"
173
+ className="h-8 rounded-none px-3 text-xs font-extrabold"
174
+ onClick={controller.openExecutionsPane}
175
+ aria-pressed={activeCanvasTab === "executions"}
176
+ >
177
+ Executions
178
+ </Button>
179
+ </div>
180
+ {controller.canCopySelectedRunToLive ? (
181
+ <Button
182
+ type="button"
183
+ data-testid="canvas-copy-to-live-button"
184
+ size="sm"
185
+ className="pointer-events-auto h-8 px-3 text-xs font-extrabold"
186
+ onClick={controller.copySelectedRunToLive}
187
+ >
188
+ Copy to live
189
+ </Button>
190
+ ) : null}
191
+ </div>
192
+ {controller.isLiveWorkflowView && !controller.isRunsPaneVisible ? (
193
+ <div className="pointer-events-auto absolute bottom-3 left-1/2 z-[6] -translate-x-1/2">
194
+ <Button
195
+ type="button"
196
+ data-testid="canvas-run-workflow-button"
197
+ size="sm"
198
+ className="h-8 px-3 text-xs font-extrabold"
199
+ onClick={controller.runWorkflowFromCanvas}
200
+ disabled={controller.isRunning}
201
+ >
202
+ {controller.isRunning ? "Running..." : "Run workflow"}
203
+ </Button>
204
+ </div>
205
+ ) : null}
206
+ <div className="pointer-events-none absolute top-3 right-3 z-[6] flex max-w-[min(22rem,calc(100%-1.5rem))] flex-col items-end gap-2">
207
+ {shouldShowRealtimeBadge && realtimeBadge ? (
208
+ <div
209
+ data-testid={realtimeBadge.testId}
210
+ className={cn(
211
+ "pointer-events-auto rounded-md border px-2.5 py-2 text-xs font-bold",
212
+ realtimeBadge.className,
213
+ )}
214
+ >
215
+ {realtimeBadge.label}
216
+ </div>
217
+ ) : null}
218
+ </div>
219
+ </div>
220
+
221
+ <WorkflowDetailScreenInspectorPanel controller={controller} />
222
+ </div>
223
+ </section>
224
+ {controller.jsonEditorState ? (
225
+ <WorkflowJsonEditorDialog
226
+ state={controller.jsonEditorState}
227
+ onClose={controller.closeJsonEditor}
228
+ onSave={(value, binaryMaps) => {
229
+ controller.saveJsonEditor(value, binaryMaps);
230
+ }}
231
+ />
232
+ ) : null}
233
+ <style>{WORKFLOW_DETAIL_TREE_STYLES}</style>
234
+ </main>
235
+ );
236
+ }
@@ -0,0 +1,55 @@
1
+ import { PanelBottomClose, PanelBottomOpen } from "lucide-react";
2
+
3
+ import { Button } from "@/components/ui/button";
4
+ import { cn } from "@/lib/utils";
5
+
6
+ import { WorkflowExecutionInspector } from "../components/workflowDetail/WorkflowExecutionInspector";
7
+ import type { WorkflowDetailControllerResult } from "../hooks/workflowDetail/useWorkflowDetailController";
8
+
9
+ export function WorkflowDetailScreenInspectorPanel(props: Readonly<{ controller: WorkflowDetailControllerResult }>) {
10
+ const { controller } = props;
11
+ return (
12
+ <div
13
+ className={cn(
14
+ "grid min-h-0 min-w-0 border-t border-border bg-card",
15
+ controller.isPanelCollapsed ? "grid-rows-[36px]" : "grid-rows-[36px_minmax(0,1fr)]",
16
+ )}
17
+ >
18
+ <div
19
+ className={cn(
20
+ "flex cursor-ns-resize select-none items-center justify-between gap-3 bg-card px-3 py-0",
21
+ !controller.isPanelCollapsed && "border-b border-border",
22
+ )}
23
+ onMouseDown={(event) => controller.startInspectorResize(event.clientY)}
24
+ >
25
+ <div className="text-xs font-extrabold tracking-wide text-muted-foreground uppercase">Execution inspector</div>
26
+ <Button
27
+ type="button"
28
+ variant="outline"
29
+ size="icon-sm"
30
+ className="size-7 shrink-0"
31
+ onClick={(event) => {
32
+ event.stopPropagation();
33
+ controller.toggleInspectorPanel();
34
+ }}
35
+ aria-label={controller.isPanelCollapsed ? "Open execution inspector" : "Collapse execution inspector"}
36
+ >
37
+ {controller.isPanelCollapsed ? (
38
+ <PanelBottomOpen size={15} strokeWidth={1.9} />
39
+ ) : (
40
+ <PanelBottomClose size={15} strokeWidth={1.9} />
41
+ )}
42
+ </Button>
43
+ </div>
44
+ {!controller.isPanelCollapsed ? (
45
+ <div className="min-h-0 min-w-0 overflow-hidden">
46
+ <WorkflowExecutionInspector
47
+ model={controller.inspectorModel}
48
+ formatting={controller.inspectorFormatting}
49
+ actions={controller.inspectorActions}
50
+ />
51
+ </div>
52
+ ) : null}
53
+ </div>
54
+ );
55
+ }
@@ -0,0 +1,35 @@
1
+ import type { WorkflowSummary } from "../hooks/realtime/realtime";
2
+
3
+ import { WorkflowsListTree } from "../components/WorkflowsListTree";
4
+
5
+ export function WorkflowsList(
6
+ args: Readonly<{ workflows: ReadonlyArray<WorkflowSummary> | undefined; error: string | null }>,
7
+ ) {
8
+ const { workflows, error } = args;
9
+
10
+ if (error) {
11
+ return (
12
+ <p className="text-sm text-destructive" data-testid="workflows-load-error">
13
+ Failed to load workflows: {error}
14
+ </p>
15
+ );
16
+ }
17
+
18
+ if (!workflows) {
19
+ return (
20
+ <p className="text-sm text-muted-foreground" data-testid="workflows-loading">
21
+ Loading workflows…
22
+ </p>
23
+ );
24
+ }
25
+
26
+ if (workflows.length === 0) {
27
+ return (
28
+ <p className="text-sm text-muted-foreground" data-testid="workflows-empty">
29
+ No workflows found.
30
+ </p>
31
+ );
32
+ }
33
+
34
+ return <WorkflowsListTree workflows={workflows} />;
35
+ }
@@ -0,0 +1,31 @@
1
+ "use client";
2
+
3
+ import type { WorkflowSummary } from "../hooks/realtime/realtime";
4
+
5
+ import { useWorkflowsQueryWithInitialData } from "../hooks/realtime/realtime";
6
+
7
+ import { WorkflowsList } from "./WorkflowsList";
8
+
9
+ export function WorkflowsScreen(args: Readonly<{ initialWorkflows?: ReadonlyArray<WorkflowSummary> }>) {
10
+ const { initialWorkflows } = args;
11
+ const workflowsQuery = useWorkflowsQueryWithInitialData(initialWorkflows);
12
+ const workflows = workflowsQuery.data;
13
+ const error = workflowsQuery.error instanceof Error ? workflowsQuery.error.message : null;
14
+
15
+ return (
16
+ <main className="mx-auto max-w-[1100px] px-6 py-6">
17
+ <header className="flex flex-wrap items-baseline justify-between gap-3">
18
+ <div>
19
+ <div className="text-xs font-extrabold uppercase tracking-wide text-muted-foreground">Codemation</div>
20
+ <h1 className="mb-0 mt-2 text-3xl font-semibold tracking-tight">Workflows</h1>
21
+ <p className="mt-2 text-sm text-muted-foreground">
22
+ Framework-managed workflows using the shared Codemation runtime.
23
+ </p>
24
+ </div>
25
+ </header>
26
+ <section className="mt-8">
27
+ <WorkflowsList workflows={workflows} error={error} />
28
+ </section>
29
+ </main>
30
+ );
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { CodemationNextHost } from "./server/CodemationNextHost";
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }