@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,30 @@
1
+ import type { NextAuthConfig } from "next-auth";
2
+
3
+ export type CodemationOAuthProviderDescriptor = Readonly<{
4
+ id: string;
5
+ name: string;
6
+ }>;
7
+
8
+ export class CodemationNextAuthOAuthProviderDescriptorMapper {
9
+ mapFromBuiltProviders(
10
+ providers: ReadonlyArray<NonNullable<NextAuthConfig["providers"]>[number]>,
11
+ ): ReadonlyArray<CodemationOAuthProviderDescriptor> {
12
+ const out: CodemationOAuthProviderDescriptor[] = [];
13
+ for (const p of providers) {
14
+ if (p === null || p === undefined) {
15
+ continue;
16
+ }
17
+ if (typeof p !== "object") {
18
+ continue;
19
+ }
20
+ const id = "id" in p && typeof (p as { id: unknown }).id === "string" ? (p as { id: string }).id : "";
21
+ if (id === "" || id === "credentials") {
22
+ continue;
23
+ }
24
+ const rawName = "name" in p ? (p as { name: unknown }).name : undefined;
25
+ const name = typeof rawName === "string" && rawName.trim().length > 0 ? rawName : id;
26
+ out.push({ id, name });
27
+ }
28
+ return out;
29
+ }
30
+ }
@@ -0,0 +1,17 @@
1
+ import { CodemationAuthPrismaClient } from "../server/CodemationAuthPrismaClient";
2
+ import type { CodemationOAuthProviderDescriptor } from "./CodemationNextAuthOAuthProviderDescriptorMapper";
3
+ import { CodemationNextAuthOAuthProviderDescriptorMapper } from "./CodemationNextAuthOAuthProviderDescriptorMapper";
4
+ import { CodemationNextAuthConfigResolver } from "./CodemationNextAuthConfigResolver";
5
+ import { CodemationNextAuthProviderCatalog } from "./CodemationNextAuthProviderCatalog";
6
+
7
+ export type { CodemationOAuthProviderDescriptor } from "./CodemationNextAuthOAuthProviderDescriptorMapper";
8
+
9
+ export class CodemationNextAuthOAuthProviderSnapshotResolver {
10
+ async resolve(): Promise<ReadonlyArray<CodemationOAuthProviderDescriptor>> {
11
+ const env = process.env;
12
+ const authConfig = await new CodemationNextAuthConfigResolver().resolve();
13
+ const prisma = await CodemationAuthPrismaClient.resolveShared();
14
+ const built = await CodemationNextAuthProviderCatalog.build(authConfig, prisma, env);
15
+ return new CodemationNextAuthOAuthProviderDescriptorMapper().mapFromBuiltProviders(built);
16
+ }
17
+ }
@@ -0,0 +1,107 @@
1
+ import type {
2
+ CodemationAuthConfig,
3
+ CodemationAuthOAuthProviderConfig,
4
+ CodemationAuthOidcProviderConfig,
5
+ } from "@codemation/host";
6
+ import type { PrismaClient } from "@codemation/host/persistence";
7
+ import { compare } from "bcryptjs";
8
+ import type { NextAuthConfig } from "next-auth";
9
+ import Credentials from "next-auth/providers/credentials";
10
+ import GitHub from "next-auth/providers/github";
11
+ import Google from "next-auth/providers/google";
12
+ import MicrosoftEntraID from "next-auth/providers/microsoft-entra-id";
13
+
14
+ export class CodemationNextAuthProviderCatalog {
15
+ static async build(
16
+ authConfig: CodemationAuthConfig | undefined,
17
+ prisma: PrismaClient,
18
+ env: NodeJS.ProcessEnv,
19
+ ): Promise<NextAuthConfig["providers"]> {
20
+ if (!authConfig) {
21
+ return [];
22
+ }
23
+ const providers: NextAuthConfig["providers"] = [];
24
+ if (CodemationNextAuthProviderCatalog.includesCredentialsProvider(authConfig)) {
25
+ providers.push(CodemationNextAuthProviderCatalog.createCredentialsProvider(prisma));
26
+ }
27
+ for (const entry of authConfig.oauth ?? []) {
28
+ providers.push(CodemationNextAuthProviderCatalog.createOAuthProvider(entry, env));
29
+ }
30
+ for (const entry of authConfig.oidc ?? []) {
31
+ providers.push(CodemationNextAuthProviderCatalog.createOidcProvider(entry, env));
32
+ }
33
+ return providers;
34
+ }
35
+
36
+ private static includesCredentialsProvider(authConfig: CodemationAuthConfig): boolean {
37
+ return authConfig.kind === "local";
38
+ }
39
+
40
+ private static createCredentialsProvider(prisma: PrismaClient): NextAuthConfig["providers"][number] {
41
+ return Credentials({
42
+ name: "Email and password",
43
+ credentials: {
44
+ email: { label: "Email", type: "email" },
45
+ password: { label: "Password", type: "password" },
46
+ },
47
+ authorize: async (credentials) => {
48
+ const email = typeof credentials?.email === "string" ? credentials.email.trim() : "";
49
+ const password = typeof credentials?.password === "string" ? credentials.password : "";
50
+ if (!email || !password) {
51
+ return null;
52
+ }
53
+ const user = await prisma.user.findUnique({ where: { email } });
54
+ if (!user?.passwordHash || user.accountStatus === "inactive") {
55
+ return null;
56
+ }
57
+ if (user.accountStatus !== "active") {
58
+ return null;
59
+ }
60
+ const matches = await compare(password, user.passwordHash);
61
+ if (!matches) {
62
+ return null;
63
+ }
64
+ return {
65
+ id: user.id,
66
+ email: user.email ?? email,
67
+ name: user.name ?? undefined,
68
+ image: user.image ?? undefined,
69
+ };
70
+ },
71
+ });
72
+ }
73
+
74
+ private static createOAuthProvider(
75
+ entry: CodemationAuthOAuthProviderConfig,
76
+ env: NodeJS.ProcessEnv,
77
+ ): NextAuthConfig["providers"][number] {
78
+ const clientId = env[entry.clientIdEnv] ?? "";
79
+ const clientSecret = env[entry.clientSecretEnv] ?? "";
80
+ if (entry.provider === "google") {
81
+ return Google({ clientId, clientSecret });
82
+ }
83
+ if (entry.provider === "github") {
84
+ return GitHub({ clientId, clientSecret });
85
+ }
86
+ const tenantId = entry.tenantIdEnv ? (env[entry.tenantIdEnv] ?? "common") : "common";
87
+ return MicrosoftEntraID({
88
+ clientId,
89
+ clientSecret,
90
+ issuer: `https://login.microsoftonline.com/${tenantId}/v2.0`,
91
+ });
92
+ }
93
+
94
+ private static createOidcProvider(
95
+ entry: CodemationAuthOidcProviderConfig,
96
+ env: NodeJS.ProcessEnv,
97
+ ): NextAuthConfig["providers"][number] {
98
+ return {
99
+ id: entry.id,
100
+ name: entry.id,
101
+ type: "oidc",
102
+ issuer: entry.issuer,
103
+ clientId: env[entry.clientIdEnv] ?? "",
104
+ clientSecret: env[entry.clientSecretEnv] ?? "",
105
+ };
106
+ }
107
+ }
@@ -0,0 +1,25 @@
1
+ import NextAuth from "next-auth";
2
+ import Credentials from "next-auth/providers/credentials";
3
+
4
+ /**
5
+ * Middleware runs on the Edge runtime: no Prisma, no consumer manifest.
6
+ * Verifies Auth.js JWT session cookies using AUTH_SECRET only.
7
+ */
8
+ const authSecretFromEnv = process.env.AUTH_SECRET ?? process.env.NEXTAUTH_SECRET;
9
+ const authSecret =
10
+ authSecretFromEnv?.trim() ||
11
+ (process.env.NODE_ENV === "development" ? "codemation-dev-auth-secret-not-for-production" : undefined);
12
+
13
+ export const { auth } = NextAuth({
14
+ trustHost: true,
15
+ session: { strategy: "jwt" },
16
+ secret: authSecret,
17
+ providers: [
18
+ Credentials({
19
+ id: "edge-jwt-verifier-placeholder",
20
+ name: "Edge verifier",
21
+ credentials: {},
22
+ authorize: async () => null,
23
+ }),
24
+ ],
25
+ });
@@ -0,0 +1,32 @@
1
+ import { PrismaAdapter } from "@auth/prisma-adapter";
2
+ import NextAuth from "next-auth";
3
+ import { CodemationAuthPrismaClient } from "../server/CodemationAuthPrismaClient";
4
+ import { CodemationNextAuthConfigResolver } from "./CodemationNextAuthConfigResolver";
5
+ import { CodemationNextAuthProviderCatalog } from "./CodemationNextAuthProviderCatalog";
6
+
7
+ export const { handlers, auth, signIn, signOut } = NextAuth(async () => {
8
+ const env = process.env;
9
+ const authConfig = await new CodemationNextAuthConfigResolver().resolve();
10
+ const prisma = await CodemationAuthPrismaClient.resolveShared();
11
+ const secretFromEnv = env.AUTH_SECRET ?? env.NEXTAUTH_SECRET;
12
+ const secret =
13
+ secretFromEnv?.trim() ||
14
+ (env.NODE_ENV === "development" ? "codemation-dev-auth-secret-not-for-production" : undefined);
15
+ if (!secret || secret.trim().length === 0) {
16
+ throw new Error("AUTH_SECRET (or NEXTAUTH_SECRET) is required for Codemation authentication.");
17
+ }
18
+ const providers = await CodemationNextAuthProviderCatalog.build(authConfig, prisma, env);
19
+ if (env.NODE_ENV === "production" && providers.length === 0) {
20
+ throw new Error("CodemationConfig.auth must configure at least one NextAuth provider for production.");
21
+ }
22
+ return {
23
+ adapter: PrismaAdapter(prisma),
24
+ secret,
25
+ session: { strategy: "jwt" },
26
+ providers: [...providers],
27
+ pages: {
28
+ signIn: "/login",
29
+ },
30
+ trustHost: true,
31
+ };
32
+ });
@@ -0,0 +1,6 @@
1
+ import type { WorkflowSummary } from "../features/workflows/hooks/realtime/realtime";
2
+ import { WorkflowsScreen } from "../features/workflows/screens/WorkflowsScreen";
3
+
4
+ export function Codemation(args: Readonly<{ initialWorkflows: ReadonlyArray<WorkflowSummary> }>) {
5
+ return <WorkflowsScreen {...args} />;
6
+ }
@@ -0,0 +1,37 @@
1
+ "use client";
2
+
3
+ import type { ReactNode } from "react";
4
+
5
+ import { Table, TableBody, TableHead, TableHeader, TableRow } from "@/components/ui/table";
6
+
7
+ export type CodemationDataTableColumn = Readonly<{
8
+ key: string;
9
+ header: string;
10
+ headerTestId?: string;
11
+ }>;
12
+
13
+ export type CodemationDataTableProps = Readonly<{
14
+ tableTestId: string;
15
+ columns: ReadonlyArray<CodemationDataTableColumn>;
16
+ children: ReactNode;
17
+ }>;
18
+
19
+ /**
20
+ * Shared data table using shadcn/ui Table primitives and design tokens.
21
+ */
22
+ export function CodemationDataTable(props: CodemationDataTableProps) {
23
+ return (
24
+ <Table data-testid={props.tableTestId}>
25
+ <TableHeader>
26
+ <TableRow>
27
+ {props.columns.map((column) => (
28
+ <TableHead key={column.key} data-testid={column.headerTestId ?? `codemation-table-header-${column.key}`}>
29
+ {column.header}
30
+ </TableHead>
31
+ ))}
32
+ </TableRow>
33
+ </TableHeader>
34
+ <TableBody>{props.children}</TableBody>
35
+ </Table>
36
+ );
37
+ }
@@ -0,0 +1,137 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
6
+ import { cn } from "@/lib/utils";
7
+
8
+ const maxWidthBySize = {
9
+ narrow: "sm:max-w-lg",
10
+ wide: "sm:max-w-2xl",
11
+ full: "sm:max-w-[min(92vw,960px)]",
12
+ } as const;
13
+
14
+ export type CodemationDialogSize = keyof typeof maxWidthBySize;
15
+
16
+ export type CodemationDialogRootProps = Readonly<{
17
+ children: React.ReactNode;
18
+ onClose: () => void;
19
+ /** Root `data-testid` (applied to the dialog panel). */
20
+ testId?: string;
21
+ /** `dialog` (default) or `alertdialog` for confirmations. */
22
+ role?: "dialog" | "alertdialog";
23
+ /** Max width preset; default `wide`. */
24
+ size?: CodemationDialogSize;
25
+ /** Extra classes on the Radix panel (e.g. `max-h-[min(90vh,640px)]`). */
26
+ contentClassName?: string;
27
+ /** Corner X to dismiss (Radix); default false — use `<CodemationDialog.Actions>` for explicit buttons. */
28
+ showCloseButton?: boolean;
29
+ }>;
30
+
31
+ function CodemationDialogRoot({
32
+ children,
33
+ onClose,
34
+ testId,
35
+ role = "dialog",
36
+ size = "wide",
37
+ contentClassName,
38
+ showCloseButton = false,
39
+ }: CodemationDialogRootProps) {
40
+ return (
41
+ <Dialog
42
+ open
43
+ onOpenChange={(open) => {
44
+ if (!open) onClose();
45
+ }}
46
+ >
47
+ <DialogContent
48
+ showCloseButton={showCloseButton}
49
+ data-testid={testId}
50
+ role={role}
51
+ aria-describedby={undefined}
52
+ className={cn(
53
+ "flex max-h-[min(92vh,900px)] flex-col gap-0 overflow-hidden p-0",
54
+ maxWidthBySize[size],
55
+ contentClassName,
56
+ )}
57
+ >
58
+ {children}
59
+ </DialogContent>
60
+ </Dialog>
61
+ );
62
+ }
63
+
64
+ export type CodemationDialogTitleProps = Readonly<{
65
+ children: React.ReactNode;
66
+ className?: string;
67
+ }>;
68
+
69
+ /**
70
+ * Do not set `id` on the underlying Radix `DialogTitle` — the dialog root assigns `titleId`
71
+ * in context; overriding `id` breaks `aria-labelledby` and Radix dev warnings.
72
+ */
73
+ function CodemationDialogTitle({ children, className }: CodemationDialogTitleProps) {
74
+ return (
75
+ <DialogTitle
76
+ className={cn("m-0 shrink-0 border-b border-border px-4 py-3 text-base leading-none font-semibold", className)}
77
+ >
78
+ {children}
79
+ </DialogTitle>
80
+ );
81
+ }
82
+
83
+ export type CodemationDialogContentProps = Readonly<{
84
+ children: React.ReactNode;
85
+ className?: string;
86
+ }>;
87
+
88
+ function CodemationDialogContent({ children, className }: CodemationDialogContentProps) {
89
+ return (
90
+ <div className={cn("flex min-h-0 flex-1 flex-col gap-4 overflow-auto px-4 py-3 text-sm", className)}>
91
+ {children}
92
+ </div>
93
+ );
94
+ }
95
+
96
+ export type CodemationDialogActionsProps = Readonly<{
97
+ children: React.ReactNode;
98
+ /** Toolbar directly under the title (e.g. filters). Default is footer actions. */
99
+ position?: "top" | "bottom";
100
+ /** Flex alignment for the button row. */
101
+ align?: "start" | "end" | "between";
102
+ className?: string;
103
+ }>;
104
+
105
+ function CodemationDialogActions({
106
+ children,
107
+ position = "bottom",
108
+ align = "end",
109
+ className,
110
+ }: CodemationDialogActionsProps) {
111
+ return (
112
+ <div
113
+ className={cn(
114
+ "flex shrink-0 flex-wrap gap-2 border-border bg-muted/30 px-4 py-3",
115
+ position === "top" ? "border-b" : "border-t",
116
+ align === "end" && "justify-end",
117
+ align === "start" && "justify-start",
118
+ align === "between" && "justify-between",
119
+ className,
120
+ )}
121
+ >
122
+ {children}
123
+ </div>
124
+ );
125
+ }
126
+
127
+ export type CodemationDialogCompound = typeof CodemationDialogRoot & {
128
+ Title: typeof CodemationDialogTitle;
129
+ Content: typeof CodemationDialogContent;
130
+ Actions: typeof CodemationDialogActions;
131
+ };
132
+
133
+ export const CodemationDialog = Object.assign(CodemationDialogRoot, {
134
+ Title: CodemationDialogTitle,
135
+ Content: CodemationDialogContent,
136
+ Actions: CodemationDialogActions,
137
+ }) as CodemationDialogCompound;
@@ -0,0 +1,46 @@
1
+ "use client";
2
+
3
+ import type { Locale } from "date-fns";
4
+ import { format, isValid, parseISO } from "date-fns";
5
+ import { enUS } from "date-fns/locale";
6
+ import type { ReactNode } from "react";
7
+
8
+ export type CodemationFormattedDateTimeProps = Readonly<{
9
+ /** ISO 8601 instant (e.g. from API). */
10
+ isoUtc: string | null | undefined;
11
+ /** Shown when `isoUtc` is missing or unparsable. */
12
+ fallbackText?: string;
13
+ /** Reserved for localization; defaults to `enUS`. */
14
+ locale?: Locale;
15
+ dataTestId?: string;
16
+ className?: string;
17
+ }>;
18
+
19
+ /**
20
+ * Renders a human-readable date/time via **date-fns** (`format` + `parseISO`).
21
+ * Pass a different `locale` when you wire i18n.
22
+ */
23
+ export function CodemationFormattedDateTime(props: CodemationFormattedDateTimeProps): ReactNode {
24
+ const { isoUtc, fallbackText = "—", locale = enUS, dataTestId, className } = props;
25
+ if (!isoUtc?.trim()) {
26
+ return (
27
+ <span className={className} data-testid={dataTestId}>
28
+ {fallbackText}
29
+ </span>
30
+ );
31
+ }
32
+ const parsed = parseISO(isoUtc);
33
+ if (!isValid(parsed)) {
34
+ return (
35
+ <span className={className} data-testid={dataTestId}>
36
+ {fallbackText}
37
+ </span>
38
+ );
39
+ }
40
+ const label = format(parsed, "PPp", { locale });
41
+ return (
42
+ <time className={className} dateTime={isoUtc} data-testid={dataTestId}>
43
+ {label}
44
+ </time>
45
+ );
46
+ }
@@ -0,0 +1,39 @@
1
+ "use client";
2
+
3
+ import type { ReactNode } from "react";
4
+
5
+ /**
6
+ * Standard color Google "G" (multicolor). Simple Icons only ships a monochrome mark;
7
+ * Google requires the full-color logo for sign-in UI.
8
+ *
9
+ * @see https://developers.google.com/identity/branding-guidelines
10
+ */
11
+ export function GoogleColorGIcon(props: Readonly<{ className?: string; testId?: string }>): ReactNode {
12
+ return (
13
+ <svg
14
+ role="img"
15
+ className={props.className}
16
+ viewBox="0 0 24 24"
17
+ xmlns="http://www.w3.org/2000/svg"
18
+ aria-hidden
19
+ data-testid={props.testId}
20
+ >
21
+ <path
22
+ fill="#4285F4"
23
+ d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
24
+ />
25
+ <path
26
+ fill="#34A853"
27
+ d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
28
+ />
29
+ <path
30
+ fill="#FBBC05"
31
+ d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
32
+ />
33
+ <path
34
+ fill="#EA4335"
35
+ d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
36
+ />
37
+ </svg>
38
+ );
39
+ }
@@ -0,0 +1,33 @@
1
+ "use client";
2
+
3
+ import { Component, type ReactNode } from "react";
4
+
5
+ import { GoogleColorGIcon } from "./GoogleColorGIcon";
6
+ import { simpleIconForProvider } from "./oauthProviderIconData";
7
+
8
+ export type OauthProviderIconProps = Readonly<{
9
+ providerId: string;
10
+ className?: string;
11
+ testId?: string;
12
+ }>;
13
+
14
+ export class OauthProviderIcon extends Component<OauthProviderIconProps> {
15
+ override render(): ReactNode {
16
+ if (this.props.providerId === "google") {
17
+ return <GoogleColorGIcon className={this.props.className} testId={this.props.testId} />;
18
+ }
19
+ const icon = simpleIconForProvider(this.props.providerId);
20
+ return (
21
+ <svg
22
+ role="img"
23
+ className={this.props.className}
24
+ viewBox="0 0 24 24"
25
+ xmlns="http://www.w3.org/2000/svg"
26
+ aria-hidden
27
+ data-testid={this.props.testId}
28
+ >
29
+ <path d={icon.path} fill={`#${icon.hex}`} />
30
+ </svg>
31
+ );
32
+ }
33
+ }
@@ -0,0 +1,59 @@
1
+ "use client";
2
+
3
+ import { useMemo, type ReactNode } from "react";
4
+ import zxcvbn from "zxcvbn";
5
+
6
+ import { cn } from "@/lib/utils";
7
+
8
+ export type PasswordStrengthMeterProps = Readonly<{
9
+ password: string;
10
+ /** Skips scoring (and layout) when false. */
11
+ enabled?: boolean;
12
+ dataTestId?: string;
13
+ }>;
14
+
15
+ const scoreLabels = ["Too weak", "Weak", "Fair", "Good", "Strong"] as const;
16
+
17
+ /**
18
+ * Password strength using **zxcvbn** (Dropbox). Not a policy gate — server still enforces min length.
19
+ */
20
+ export function PasswordStrengthMeter(props: PasswordStrengthMeterProps): ReactNode {
21
+ const { password, enabled = true, dataTestId = "password-strength-meter" } = props;
22
+ const result = useMemo(() => {
23
+ if (!enabled || password.length === 0) return null;
24
+ return zxcvbn(password);
25
+ }, [enabled, password]);
26
+
27
+ if (!enabled || password.length === 0 || !result) {
28
+ return null;
29
+ }
30
+
31
+ const { score, feedback } = result;
32
+ const hint = feedback.warning || (feedback.suggestions[0] ?? "");
33
+ const label = scoreLabels[Math.min(score, 4)] ?? scoreLabels[0];
34
+
35
+ const barClass = (i: number) =>
36
+ cn(
37
+ "h-1.5 flex-1 rounded-sm bg-muted transition-colors",
38
+ i <= score &&
39
+ (score <= 1 ? "bg-destructive" : score <= 3 ? "bg-amber-500" : "bg-emerald-600 dark:bg-emerald-500"),
40
+ );
41
+
42
+ return (
43
+ <div className="flex flex-col gap-1.5" data-testid={dataTestId} role="status" aria-live="polite">
44
+ <div className="flex gap-1" aria-hidden>
45
+ {([0, 1, 2, 3, 4] as const).map((i) => (
46
+ <span key={i} className={barClass(i)} />
47
+ ))}
48
+ </div>
49
+ <span className="text-xs font-medium text-foreground" data-testid={`${dataTestId}-label`}>
50
+ {label}
51
+ </span>
52
+ {hint ? (
53
+ <span className="text-xs text-muted-foreground" data-testid={`${dataTestId}-hint`}>
54
+ {hint}
55
+ </span>
56
+ ) : null}
57
+ </div>
58
+ );
59
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Canonical forms stack: React Hook Form + Zod + shadcn Form primitives.
3
+ * Import from here in feature code so validation and layout stay consistent.
4
+ */
5
+ export { z } from "zod";
6
+ export { zodResolver } from "@hookform/resolvers/zod";
7
+ export {
8
+ useForm,
9
+ useFormContext,
10
+ useFormState,
11
+ useWatch,
12
+ type FieldPath,
13
+ type FieldValues,
14
+ type Resolver,
15
+ type SubmitHandler,
16
+ type UseFormProps,
17
+ type UseFormReturn,
18
+ } from "react-hook-form";
19
+ export {
20
+ Form,
21
+ FormControl,
22
+ FormDescription,
23
+ FormField,
24
+ FormItem,
25
+ FormLabel,
26
+ FormMessage,
27
+ useFormField,
28
+ } from "@/components/ui/form";
@@ -0,0 +1,75 @@
1
+ "use client";
2
+
3
+ import Editor from "@monaco-editor/react";
4
+ import type { ComponentProps } from "react";
5
+
6
+ import { Textarea } from "@/components/ui/textarea";
7
+
8
+ const defaultOptions: NonNullable<ComponentProps<typeof Editor>["options"]> = {
9
+ automaticLayout: true,
10
+ formatOnPaste: true,
11
+ formatOnType: true,
12
+ minimap: { enabled: false },
13
+ scrollBeyondLastLine: false,
14
+ lineNumbersMinChars: 3,
15
+ tabSize: 2,
16
+ insertSpaces: true,
17
+ wordWrap: "on",
18
+ bracketPairColorization: {
19
+ enabled: true,
20
+ },
21
+ guides: {
22
+ indentation: true,
23
+ bracketPairs: true,
24
+ },
25
+ padding: {
26
+ top: 12,
27
+ bottom: 12,
28
+ },
29
+ };
30
+
31
+ /**
32
+ * Monaco-based JSON editor with a mirrored, visually hidden `<textarea>` that carries the same value.
33
+ * Tests and automation can drive `data-testid` on that textarea because Monaco’s surface is not a reliable
34
+ * DOM target for `fireEvent.change` / user typing simulation.
35
+ */
36
+ export function JsonMonacoEditor(
37
+ args: Readonly<{
38
+ path: string;
39
+ value: string;
40
+ onChange: (value: string | undefined) => void;
41
+ /** Shown below the editor region when set. */
42
+ error?: string | null;
43
+ /** Passed to the hidden textarea for stable test selectors. */
44
+ testId?: string;
45
+ }>,
46
+ ) {
47
+ const { path, value, onChange, error, testId = "workflow-json-editor-input" } = args;
48
+ return (
49
+ <div className="relative flex min-h-0 flex-1 flex-col">
50
+ <div className="h-[min(60vh,560px)] min-h-[200px] shrink-0 overflow-hidden rounded-md border border-border bg-background">
51
+ <Editor
52
+ height="100%"
53
+ language="json"
54
+ path={path}
55
+ value={value}
56
+ onChange={onChange}
57
+ loading={<div className="grid h-full place-items-center text-xs text-muted-foreground">Loading editor…</div>}
58
+ options={defaultOptions}
59
+ />
60
+ </div>
61
+ <Textarea
62
+ data-testid={testId}
63
+ value={value}
64
+ onChange={(event) => {
65
+ onChange(event.target.value);
66
+ }}
67
+ spellCheck={false}
68
+ className="pointer-events-none absolute inset-0 h-px w-px min-h-0 resize-none border-0 p-0 opacity-0"
69
+ aria-hidden="true"
70
+ tabIndex={-1}
71
+ />
72
+ {error ? <div className="mt-1 text-xs text-destructive">{error}</div> : null}
73
+ </div>
74
+ );
75
+ }