create-m5kdev 0.33.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/dist/src/catalog.cjs +21 -1
  2. package/dist/src/catalog.cjs.map +1 -1
  3. package/dist/src/catalog.d.cts +3 -1
  4. package/dist/src/create.cjs +6 -2
  5. package/dist/src/create.cjs.map +1 -1
  6. package/dist/src/doctor.cjs.map +1 -1
  7. package/dist/src/fs.cjs +10 -5
  8. package/dist/src/fs.cjs.map +1 -1
  9. package/dist/src/prompts.cjs +32 -0
  10. package/dist/src/prompts.cjs.map +1 -1
  11. package/dist/src/prompts.d.cts +7 -1
  12. package/dist/src/template.cjs +23 -4
  13. package/dist/src/template.cjs.map +1 -1
  14. package/dist/src/template.d.cts +17 -3
  15. package/dist/src/types.d.cts +11 -4
  16. package/package.json +4 -1
  17. package/templates/minimal-app/.cursor/rules/module-dto-guide.mdc +1 -1
  18. package/templates/minimal-app/.cursor/rules/module-grants-guide.mdc +3 -3
  19. package/templates/minimal-app/.cursor/rules/module-module-guide.mdc +3 -2
  20. package/templates/minimal-app/.cursor/rules/module-service-guide.mdc +1 -1
  21. package/templates/minimal-app/.dockerignore.tpl +31 -0
  22. package/templates/minimal-app/.gitignore.tpl +1 -0
  23. package/templates/minimal-app/AGENTS.md.tpl +13 -10
  24. package/templates/minimal-app/CLAUDE.md.tpl +132 -131
  25. package/templates/minimal-app/README.md.tpl +50 -19
  26. package/templates/minimal-app/apps/e2e/package.json.tpl +23 -23
  27. package/templates/minimal-app/apps/e2e/playwright.config.ts.tpl +51 -39
  28. package/templates/minimal-app/apps/e2e/playwright.expo.config.ts.tpl +38 -38
  29. package/templates/minimal-app/apps/e2e/servers.ts.tpl +136 -112
  30. package/templates/minimal-app/apps/e2e/tests/conversation.spec.ts.tpl +22 -0
  31. package/templates/minimal-app/apps/e2e/tests/email.spec.ts.tpl +47 -47
  32. package/templates/minimal-app/apps/e2e/tests/expo-helpers.ts.tpl +73 -73
  33. package/templates/minimal-app/apps/e2e/tests/expo-standard.spec.ts.tpl +517 -517
  34. package/templates/minimal-app/apps/e2e/tests/expo-waitlist.spec.ts.tpl +94 -94
  35. package/templates/minimal-app/apps/e2e/tests/files.spec.ts.tpl +35 -0
  36. package/templates/minimal-app/apps/e2e/tests/helpers.ts.tpl +289 -289
  37. package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +516 -516
  38. package/templates/minimal-app/apps/e2e/tests/waitlist.spec.ts.tpl +107 -107
  39. package/templates/minimal-app/apps/e2e/tests/workflow.spec.ts.tpl +26 -0
  40. package/templates/minimal-app/apps/e2e/tsconfig.json.tpl +10 -10
  41. package/templates/minimal-app/apps/email/package.json.tpl +34 -34
  42. package/templates/minimal-app/apps/email/src/emails/trialEndingEmail.tsx.tpl +40 -0
  43. package/templates/minimal-app/apps/email/src/index.ts.tpl +7 -0
  44. package/templates/minimal-app/apps/email/src/resources.ts.tpl +72 -65
  45. package/templates/minimal-app/apps/expo/app.json.tpl +36 -36
  46. package/templates/minimal-app/apps/expo/eslint.config.js.tpl +8 -8
  47. package/templates/minimal-app/apps/expo/metro.config.js.tpl +16 -16
  48. package/templates/minimal-app/apps/expo/src/app/_layout.tsx.tpl +31 -31
  49. package/templates/minimal-app/apps/expo/src/app/claim-account.tsx.tpl +151 -151
  50. package/templates/minimal-app/apps/expo/src/app/forgot-password.tsx.tpl +53 -53
  51. package/templates/minimal-app/apps/expo/src/app/index.tsx.tpl +8 -8
  52. package/templates/minimal-app/apps/expo/src/app/login.tsx.tpl +71 -71
  53. package/templates/minimal-app/apps/expo/src/app/logout.tsx.tpl +19 -19
  54. package/templates/minimal-app/apps/expo/src/app/organization/accept-invitation.tsx.tpl +85 -85
  55. package/templates/minimal-app/apps/expo/src/app/posts.tsx.tpl +40 -40
  56. package/templates/minimal-app/apps/expo/src/app/reset-password.tsx.tpl +75 -75
  57. package/templates/minimal-app/apps/expo/src/app/signup.tsx.tpl +193 -193
  58. package/templates/minimal-app/apps/expo/src/components/AuthScaffold.tsx.tpl +34 -34
  59. package/templates/minimal-app/apps/expo/src/components/FormTextField.tsx.tpl +37 -37
  60. package/templates/minimal-app/apps/expo/src/components/LoadingScreen.tsx.tpl +10 -10
  61. package/templates/minimal-app/apps/expo/src/components/StatusText.tsx.tpl +22 -22
  62. package/templates/minimal-app/apps/expo/src/config.ts.tpl +9 -9
  63. package/templates/minimal-app/apps/expo/src/global.css.tpl +11 -11
  64. package/templates/minimal-app/apps/expo/src/lib/trpc.ts.tpl +20 -20
  65. package/templates/minimal-app/apps/expo/tsconfig.json.tpl +11 -11
  66. package/templates/minimal-app/apps/expo/uniwind-env.d.ts.tpl +1 -1
  67. package/templates/minimal-app/apps/landing/.env.production.example.tpl +7 -0
  68. package/templates/minimal-app/apps/landing/Dockerfile.tpl +39 -0
  69. package/templates/minimal-app/apps/landing/fly.toml.tpl +20 -0
  70. package/templates/minimal-app/apps/landing/index.html.tpl +35 -0
  71. package/templates/minimal-app/apps/landing/package.json.tpl +34 -0
  72. package/templates/minimal-app/apps/landing/scripts/fly-deploy.mjs.tpl +75 -0
  73. package/templates/minimal-app/apps/landing/scripts/fly-secrets.mjs.tpl +31 -0
  74. package/templates/minimal-app/apps/landing/src/App.tsx.tpl +12 -0
  75. package/templates/minimal-app/apps/landing/src/LandingPage.tsx.tpl +37 -0
  76. package/templates/minimal-app/apps/landing/src/index.css.tpl +24 -0
  77. package/templates/minimal-app/apps/landing/src/main.tsx.tpl +16 -0
  78. package/templates/minimal-app/apps/landing/src/vite-env.d.ts.tpl +11 -0
  79. package/templates/minimal-app/apps/landing/tsconfig.json.tpl +12 -0
  80. package/templates/minimal-app/apps/landing/vite.config.ts.tpl +25 -0
  81. package/templates/minimal-app/apps/server/AGENTS.md.tpl +54 -46
  82. package/templates/minimal-app/apps/server/{drizzle/seed.e2e.ts.tpl → db.e2e.ts.tpl} +57 -40
  83. package/templates/minimal-app/apps/server/{drizzle/seed.ts.tpl → db.ts.tpl} +27 -20
  84. package/templates/minimal-app/apps/server/drizzle/0003_silly_the_liberteens.sql +27 -0
  85. package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +56 -166
  86. package/templates/minimal-app/apps/server/drizzle/meta/0001_snapshot.json.tpl +1796 -1910
  87. package/templates/minimal-app/apps/server/drizzle/meta/0002_snapshot.json.tpl +1818 -1932
  88. package/templates/minimal-app/apps/server/drizzle/meta/0003_snapshot.json.tpl +2137 -0
  89. package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +8 -1
  90. package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +3 -33
  91. package/templates/minimal-app/apps/server/package.json.tpl +5 -3
  92. package/templates/minimal-app/apps/server/src/app.ts.tpl +36 -0
  93. package/templates/minimal-app/apps/server/src/modules/conversation/conversation.mastra.ts.tpl +26 -0
  94. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.module.ts.tpl +47 -0
  95. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.service.ts.tpl +43 -0
  96. package/templates/minimal-app/apps/server/src/modules/demo-workflow/demo-workflow.trpc.ts.tpl +16 -0
  97. package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +28 -28
  98. package/templates/minimal-app/apps/server/src/modules/posts/posts.grants.ts.tpl +28 -28
  99. package/templates/minimal-app/apps/server/src/modules/posts/posts.module.ts.tpl +54 -54
  100. package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +67 -67
  101. package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +116 -116
  102. package/templates/minimal-app/apps/server/src/modules/posts/posts.trpc.ts.tpl +37 -37
  103. package/templates/minimal-app/apps/server/src/modules/test-harness/test-harness.module.ts.tpl +279 -279
  104. package/templates/minimal-app/apps/server/src/schema.ts.tpl +31 -20
  105. package/templates/minimal-app/apps/server/src/types.ts.tpl +12 -12
  106. package/templates/minimal-app/apps/server/tsconfig.json.tpl +1 -1
  107. package/templates/minimal-app/apps/server/tsup.config.ts.tpl +23 -9
  108. package/templates/minimal-app/apps/shared/.env.example.tpl +5 -0
  109. package/templates/minimal-app/apps/shared/.env.production.example.tpl +23 -0
  110. package/templates/minimal-app/apps/shared/.env.tpl +5 -0
  111. package/templates/minimal-app/apps/shared/Dockerfile.tpl +48 -0
  112. package/templates/minimal-app/apps/shared/fly.toml.tpl +24 -0
  113. package/templates/minimal-app/apps/shared/package.json.tpl +25 -24
  114. package/templates/minimal-app/apps/shared/scripts/fly-deploy.mjs.tpl +75 -0
  115. package/templates/minimal-app/apps/shared/scripts/fly-secrets.mjs.tpl +31 -0
  116. package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +28 -28
  117. package/templates/minimal-app/apps/shared/src/modules/conversation/conversation.constants.ts.tpl +1 -0
  118. package/templates/minimal-app/apps/shared/src/modules/notification/notification.schema.ts.tpl +3 -3
  119. package/templates/minimal-app/apps/shared/src/modules/posts/posts.constants.ts.tpl +5 -5
  120. package/templates/minimal-app/apps/shared/src/modules/posts/posts.schema.ts.tpl +25 -25
  121. package/templates/minimal-app/apps/webapp/package.json.tpl +53 -53
  122. package/templates/minimal-app/apps/webapp/public/push-sw.js.tpl +34 -34
  123. package/templates/minimal-app/apps/webapp/src/App.tsx.tpl +46 -44
  124. package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +96 -65
  125. package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +114 -93
  126. package/templates/minimal-app/apps/webapp/src/index.css.tpl +15 -15
  127. package/templates/minimal-app/apps/webapp/src/main.tsx.tpl +26 -26
  128. package/templates/minimal-app/apps/webapp/src/modules/conversation/ConversationRoute.tsx.tpl +36 -0
  129. package/templates/minimal-app/apps/webapp/src/modules/files/FilesRoute.tsx.tpl +88 -0
  130. package/templates/minimal-app/apps/webapp/src/modules/files/hooks/useFilesRoute.ts.tpl +50 -0
  131. package/templates/minimal-app/apps/webapp/src/modules/workflows/WorkflowsRoute.tsx.tpl +97 -0
  132. package/templates/minimal-app/apps/webapp/src/modules/workflows/hooks/useWorkflowsRoute.ts.tpl +64 -0
  133. package/templates/minimal-app/apps/webapp/src/utils/i18n.ts.tpl +13 -13
  134. package/templates/minimal-app/apps/webapp/src/utils/trpc.ts.tpl +10 -10
  135. package/templates/minimal-app/apps/webapp/src/vite-env.d.ts.tpl +1 -1
  136. package/templates/minimal-app/apps/webapp/translations/en/app.json.tpl +7 -7
  137. package/templates/minimal-app/apps/webapp/translations/en/{{APP_SLUG}}.json.tpl +193 -135
  138. package/templates/minimal-app/apps/webapp/tsconfig.json.tpl +17 -17
  139. package/templates/minimal-app/apps/webapp/vite.config.ts.tpl +31 -31
  140. package/templates/minimal-app/biome.json.tpl +17 -12
  141. package/templates/minimal-app/package.json.tpl +6 -2
  142. package/templates/minimal-app/pnpm-workspace.yaml.tpl +14 -7
  143. package/templates/minimal-app/template.manifest.json +92 -2
  144. package/templates/minimal-app/apps/server/drizzle/db.ts.tpl +0 -26
  145. package/templates/minimal-app/apps/server/drizzle/guard.ts.tpl +0 -36
  146. package/templates/minimal-app/apps/server/drizzle/reset.ts.tpl +0 -40
  147. package/templates/minimal-app/apps/server/drizzle/sync.ts.tpl +0 -16
@@ -1,44 +1,46 @@
1
- import { AppConfigProvider } from "@m5kdev/frontend/modules/app/components/AppConfigProvider";
2
- import { AppTrpcQueryProvider } from "@m5kdev/frontend/modules/app/components/AppTrpcQueryProvider";
3
- import { AuthProvider } from "@m5kdev/frontend/modules/auth/components/AuthProvider";
4
- import { DialogProvider } from "@m5kdev/web-ui/components/DialogProvider";
5
- import { ThemeProvider } from "@m5kdev/web-ui/components/theme-provider";
6
- import { AppLoader } from "@m5kdev/web-ui/modules/app/components/AppLoader";
7
- import {
8
- APP_LOCALE_CONFIG,
9
- APP_NAME,
10
- APP_ROLES_CONFIG,
11
- } from "{{PACKAGE_SCOPE}}/shared/modules/app/app.constants";
12
- import { NuqsAdapter } from "nuqs/adapters/react-router/v7";
13
- import { BrowserRouter } from "react-router";
14
- import { Toaster } from "sonner";
15
- import { Router } from "./Router";
16
-
17
- export function App() {
18
- return (
19
- <NuqsAdapter>
20
- <BrowserRouter>
21
- <AppConfigProvider
22
- config={{
23
- appName: APP_NAME,
24
- appUrl: import.meta.env.VITE_APP_URL,
25
- serverUrl: import.meta.env.VITE_SERVER_URL,
26
- locales: APP_LOCALE_CONFIG,
27
- roles: APP_ROLES_CONFIG,
28
- }}
29
- >
30
- <ThemeProvider defaultTheme="light" storageKey="m5kdev-theme">
31
- <AuthProvider loader={<AppLoader />}>
32
- <AppTrpcQueryProvider>
33
- <DialogProvider>
34
- <Router />
35
- </DialogProvider>
36
- <Toaster richColors closeButton />
37
- </AppTrpcQueryProvider>
38
- </AuthProvider>
39
- </ThemeProvider>
40
- </AppConfigProvider>
41
- </BrowserRouter>
42
- </NuqsAdapter>
43
- );
44
- }
1
+ import { Toast } from "@heroui/react";
2
+ import { AppConfigProvider } from "@m5kdev/frontend/modules/app/components/AppConfigProvider";
3
+ import { AppTrpcQueryProvider } from "@m5kdev/frontend/modules/app/components/AppTrpcQueryProvider";
4
+ import { AuthProvider } from "@m5kdev/frontend/modules/auth/components/AuthProvider";
5
+ import { DialogProvider } from "@m5kdev/web-ui/components/DialogProvider";
6
+ import { ThemeProvider } from "@m5kdev/web-ui/components/theme-provider";
7
+ import { AppLoader } from "@m5kdev/web-ui/modules/app/components/AppLoader";
8
+ import {
9
+ APP_LOCALE_CONFIG,
10
+ APP_NAME,
11
+ APP_ROLES_CONFIG,
12
+ } from "{{PACKAGE_SCOPE}}/shared/modules/app/app.constants";
13
+ import { NuqsAdapter } from "nuqs/adapters/react-router/v7";
14
+ import { BrowserRouter } from "react-router";
15
+ import { Toaster } from "sonner";
16
+ import { Router } from "./Router";
17
+
18
+ export function App() {
19
+ return (
20
+ <NuqsAdapter>
21
+ <BrowserRouter>
22
+ <AppConfigProvider
23
+ config={{
24
+ appName: APP_NAME,
25
+ appUrl: import.meta.env.VITE_APP_URL,
26
+ serverUrl: import.meta.env.VITE_SERVER_URL,
27
+ locales: APP_LOCALE_CONFIG,
28
+ roles: APP_ROLES_CONFIG,
29
+ }}
30
+ >
31
+ <ThemeProvider defaultTheme="light" storageKey="m5kdev-theme">
32
+ <AuthProvider loader={<AppLoader />}>
33
+ <AppTrpcQueryProvider>
34
+ <DialogProvider>
35
+ <Router />
36
+ </DialogProvider>
37
+ <Toaster richColors closeButton />
38
+ <Toast.Provider placement="bottom end" />
39
+ </AppTrpcQueryProvider>
40
+ </AuthProvider>
41
+ </ThemeProvider>
42
+ </AppConfigProvider>
43
+ </BrowserRouter>
44
+ </NuqsAdapter>
45
+ );
46
+ }
@@ -1,65 +1,96 @@
1
- import { authClient } from "@m5kdev/frontend/modules/auth/auth.lib";
2
- import { useSession } from "@m5kdev/frontend/modules/auth/hooks/useSession";
3
- import { AppShell } from "@m5kdev/web-ui/modules/app/components/AppShell";
4
- import { AppSidebarContent } from "@m5kdev/web-ui/modules/app/components/AppSidebarContent";
5
- import { AppSidebarHeader } from "@m5kdev/web-ui/modules/app/components/AppSidebarHeader";
6
- import { AppSidebarUser } from "@m5kdev/web-ui/modules/app/components/AppSidebarUser";
7
- import { AuthOrganizationSelect } from "@m5kdev/web-ui/modules/auth/components/AuthOrganizationSelect";
8
- import { AuthUtilityImpersonationBanner } from "@m5kdev/web-ui/modules/auth/components/AuthUtilityImpersonationBanner";
9
- import { APP_NAME } from "{{PACKAGE_SCOPE}}/shared/modules/app/app.constants";
10
- import { FileIcon, UsersIcon } from "lucide-react";
11
- import { useState } from "react";
12
- import { useTranslation } from "react-i18next";
13
- import { useNavigate } from "react-router";
14
-
15
- export function Layout() {
16
- const { data: session } = useSession();
17
- const { t } = useTranslation("{{APP_SLUG}}");
18
- const navigate = useNavigate();
19
-
20
- // ephemeral UI state: which collapsible sidebar groups are open
21
- const [navigationState, setNavigationState] = useState<Record<string, boolean>>({});
22
-
23
- const onSignOut = () => {
24
- authClient.signOut();
25
- navigate("/login");
26
- };
27
-
28
- return (
29
- <AppShell
30
- header={<AuthUtilityImpersonationBanner />}
31
- sidebar={{
32
- header: <AppSidebarHeader logo={{ src: "/logo.svg", alt: APP_NAME }} title={APP_NAME} />,
33
- content: (
34
- <>
35
- <div className="px-2 group-data-[collapsible=icon]:hidden">
36
- <AuthOrganizationSelect />
37
- </div>
38
- <AppSidebarContent
39
- navigationItems={[
40
- {
41
- label: t("layout.navigation.posts"),
42
- icon: <FileIcon />,
43
- link: "/",
44
- },
45
- {
46
- label: t("layout.navigation.members"),
47
- icon: <UsersIcon />,
48
- link: "/organization/members",
49
- },
50
- ]}
51
- navigationState={navigationState}
52
- onNavigationStateChange={setNavigationState}
53
- />
54
- </>
55
- ),
56
- footer: (
57
- // data-testid so e2e (and app tests) can assert on the signed-in user
58
- <div data-testid="session-email" className="contents">
59
- <AppSidebarUser user={session?.user} onSignOut={onSignOut} />
60
- </div>
61
- ),
62
- }}
63
- />
64
- );
65
- }
1
+ // biome-ignore-all assist/source/organizeImports: feature-gated lucide icons stay in marker blocks
2
+ import { authClient } from "@m5kdev/frontend/modules/auth/auth.lib";
3
+ import { useSession } from "@m5kdev/frontend/modules/auth/hooks/useSession";
4
+ import { AppShell } from "@m5kdev/web-ui/modules/app/components/AppShell";
5
+ import { AppSidebarContent } from "@m5kdev/web-ui/modules/app/components/AppSidebarContent";
6
+ import { AppSidebarHeader } from "@m5kdev/web-ui/modules/app/components/AppSidebarHeader";
7
+ import { AppSidebarUser } from "@m5kdev/web-ui/modules/app/components/AppSidebarUser";
8
+ import { AuthOrganizationSelect } from "@m5kdev/web-ui/modules/auth/components/AuthOrganizationSelect";
9
+ import { AuthUtilityImpersonationBanner } from "@m5kdev/web-ui/modules/auth/components/AuthUtilityImpersonationBanner";
10
+ import { APP_NAME } from "{{PACKAGE_SCOPE}}/shared/modules/app/app.constants";
11
+ import { FileIcon, UsersIcon } from "lucide-react";
12
+ // m5k:ai:start
13
+ import { SparklesIcon } from "lucide-react";
14
+ // m5k:ai:end
15
+ // m5k:files:start
16
+ import { ImageIcon } from "lucide-react";
17
+ // m5k:files:end
18
+ // m5k:workflows:start
19
+ import { WorkflowIcon } from "lucide-react";
20
+ // m5k:workflows:end
21
+ import { useState } from "react";
22
+ import { useTranslation } from "react-i18next";
23
+ import { useNavigate } from "react-router";
24
+
25
+ export function Layout() {
26
+ const { data: session } = useSession();
27
+ const { t } = useTranslation("{{APP_SLUG}}");
28
+ const navigate = useNavigate();
29
+
30
+ // ephemeral UI state: which collapsible sidebar groups are open
31
+ const [navigationState, setNavigationState] = useState<Record<string, boolean>>({});
32
+
33
+ const onSignOut = () => {
34
+ authClient.signOut();
35
+ navigate("/login");
36
+ };
37
+
38
+ return (
39
+ <AppShell
40
+ header={<AuthUtilityImpersonationBanner />}
41
+ sidebar={{
42
+ header: <AppSidebarHeader logo={{ src: "/logo.svg", alt: APP_NAME }} title={APP_NAME} />,
43
+ content: (
44
+ <>
45
+ <div className="px-2 group-data-[collapsible=icon]:hidden">
46
+ <AuthOrganizationSelect />
47
+ </div>
48
+ <AppSidebarContent
49
+ navigationItems={[
50
+ {
51
+ label: t("layout.navigation.posts"),
52
+ icon: <FileIcon />,
53
+ link: "/",
54
+ },
55
+ // m5k:ai:start
56
+ {
57
+ label: t("layout.navigation.conversation"),
58
+ icon: <SparklesIcon />,
59
+ link: "/conversation",
60
+ },
61
+ // m5k:ai:end
62
+ // m5k:files:start
63
+ {
64
+ label: t("layout.navigation.files"),
65
+ icon: <ImageIcon />,
66
+ link: "/files",
67
+ },
68
+ // m5k:files:end
69
+ // m5k:workflows:start
70
+ {
71
+ label: t("layout.navigation.workflows"),
72
+ icon: <WorkflowIcon />,
73
+ link: "/workflows",
74
+ },
75
+ // m5k:workflows:end
76
+ {
77
+ label: t("layout.navigation.members"),
78
+ icon: <UsersIcon />,
79
+ link: "/organization/members",
80
+ },
81
+ ]}
82
+ navigationState={navigationState}
83
+ onNavigationStateChange={setNavigationState}
84
+ />
85
+ </>
86
+ ),
87
+ footer: (
88
+ // data-testid so e2e (and app tests) can assert on the signed-in user
89
+ <div data-testid="session-email" className="contents">
90
+ <AppSidebarUser user={session?.user} onSignOut={onSignOut} />
91
+ </div>
92
+ ),
93
+ }}
94
+ />
95
+ );
96
+ }
@@ -1,93 +1,114 @@
1
- import { syncI18nLocale } from "@m5kdev/frontend/modules/app/utils/locale";
2
- import { AuthAdminRouter } from "@m5kdev/web-ui/modules/auth/components/AuthAdminRouter";
3
- import { AuthOrganizationAcceptInvitationRoute } from "@m5kdev/web-ui/modules/auth/components/AuthOrganizationAcceptInvitationRoute";
4
- import { AuthOrganizationRouter } from "@m5kdev/web-ui/modules/auth/components/AuthOrganizationRouter";
5
- import { AuthPublicRouter } from "@m5kdev/web-ui/modules/auth/components/AuthPublicRouter";
6
- import { AuthUserRouter } from "@m5kdev/web-ui/modules/auth/components/AuthUserRouter";
7
- import { AuthUtilityProtectedRoutes as ProtectedRoutes } from "@m5kdev/web-ui/modules/auth/components/AuthUtilityProtectedRoutes";
8
- import { APP_NAME } from "{{PACKAGE_SCOPE}}/shared/modules/app/app.constants";
9
- import { useTranslation } from "react-i18next";
10
- import { Route, Routes } from "react-router";
11
- import { z } from "zod";
12
- import { PostsRoute } from "@/modules/posts/PostsRoute";
13
- import { Layout } from "./Layout";
14
-
15
- const preferenceSchema = z.object({
16
- compactMode: z.boolean().optional(),
17
- postsPerPage: z.number().min(1).max(20).optional(),
18
- });
19
-
20
- const preferenceControls = {
21
- compactMode: {
22
- label: "Compact mode",
23
- element: "switch",
24
- },
25
- postsPerPage: {
26
- label: "Posts per page",
27
- element: "number",
28
- min: 1,
29
- max: 20,
30
- step: 1,
31
- },
32
- } as const;
33
-
34
- function AuthHeader() {
35
- const { t } = useTranslation("{{APP_SLUG}}");
36
-
37
- return (
38
- <div className="text-center">
39
- <p className="text-[0.68rem] font-semibold uppercase tracking-[0.3em] text-default-500">
40
- {t("auth.header.eyebrow")}
41
- </p>
42
- <h1 className="mt-3 font-editorial text-4xl text-ink">{APP_NAME}</h1>
43
- <p className="mt-3 text-sm text-default-600">{t("auth.header.tagline")}</p>
44
- </div>
45
- );
46
- }
47
-
48
- export function Router() {
49
- const isWaitlist = import.meta.env.VITE_ENABLE_WAITLIST === "true";
50
-
51
- return (
52
- <Routes>
53
- {/* Built-in routers register as function calls so their <Route> elements
54
- land directly in this tree. */}
55
- {AuthPublicRouter({
56
- header: <AuthHeader />,
57
- waitlist: isWaitlist,
58
- onLocaleChange: syncI18nLocale,
59
- })}
60
-
61
- {/* public: a logged-out invitee must reach this route so it can send
62
- them to signup — inside ProtectedRoutes they'd bounce to /login */}
63
- <Route
64
- path="/organization/accept-invitation"
65
- element={<AuthOrganizationAcceptInvitationRoute />}
66
- />
67
-
68
- <Route
69
- element={
70
- <ProtectedRoutes>
71
- <Layout />
72
- </ProtectedRoutes>
73
- }
74
- >
75
- <Route index element={<PostsRoute />} />
76
-
77
- {AuthUserRouter({
78
- schema: preferenceSchema,
79
- controls: preferenceControls,
80
- onLocaleChange: syncI18nLocale,
81
- })}
82
- {AuthOrganizationRouter({
83
- schema: preferenceSchema,
84
- controls: preferenceControls,
85
- })}
86
- {AuthAdminRouter({
87
- enableWaitlist: true,
88
- enableAccountClaimActions: true,
89
- })}
90
- </Route>
91
- </Routes>
92
- );
93
- }
1
+ // biome-ignore-all assist/source/organizeImports: feature-gated route imports stay in marker blocks
2
+ import { syncI18nLocale } from "@m5kdev/frontend/modules/app/utils/locale";
3
+ import { AuthAdminRouter } from "@m5kdev/web-ui/modules/auth/components/AuthAdminRouter";
4
+ import { AuthOrganizationAcceptInvitationRoute } from "@m5kdev/web-ui/modules/auth/components/AuthOrganizationAcceptInvitationRoute";
5
+ import { AuthOrganizationRouter } from "@m5kdev/web-ui/modules/auth/components/AuthOrganizationRouter";
6
+ import { AuthPublicRouter } from "@m5kdev/web-ui/modules/auth/components/AuthPublicRouter";
7
+ import { AuthUserRouter } from "@m5kdev/web-ui/modules/auth/components/AuthUserRouter";
8
+ import { AuthUtilityProtectedRoutes as ProtectedRoutes } from "@m5kdev/web-ui/modules/auth/components/AuthUtilityProtectedRoutes";
9
+ import { APP_NAME } from "{{PACKAGE_SCOPE}}/shared/modules/app/app.constants";
10
+ import { useTranslation } from "react-i18next";
11
+ import { Route, Routes } from "react-router";
12
+ import { z } from "zod";
13
+ import { PostsRoute } from "@/modules/posts/PostsRoute";
14
+ // m5k:ai:start
15
+ import { ConversationRoute } from "@/modules/conversation/ConversationRoute";
16
+ // m5k:ai:end
17
+ // m5k:files:start
18
+ import { FilesRoute } from "@/modules/files/FilesRoute";
19
+ // m5k:files:end
20
+ // m5k:workflows:start
21
+ import { WorkflowsRoute } from "@/modules/workflows/WorkflowsRoute";
22
+ // m5k:workflows:end
23
+ import { Layout } from "./Layout";
24
+
25
+ const preferenceSchema = z.object({
26
+ compactMode: z.boolean().optional(),
27
+ postsPerPage: z.number().min(1).max(20).optional(),
28
+ });
29
+
30
+ const preferenceControls = {
31
+ compactMode: {
32
+ label: "Compact mode",
33
+ element: "switch",
34
+ },
35
+ postsPerPage: {
36
+ label: "Posts per page",
37
+ element: "number",
38
+ min: 1,
39
+ max: 20,
40
+ step: 1,
41
+ },
42
+ } as const;
43
+
44
+ function AuthHeader() {
45
+ const { t } = useTranslation("{{APP_SLUG}}");
46
+
47
+ return (
48
+ <div className="text-center">
49
+ <p className="text-[0.68rem] font-semibold uppercase tracking-[0.3em] text-default-500">
50
+ {t("auth.header.eyebrow")}
51
+ </p>
52
+ <h1 className="mt-3 font-editorial text-4xl text-ink">{APP_NAME}</h1>
53
+ <p className="mt-3 text-sm text-default-600">{t("auth.header.tagline")}</p>
54
+ </div>
55
+ );
56
+ }
57
+
58
+ export function Router() {
59
+ const isWaitlist = import.meta.env.VITE_ENABLE_WAITLIST === "true";
60
+
61
+ return (
62
+ <Routes>
63
+ {/* Built-in routers register as function calls so their <Route> elements
64
+ land directly in this tree. */}
65
+ {AuthPublicRouter({
66
+ header: <AuthHeader />,
67
+ waitlist: isWaitlist,
68
+ onLocaleChange: syncI18nLocale,
69
+ })}
70
+
71
+ {/* public: a logged-out invitee must reach this route so it can send
72
+ them to signup — inside ProtectedRoutes they'd bounce to /login */}
73
+ <Route
74
+ path="/organization/accept-invitation"
75
+ element={<AuthOrganizationAcceptInvitationRoute />}
76
+ />
77
+
78
+ <Route
79
+ element={
80
+ <ProtectedRoutes>
81
+ <Layout />
82
+ </ProtectedRoutes>
83
+ }
84
+ >
85
+ <Route index element={<PostsRoute />} />
86
+ {[
87
+ // m5k:ai:start
88
+ <Route key="conversation" path="conversation" element={<ConversationRoute />} />,
89
+ // m5k:ai:end
90
+ // m5k:files:start
91
+ <Route key="files" path="files" element={<FilesRoute />} />,
92
+ // m5k:files:end
93
+ // m5k:workflows:start
94
+ <Route key="workflows" path="workflows" element={<WorkflowsRoute />} />,
95
+ // m5k:workflows:end
96
+ ]}
97
+
98
+ {AuthUserRouter({
99
+ schema: preferenceSchema,
100
+ controls: preferenceControls,
101
+ onLocaleChange: syncI18nLocale,
102
+ })}
103
+ {AuthOrganizationRouter({
104
+ schema: preferenceSchema,
105
+ controls: preferenceControls,
106
+ })}
107
+ {AuthAdminRouter({
108
+ enableWaitlist: true,
109
+ enableAccountClaimActions: true,
110
+ })}
111
+ </Route>
112
+ </Routes>
113
+ );
114
+ }
@@ -1,15 +1,15 @@
1
- /* biome-ignore-all lint/suspicious/noUnknownAtRules: Tailwind v4 */
2
-
3
- @import "tailwindcss";
4
- @import "@heroui/styles";
5
- @import "tw-animate-css";
6
-
7
- @source "./node_modules/@m5kdev/web-ui/dist/src/**/*.{mjs}";
8
- @source "../node_modules/@m5kdev/web-ui/dist/src/**/*.{mjs}";
9
- @source "../../node_modules/@m5kdev/web-ui/dist/src/**/*.{mjs}";
10
- @source "../../../node_modules/@m5kdev/web-ui/dist/src/**/*.{mjs}";
11
- @source "../../../../node_modules/@m5kdev/web-ui/dist/src/**/*.{mjs}";
12
-
13
- :root {
14
- --accent: oklch(0.6204 0.195 253.83);
15
- }
1
+ /* biome-ignore-all lint/suspicious/noUnknownAtRules: Tailwind v4 */
2
+
3
+ @import "tailwindcss";
4
+ @import "@heroui/styles";
5
+ @import "tw-animate-css";
6
+
7
+ @source "./node_modules/@m5kdev/web-ui/dist/src/**/*.{mjs}";
8
+ @source "../node_modules/@m5kdev/web-ui/dist/src/**/*.{mjs}";
9
+ @source "../../node_modules/@m5kdev/web-ui/dist/src/**/*.{mjs}";
10
+ @source "../../../node_modules/@m5kdev/web-ui/dist/src/**/*.{mjs}";
11
+ @source "../../../../node_modules/@m5kdev/web-ui/dist/src/**/*.{mjs}";
12
+
13
+ :root {
14
+ --accent: oklch(0.6204 0.195 253.83);
15
+ }
@@ -1,26 +1,26 @@
1
- import type { PostHogConfig } from "posthog-js";
2
- import { PostHogProvider } from "posthog-js/react";
3
- import { StrictMode } from "react";
4
- import { createRoot } from "react-dom/client";
5
- import { App } from "./App";
6
- import "./index.css";
7
- import "./utils/i18n";
8
-
9
- const options = {
10
- api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST,
11
- defaults: "2025-05-24",
12
- } satisfies Partial<PostHogConfig>;
13
-
14
- const rootElement = document.getElementById("root");
15
-
16
- if (!rootElement) {
17
- throw new Error("Root element #root not found.");
18
- }
19
-
20
- createRoot(rootElement).render(
21
- <StrictMode>
22
- <PostHogProvider apiKey={import.meta.env.VITE_PUBLIC_POSTHOG_KEY ?? "demo"} options={options}>
23
- <App />
24
- </PostHogProvider>
25
- </StrictMode>
26
- );
1
+ import type { PostHogConfig } from "posthog-js";
2
+ import { PostHogProvider } from "posthog-js/react";
3
+ import { StrictMode } from "react";
4
+ import { createRoot } from "react-dom/client";
5
+ import { App } from "./App";
6
+ import "./index.css";
7
+ import "./utils/i18n";
8
+
9
+ const options = {
10
+ api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST,
11
+ defaults: "2025-05-24",
12
+ } satisfies Partial<PostHogConfig>;
13
+
14
+ const rootElement = document.getElementById("root");
15
+
16
+ if (!rootElement) {
17
+ throw new Error("Root element #root not found.");
18
+ }
19
+
20
+ createRoot(rootElement).render(
21
+ <StrictMode>
22
+ <PostHogProvider apiKey={import.meta.env.VITE_PUBLIC_POSTHOG_KEY ?? "demo"} options={options}>
23
+ <App />
24
+ </PostHogProvider>
25
+ </StrictMode>
26
+ );
@@ -0,0 +1,36 @@
1
+ import { Card, Skeleton } from "@heroui/react";
2
+ import { useSession } from "@m5kdev/frontend/modules/auth/hooks/useSession";
3
+ import { AiConversation } from "@m5kdev/web-ui/modules/ai/components/AiConversation";
4
+ import { STARTER_ASSISTANT_AGENT_ID } from "{{PACKAGE_SCOPE}}/shared/modules/conversation/conversation.constants";
5
+ import { useTranslation } from "react-i18next";
6
+
7
+ export function ConversationRoute() {
8
+ const { t } = useTranslation("{{APP_SLUG}}");
9
+ const { data: session } = useSession();
10
+ const threadId = session?.user?.id;
11
+
12
+ return (
13
+ <div
14
+ className="grid h-svh grid-rows-[auto_minmax(0,1fr)] gap-4 p-4"
15
+ data-testid="conversation-route"
16
+ >
17
+ <Card>
18
+ <Card.Header className="flex flex-col gap-1">
19
+ <p className="text-xs font-medium uppercase tracking-wide text-muted">
20
+ {t("conversation.hero.eyebrow")}
21
+ </p>
22
+ <Card.Title>{t("conversation.hero.title")}</Card.Title>
23
+ <Card.Description>{t("conversation.hero.body")}</Card.Description>
24
+ </Card.Header>
25
+ </Card>
26
+
27
+ {threadId ? (
28
+ <div className="min-h-0 overflow-hidden rounded-xl border">
29
+ <AiConversation agentId={STARTER_ASSISTANT_AGENT_ID} threadId={threadId} />
30
+ </div>
31
+ ) : (
32
+ <Skeleton className="min-h-0 rounded-xl" />
33
+ )}
34
+ </div>
35
+ );
36
+ }