@mettlecast/domain-cli 0.2.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 (301) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +133 -0
  3. package/dist/builder/build-registry.d.ts +20 -0
  4. package/dist/builder/build-registry.js +178 -0
  5. package/dist/builder/build-types.d.ts +10 -0
  6. package/dist/builder/build-types.js +32 -0
  7. package/dist/builder/load-domain-module-config.d.ts +12 -0
  8. package/dist/builder/load-domain-module-config.js +34 -0
  9. package/dist/builder/load-module.d.ts +26 -0
  10. package/dist/builder/load-module.js +114 -0
  11. package/dist/builder/zod-to-json.d.ts +10 -0
  12. package/dist/builder/zod-to-json.js +15 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +338 -0
  15. package/dist/commands/add-api.d.ts +32 -0
  16. package/dist/commands/add-api.js +106 -0
  17. package/dist/commands/add-component.d.ts +15 -0
  18. package/dist/commands/add-component.js +78 -0
  19. package/dist/commands/add-domain.d.ts +21 -0
  20. package/dist/commands/add-domain.js +71 -0
  21. package/dist/commands/add-flow.d.ts +14 -0
  22. package/dist/commands/add-flow.js +108 -0
  23. package/dist/commands/add-hook.d.ts +15 -0
  24. package/dist/commands/add-hook.js +65 -0
  25. package/dist/commands/add-migration.d.ts +26 -0
  26. package/dist/commands/add-migration.js +93 -0
  27. package/dist/commands/add-module.d.ts +7 -0
  28. package/dist/commands/add-module.js +261 -0
  29. package/dist/commands/add-page.d.ts +13 -0
  30. package/dist/commands/add-page.js +170 -0
  31. package/dist/commands/add-seed-page.d.ts +5 -0
  32. package/dist/commands/add-seed-page.js +251 -0
  33. package/dist/commands/add-subscriber.d.ts +30 -0
  34. package/dist/commands/add-subscriber.js +87 -0
  35. package/dist/commands/add-test.d.ts +22 -0
  36. package/dist/commands/add-test.js +285 -0
  37. package/dist/commands/audit.d.ts +39 -0
  38. package/dist/commands/audit.js +171 -0
  39. package/dist/commands/build-catalog.d.ts +68 -0
  40. package/dist/commands/build-catalog.js +105 -0
  41. package/dist/commands/build-flows.d.ts +26 -0
  42. package/dist/commands/build-flows.js +174 -0
  43. package/dist/commands/build.d.ts +17 -0
  44. package/dist/commands/build.js +39 -0
  45. package/dist/commands/check-hashes.d.ts +38 -0
  46. package/dist/commands/check-hashes.js +97 -0
  47. package/dist/commands/create-project.d.ts +5 -0
  48. package/dist/commands/create-project.js +272 -0
  49. package/dist/commands/dev.d.ts +15 -0
  50. package/dist/commands/dev.js +27 -0
  51. package/dist/commands/doctor.d.ts +48 -0
  52. package/dist/commands/doctor.js +1301 -0
  53. package/dist/commands/explain.d.ts +34 -0
  54. package/dist/commands/explain.js +167 -0
  55. package/dist/commands/power-tune.d.ts +1 -0
  56. package/dist/commands/power-tune.js +87 -0
  57. package/dist/commands/reseed-page.d.ts +6 -0
  58. package/dist/commands/reseed-page.js +297 -0
  59. package/dist/commands/show-dns.d.ts +15 -0
  60. package/dist/commands/show-dns.js +101 -0
  61. package/dist/commands/show.d.ts +46 -0
  62. package/dist/commands/show.js +135 -0
  63. package/dist/commands/test.d.ts +18 -0
  64. package/dist/commands/test.js +56 -0
  65. package/dist/commands/upgrade-backend.d.ts +9 -0
  66. package/dist/commands/upgrade-backend.js +46 -0
  67. package/dist/commands/upgrade-event.d.ts +22 -0
  68. package/dist/commands/upgrade-event.js +111 -0
  69. package/dist/commands/upgrade-frontend.d.ts +5 -0
  70. package/dist/commands/upgrade-frontend.js +99 -0
  71. package/dist/commands/upgrade.d.ts +7 -0
  72. package/dist/commands/upgrade.js +495 -0
  73. package/dist/commands/validate.d.ts +30 -0
  74. package/dist/commands/validate.js +148 -0
  75. package/dist/runtime-stubs/hydrate-local-ctx.d.ts +49 -0
  76. package/dist/runtime-stubs/hydrate-local-ctx.js +45 -0
  77. package/dist/runtime-stubs/load-secrets.d.ts +11 -0
  78. package/dist/runtime-stubs/load-secrets.js +37 -0
  79. package/dist/server/api-server.d.ts +21 -0
  80. package/dist/server/api-server.js +14 -0
  81. package/dist/server/mount-routes.d.ts +19 -0
  82. package/dist/server/mount-routes.js +42 -0
  83. package/dist/templates/api-skeleton.d.ts +18 -0
  84. package/dist/templates/api-skeleton.js +69 -0
  85. package/dist/templates/claude-md.d.ts +4 -0
  86. package/dist/templates/claude-md.js +7 -0
  87. package/dist/templates/dashboard-pages/index.d.ts +101 -0
  88. package/dist/templates/dashboard-pages/index.js +22 -0
  89. package/dist/templates/domain-config.d.ts +10 -0
  90. package/dist/templates/domain-config.js +19 -0
  91. package/dist/templates/events-skeleton.d.ts +9 -0
  92. package/dist/templates/events-skeleton.js +23 -0
  93. package/dist/templates/patterns/api/create-with-event.d.ts +5 -0
  94. package/dist/templates/patterns/api/create-with-event.js +69 -0
  95. package/dist/templates/patterns/api/idempotent-mutation.d.ts +5 -0
  96. package/dist/templates/patterns/api/idempotent-mutation.js +99 -0
  97. package/dist/templates/patterns/api/paginated-list.d.ts +5 -0
  98. package/dist/templates/patterns/api/paginated-list.js +71 -0
  99. package/dist/templates/patterns/api/simple-crud.d.ts +6 -0
  100. package/dist/templates/patterns/api/simple-crud.js +95 -0
  101. package/dist/templates/patterns/api/system-admin.d.ts +5 -0
  102. package/dist/templates/patterns/api/system-admin.js +98 -0
  103. package/dist/templates/patterns/api/webhook-receiver-style.d.ts +5 -0
  104. package/dist/templates/patterns/api/webhook-receiver-style.js +125 -0
  105. package/dist/templates/patterns/component/index.d.ts +25 -0
  106. package/dist/templates/patterns/component/index.js +814 -0
  107. package/dist/templates/patterns/flow/autonomous-remediation.d.ts +5 -0
  108. package/dist/templates/patterns/flow/autonomous-remediation.js +98 -0
  109. package/dist/templates/patterns/flow/fan-out-reaction.d.ts +5 -0
  110. package/dist/templates/patterns/flow/fan-out-reaction.js +53 -0
  111. package/dist/templates/patterns/flow/human-in-loop.d.ts +5 -0
  112. package/dist/templates/patterns/flow/human-in-loop.js +90 -0
  113. package/dist/templates/patterns/flow/multi-step-orchestration.d.ts +5 -0
  114. package/dist/templates/patterns/flow/multi-step-orchestration.js +67 -0
  115. package/dist/templates/patterns/hook/index.d.ts +7 -0
  116. package/dist/templates/patterns/hook/index.js +378 -0
  117. package/dist/templates/patterns/migration/column-add.d.ts +5 -0
  118. package/dist/templates/patterns/migration/column-add.js +52 -0
  119. package/dist/templates/patterns/migration/event-store.d.ts +6 -0
  120. package/dist/templates/patterns/migration/event-store.js +97 -0
  121. package/dist/templates/patterns/migration/index.d.ts +5 -0
  122. package/dist/templates/patterns/migration/index.js +52 -0
  123. package/dist/templates/patterns/migration/rls-policy.d.ts +5 -0
  124. package/dist/templates/patterns/migration/rls-policy.js +78 -0
  125. package/dist/templates/patterns/migration/tenant-scoped-table.d.ts +6 -0
  126. package/dist/templates/patterns/migration/tenant-scoped-table.js +81 -0
  127. package/dist/templates/patterns/page/dashboard.d.ts +4 -0
  128. package/dist/templates/patterns/page/dashboard.js +164 -0
  129. package/dist/templates/patterns/page/detail.d.ts +4 -0
  130. package/dist/templates/patterns/page/detail.js +162 -0
  131. package/dist/templates/patterns/page/form.d.ts +4 -0
  132. package/dist/templates/patterns/page/form.js +192 -0
  133. package/dist/templates/patterns/page/index.d.ts +8 -0
  134. package/dist/templates/patterns/page/index.js +14 -0
  135. package/dist/templates/patterns/page/list.d.ts +4 -0
  136. package/dist/templates/patterns/page/list.js +138 -0
  137. package/dist/templates/patterns/page/settings.d.ts +4 -0
  138. package/dist/templates/patterns/page/settings.js +318 -0
  139. package/dist/templates/patterns/page/wizard.d.ts +4 -0
  140. package/dist/templates/patterns/page/wizard.js +293 -0
  141. package/dist/templates/patterns/subscriber/audit-relay.d.ts +5 -0
  142. package/dist/templates/patterns/subscriber/audit-relay.js +51 -0
  143. package/dist/templates/patterns/subscriber/cascade-deletion.d.ts +5 -0
  144. package/dist/templates/patterns/subscriber/cascade-deletion.js +60 -0
  145. package/dist/templates/patterns/subscriber/single-step-projection.d.ts +5 -0
  146. package/dist/templates/patterns/subscriber/single-step-projection.js +36 -0
  147. package/dist/templates/readme.d.ts +4 -0
  148. package/dist/templates/readme.js +7 -0
  149. package/dist/templates/subscriber-skeleton.d.ts +11 -0
  150. package/dist/templates/subscriber-skeleton.js +23 -0
  151. package/dist/utils/checksum.d.ts +18 -0
  152. package/dist/utils/checksum.js +39 -0
  153. package/dist/utils/file-helpers.d.ts +35 -0
  154. package/dist/utils/file-helpers.js +59 -0
  155. package/dist/utils/header-inject.d.ts +15 -0
  156. package/dist/utils/header-inject.js +93 -0
  157. package/dist/utils/install-file.d.ts +17 -0
  158. package/dist/utils/install-file.js +78 -0
  159. package/dist/utils/logger.d.ts +12 -0
  160. package/dist/utils/logger.js +24 -0
  161. package/dist/utils/manifest.d.ts +28 -0
  162. package/dist/utils/manifest.js +93 -0
  163. package/dist/utils/s3-fetch.d.ts +25 -0
  164. package/dist/utils/s3-fetch.js +52 -0
  165. package/dist/utils/scaffold-config.d.ts +95 -0
  166. package/dist/utils/scaffold-config.js +53 -0
  167. package/dist/utils/token-substitution.d.ts +35 -0
  168. package/dist/utils/token-substitution.js +52 -0
  169. package/dist/utils/zod-extractor.d.ts +10 -0
  170. package/dist/utils/zod-extractor.js +28 -0
  171. package/package.json +43 -0
  172. package/src/__tests__/build-registry.test.ts +115 -0
  173. package/src/__tests__/build-types.test.ts +100 -0
  174. package/src/__tests__/builder/walkDomainDir.test.ts +140 -0
  175. package/src/__tests__/commands/add-domain.test.ts +174 -0
  176. package/src/__tests__/commands/add-flow.test.ts +69 -0
  177. package/src/__tests__/commands/add-module.test.ts +207 -0
  178. package/src/__tests__/commands/build-flows.test.ts +154 -0
  179. package/src/__tests__/commands/check-hashes.test.ts +142 -0
  180. package/src/__tests__/commands/create-project.test.ts +712 -0
  181. package/src/__tests__/commands/upgrade.test.ts +762 -0
  182. package/src/__tests__/doctor.test.ts +291 -0
  183. package/src/__tests__/hydrate-local-ctx.test.ts +71 -0
  184. package/src/__tests__/load-domain-module-config.test.ts +10 -0
  185. package/src/__tests__/mount-routes.test.ts +100 -0
  186. package/src/__tests__/scaffold-src/part-a-layout.test.ts +46 -0
  187. package/src/__tests__/scripts/package-scaffold.test.ts +45 -0
  188. package/src/__tests__/utils/checksum.test.ts +90 -0
  189. package/src/__tests__/utils/header-inject.test.ts +89 -0
  190. package/src/__tests__/utils/install-file.test.ts +207 -0
  191. package/src/__tests__/utils/manifest.test.ts +229 -0
  192. package/src/__tests__/utils/token-substitution.test.ts +134 -0
  193. package/src/__tests__/validate.test.ts +18 -0
  194. package/src/builder/build-registry.ts +237 -0
  195. package/src/builder/build-types.ts +36 -0
  196. package/src/builder/load-domain-module-config.ts +43 -0
  197. package/src/builder/load-module.ts +133 -0
  198. package/src/builder/zod-to-json.ts +16 -0
  199. package/src/cli.ts +387 -0
  200. package/src/commands/add-api.ts +145 -0
  201. package/src/commands/add-component.ts +116 -0
  202. package/src/commands/add-domain.ts +100 -0
  203. package/src/commands/add-flow.ts +136 -0
  204. package/src/commands/add-hook.ts +100 -0
  205. package/src/commands/add-migration.ts +129 -0
  206. package/src/commands/add-module.ts +347 -0
  207. package/src/commands/add-page.ts +242 -0
  208. package/src/commands/add-seed-page.ts +338 -0
  209. package/src/commands/add-subscriber.ts +123 -0
  210. package/src/commands/add-test.ts +328 -0
  211. package/src/commands/audit.ts +217 -0
  212. package/src/commands/build-catalog.ts +181 -0
  213. package/src/commands/build-flows.ts +198 -0
  214. package/src/commands/build.ts +59 -0
  215. package/src/commands/check-hashes.ts +150 -0
  216. package/src/commands/create-project.ts +367 -0
  217. package/src/commands/dev.ts +47 -0
  218. package/src/commands/doctor.ts +1362 -0
  219. package/src/commands/explain.ts +209 -0
  220. package/src/commands/power-tune.ts +112 -0
  221. package/src/commands/reseed-page.ts +410 -0
  222. package/src/commands/show-dns.ts +139 -0
  223. package/src/commands/show.ts +179 -0
  224. package/src/commands/test.ts +91 -0
  225. package/src/commands/upgrade-backend.ts +72 -0
  226. package/src/commands/upgrade-event.ts +175 -0
  227. package/src/commands/upgrade-frontend.ts +135 -0
  228. package/src/commands/upgrade.ts +668 -0
  229. package/src/commands/validate.ts +208 -0
  230. package/src/runtime-stubs/hydrate-local-ctx.ts +102 -0
  231. package/src/runtime-stubs/load-secrets.ts +40 -0
  232. package/src/server/api-server.ts +34 -0
  233. package/src/server/mount-routes.ts +68 -0
  234. package/src/templates/api-skeleton.ts +72 -0
  235. package/src/templates/claude-md.ts +7 -0
  236. package/src/templates/dashboard-pages/account/api-keys.tsx +107 -0
  237. package/src/templates/dashboard-pages/account/audit-log.tsx +60 -0
  238. package/src/templates/dashboard-pages/account/index.ts +5 -0
  239. package/src/templates/dashboard-pages/account/members.tsx +107 -0
  240. package/src/templates/dashboard-pages/account/profile.tsx +53 -0
  241. package/src/templates/dashboard-pages/account/workspace-settings.tsx +64 -0
  242. package/src/templates/dashboard-pages/auth/accept-invitation.tsx +62 -0
  243. package/src/templates/dashboard-pages/auth/choose-org.tsx +66 -0
  244. package/src/templates/dashboard-pages/auth/forgot-password.tsx +69 -0
  245. package/src/templates/dashboard-pages/auth/index.ts +10 -0
  246. package/src/templates/dashboard-pages/auth/login.tsx +75 -0
  247. package/src/templates/dashboard-pages/auth/mfa-setup.tsx +56 -0
  248. package/src/templates/dashboard-pages/auth/mfa-verify.tsx +49 -0
  249. package/src/templates/dashboard-pages/auth/reset-password.tsx +64 -0
  250. package/src/templates/dashboard-pages/auth/sign-out.tsx +20 -0
  251. package/src/templates/dashboard-pages/auth/signup.tsx +71 -0
  252. package/src/templates/dashboard-pages/auth/verify-email.tsx +53 -0
  253. package/src/templates/dashboard-pages/index.ts +22 -0
  254. package/src/templates/dashboard-pages/shell/dashboard-home.tsx +41 -0
  255. package/src/templates/dashboard-pages/shell/forbidden.tsx +46 -0
  256. package/src/templates/dashboard-pages/shell/index.ts +5 -0
  257. package/src/templates/dashboard-pages/shell/maintenance.tsx +27 -0
  258. package/src/templates/dashboard-pages/shell/not-found.tsx +34 -0
  259. package/src/templates/dashboard-pages/shell/server-error.tsx +44 -0
  260. package/src/templates/domain-config.ts +20 -0
  261. package/src/templates/events-skeleton.ts +24 -0
  262. package/src/templates/patterns/api/create-with-event.ts +72 -0
  263. package/src/templates/patterns/api/idempotent-mutation.ts +102 -0
  264. package/src/templates/patterns/api/paginated-list.ts +74 -0
  265. package/src/templates/patterns/api/simple-crud.ts +100 -0
  266. package/src/templates/patterns/api/system-admin.ts +101 -0
  267. package/src/templates/patterns/api/webhook-receiver-style.ts +128 -0
  268. package/src/templates/patterns/component/index.ts +833 -0
  269. package/src/templates/patterns/flow/autonomous-remediation.ts +101 -0
  270. package/src/templates/patterns/flow/fan-out-reaction.ts +56 -0
  271. package/src/templates/patterns/flow/human-in-loop.ts +93 -0
  272. package/src/templates/patterns/flow/multi-step-orchestration.ts +70 -0
  273. package/src/templates/patterns/hook/index.ts +382 -0
  274. package/src/templates/patterns/migration/column-add.ts +54 -0
  275. package/src/templates/patterns/migration/event-store.ts +99 -0
  276. package/src/templates/patterns/migration/index.ts +54 -0
  277. package/src/templates/patterns/migration/rls-policy.ts +80 -0
  278. package/src/templates/patterns/migration/tenant-scoped-table.ts +83 -0
  279. package/src/templates/patterns/page/dashboard.ts +164 -0
  280. package/src/templates/patterns/page/detail.ts +162 -0
  281. package/src/templates/patterns/page/form.ts +192 -0
  282. package/src/templates/patterns/page/index.ts +17 -0
  283. package/src/templates/patterns/page/list.ts +138 -0
  284. package/src/templates/patterns/page/settings.ts +318 -0
  285. package/src/templates/patterns/page/wizard.ts +293 -0
  286. package/src/templates/patterns/subscriber/audit-relay.ts +54 -0
  287. package/src/templates/patterns/subscriber/cascade-deletion.ts +63 -0
  288. package/src/templates/patterns/subscriber/single-step-projection.ts +39 -0
  289. package/src/templates/readme.ts +7 -0
  290. package/src/templates/subscriber-skeleton.ts +28 -0
  291. package/src/utils/checksum.ts +43 -0
  292. package/src/utils/file-helpers.ts +91 -0
  293. package/src/utils/header-inject.ts +126 -0
  294. package/src/utils/install-file.ts +99 -0
  295. package/src/utils/logger.ts +26 -0
  296. package/src/utils/manifest.ts +137 -0
  297. package/src/utils/s3-fetch.ts +91 -0
  298. package/src/utils/scaffold-config.ts +172 -0
  299. package/src/utils/token-substitution.ts +80 -0
  300. package/src/utils/zod-extractor.ts +31 -0
  301. package/tsconfig.json +13 -0
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Detail page pattern template — single entity detail page with tabs for related data.
3
+ */
4
+ export function detailPageTemplate(pageName: string): string {
5
+ const componentName = `${pageName}Page`;
6
+ return `import { useSearchParams } from 'react-router-dom';
7
+ import { ArrowLeft, Pencil, Trash2 } from 'lucide-react';
8
+ import { useQuery } from '@tanstack/react-query';
9
+ import { useTib } from '@/hooks/useTib';
10
+ import { Page } from '@/components/layout/Page';
11
+ import { PageHeader } from '@/components/layout/PageHeader';
12
+ import { Button } from '@/components/ui/button';
13
+ import { Badge } from '@/components/ui/badge';
14
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
15
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
16
+ import { ErrorState } from '@/components/state/ErrorState';
17
+ import { LoadingState } from '@/components/state/LoadingState';
18
+ import { EmptyState } from '@/components/state/EmptyState';
19
+ import { useNavigate } from 'react-router-dom';
20
+
21
+ interface ${componentName}Props {
22
+ workspaceId: string;
23
+ entityId: string;
24
+ }
25
+
26
+ interface EntityDetail {
27
+ id: string;
28
+ name: string;
29
+ status: string;
30
+ description?: string;
31
+ createdAt: string;
32
+ updatedAt: string;
33
+ }
34
+
35
+ export function ${componentName}({ workspaceId, entityId }: ${componentName}Props) {
36
+ const tib = useTib();
37
+ const navigate = useNavigate();
38
+ const [searchParams, setSearchParams] = useSearchParams();
39
+ const activeTab = searchParams.get('tab') ?? 'overview';
40
+
41
+ const { data, isLoading, isError, error, refetch } = useQuery({
42
+ queryKey: ['entity-detail', workspaceId, entityId],
43
+ queryFn: () => tib.data.get({ workspaceId, id: entityId }) as Promise<EntityDetail>,
44
+ staleTime: 10_000,
45
+ enabled: !!entityId,
46
+ });
47
+
48
+ const setTab = (tab: string) => setSearchParams({ tab });
49
+
50
+ return (
51
+ <Page title={data?.name ?? 'Detail'} description={data?.description ?? 'View entity details'}>
52
+ <Page.Header
53
+ breadcrumb={[
54
+ { label: '${pageName}', to: '..' },
55
+ { label: data?.name ?? entityId },
56
+ ]}
57
+ status={data && (
58
+ <Badge variant={data.status === 'active' ? 'success' : 'neutral'}>
59
+ {data.status}
60
+ </Badge>
61
+ )}
62
+ actions={
63
+ <>
64
+ <Button variant="outline" size="sm" onClick={() => navigate(-1)}>
65
+ <ArrowLeft className="h-4 w-4 mr-1" />
66
+ Back
67
+ </Button>
68
+ <Button variant="outline" size="sm">
69
+ <Pencil className="h-4 w-4 mr-1" />
70
+ Edit
71
+ </Button>
72
+ <Button variant="destructive" size="sm">
73
+ <Trash2 className="h-4 w-4 mr-1" />
74
+ Delete
75
+ </Button>
76
+ </>
77
+ }
78
+ />
79
+ <Page.Tabs items={[
80
+ { value: 'overview', label: 'Overview', to: 'overview' },
81
+ { value: 'related', label: 'Related', to: 'related' },
82
+ { value: 'activity', label: 'Activity', to: 'activity' },
83
+ ]} />
84
+ <Page.Body>
85
+ {isLoading ? (
86
+ <LoadingState variant="page" />
87
+ ) : isError ? (
88
+ <ErrorState
89
+ message={error instanceof Error ? error.message : 'Failed to load entity'}
90
+ onRetry={() => refetch()}
91
+ />
92
+ ) : !data ? (
93
+ <EmptyState
94
+ icon={Pencil}
95
+ title="Entity not found"
96
+ description="The requested entity could not be found."
97
+ />
98
+ ) : (
99
+ <Tabs value={activeTab} onValueChange={setTab}>
100
+ <TabsContent value="overview">
101
+ <div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
102
+ <Card>
103
+ <CardHeader>
104
+ <CardTitle className="text-lg font-semibold">Details</CardTitle>
105
+ </CardHeader>
106
+ <CardContent className="space-y-3">
107
+ <div>
108
+ <p className="text-sm text-muted-foreground">Name</p>
109
+ <p className="text-sm font-medium">{data.name}</p>
110
+ </div>
111
+ <div>
112
+ <p className="text-sm text-muted-foreground">Status</p>
113
+ <Badge variant={data.status === 'active' ? 'success' : 'neutral'}>
114
+ {data.status}
115
+ </Badge>
116
+ </div>
117
+ <div>
118
+ <p className="text-sm text-muted-foreground">Created</p>
119
+ <p className="text-sm">{new Date(data.createdAt).toLocaleDateString()}</p>
120
+ </div>
121
+ <div>
122
+ <p className="text-sm text-muted-foreground">Updated</p>
123
+ <p className="text-sm">{new Date(data.updatedAt).toLocaleDateString()}</p>
124
+ </div>
125
+ </CardContent>
126
+ </Card>
127
+ <Card>
128
+ <CardHeader>
129
+ <CardTitle className="text-lg font-semibold">Description</CardTitle>
130
+ </CardHeader>
131
+ <CardContent>
132
+ <p className="text-sm text-muted-foreground">
133
+ {data.description ?? 'No description provided.'}
134
+ </p>
135
+ </CardContent>
136
+ </Card>
137
+ </div>
138
+ </TabsContent>
139
+ <TabsContent value="related">
140
+ <EmptyState
141
+ icon={Pencil}
142
+ title="No related data"
143
+ description="Related items will appear here."
144
+ />
145
+ </TabsContent>
146
+ <TabsContent value="activity">
147
+ <EmptyState
148
+ icon={Pencil}
149
+ title="No recent activity"
150
+ description="Activity for this entity will appear here."
151
+ />
152
+ </TabsContent>
153
+ </Tabs>
154
+ )}
155
+ </Page.Body>
156
+ </Page>
157
+ );
158
+ }
159
+
160
+ export default ${componentName};
161
+ `;
162
+ }
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Form page pattern template — create/edit form with Zod validation.
3
+ */
4
+ export function formPageTemplate(pageName: string): string {
5
+ const componentName = `${pageName}Page`;
6
+ return `import { useForm } from 'react-hook-form';
7
+ import { zodResolver } from '@hookform/resolvers/zod';
8
+ import { z } from 'zod';
9
+ import { useNavigate } from 'react-router-dom';
10
+ import { ArrowLeft, Save } from 'lucide-react';
11
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
12
+ import { useTib } from '@/hooks/useTib';
13
+ import { Page } from '@/components/layout/Page';
14
+ import { PageHeader } from '@/components/layout/PageHeader';
15
+ import { Button } from '@/components/ui/button';
16
+ import { Input } from '@/components/ui/input';
17
+ import { Label } from '@/components/ui/label';
18
+ import { Textarea } from '@/components/ui/textarea';
19
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
20
+ import { ErrorState } from '@/components/state/ErrorState';
21
+ import { LoadingState } from '@/components/state/LoadingState';
22
+ import { toast } from 'sonner';
23
+
24
+ const formSchema = z.object({
25
+ name: z.string().min(1, 'Name is required').max(100, 'Name must be 100 characters or less'),
26
+ description: z.string().max(500).optional(),
27
+ status: z.enum(['active', 'inactive']),
28
+ type: z.string().min(1, 'Type is required'),
29
+ });
30
+
31
+ type FormValues = z.infer<typeof formSchema>;
32
+
33
+ interface ${componentName}Props {
34
+ workspaceId: string;
35
+ entityId?: string; // undefined = create mode, defined = edit mode
36
+ }
37
+
38
+ export function ${componentName}({ workspaceId, entityId }: ${componentName}Props) {
39
+ const tib = useTib();
40
+ const navigate = useNavigate();
41
+ const queryClient = useQueryClient();
42
+ const isEdit = !!entityId;
43
+
44
+ const { data: existing, isLoading: isLoadingExisting } = useQuery({
45
+ queryKey: ['entity-detail', workspaceId, entityId],
46
+ queryFn: () => tib.data.get({ workspaceId, id: entityId! }) as Promise<FormValues>,
47
+ enabled: isEdit,
48
+ staleTime: 30_000,
49
+ });
50
+
51
+ const {
52
+ register,
53
+ handleSubmit,
54
+ formState: { errors, isSubmitting },
55
+ setValue,
56
+ watch,
57
+ } = useForm<FormValues>({
58
+ resolver: zodResolver(formSchema),
59
+ defaultValues: {
60
+ name: '',
61
+ description: '',
62
+ status: 'active',
63
+ type: '',
64
+ },
65
+ values: existing,
66
+ });
67
+
68
+ const mutation = useMutation({
69
+ mutationFn: (values: FormValues) =>
70
+ isEdit
71
+ ? tib.data.update({ workspaceId, id: entityId!, ...values })
72
+ : tib.data.create({ workspaceId, ...values }),
73
+ onSuccess: () => {
74
+ toast.success(isEdit ? '\${pageName} updated' : '\${pageName} created');
75
+ queryClient.invalidateQueries({ queryKey: ['data-list', workspaceId] });
76
+ navigate('..');
77
+ },
78
+ onError: (err) => {
79
+ toast.error('Failed to save', {
80
+ description: err instanceof Error ? err.message : 'Unknown error',
81
+ });
82
+ },
83
+ });
84
+
85
+ const onSubmit = (values: FormValues) => mutation.mutate(values);
86
+
87
+ if (isEdit && isLoadingExisting) {
88
+ return <LoadingState variant="page" />;
89
+ }
90
+
91
+ return (
92
+ <Page title={isEdit ? 'Edit ${pageName}' : 'Create ${pageName}'} description={isEdit ? 'Update existing entity' : 'Add a new entity'}>
93
+ <Page.Header
94
+ breadcrumb={[
95
+ { label: '${pageName}', to: '..' },
96
+ { label: isEdit ? 'Edit' : 'Create' },
97
+ ]}
98
+ actions={
99
+ <Button variant="outline" size="sm" onClick={() => navigate(-1)}>
100
+ <ArrowLeft className="h-4 w-4 mr-1" />
101
+ Back
102
+ </Button>
103
+ }
104
+ />
105
+ <Page.Body>
106
+ <form onSubmit={handleSubmit(onSubmit)} className="max-w-2xl space-y-6">
107
+ <div className="space-y-2">
108
+ <Label htmlFor="name">Name</Label>
109
+ <Input
110
+ id="name"
111
+ {...register('name')}
112
+ placeholder="Enter name"
113
+ aria-invalid={!!errors.name}
114
+ aria-describedby={errors.name ? 'name-error' : undefined}
115
+ />
116
+ {errors.name && (
117
+ <p id="name-error" className="text-xs text-destructive">{errors.name.message}</p>
118
+ )}
119
+ </div>
120
+
121
+ <div className="space-y-2">
122
+ <Label htmlFor="description">Description</Label>
123
+ <Textarea
124
+ id="description"
125
+ {...register('description')}
126
+ placeholder="Optional description"
127
+ rows={3}
128
+ />
129
+ {errors.description && (
130
+ <p className="text-xs text-destructive">{errors.description.message}</p>
131
+ )}
132
+ </div>
133
+
134
+ <div className="space-y-2">
135
+ <Label htmlFor="type">Type</Label>
136
+ <Input
137
+ id="type"
138
+ {...register('type')}
139
+ placeholder="Enter type"
140
+ aria-invalid={!!errors.type}
141
+ aria-describedby={errors.type ? 'type-error' : undefined}
142
+ />
143
+ {errors.type && (
144
+ <p id="type-error" className="text-xs text-destructive">{errors.type.message}</p>
145
+ )}
146
+ </div>
147
+
148
+ <div className="space-y-2">
149
+ <Label htmlFor="status">Status</Label>
150
+ <Select
151
+ value={watch('status')}
152
+ onValueChange={(value) => setValue('status', value as 'active' | 'inactive')}
153
+ >
154
+ <SelectTrigger id="status">
155
+ <SelectValue placeholder="Select status" />
156
+ </SelectTrigger>
157
+ <SelectContent>
158
+ <SelectItem value="active">Active</SelectItem>
159
+ <SelectItem value="inactive">Inactive</SelectItem>
160
+ </SelectContent>
161
+ </Select>
162
+ {errors.status && (
163
+ <p className="text-xs text-destructive">{errors.status.message}</p>
164
+ )}
165
+ </div>
166
+
167
+ <div className="flex items-center gap-3 pt-4">
168
+ <Button
169
+ type="submit"
170
+ variant="teal"
171
+ disabled={isSubmitting || mutation.isPending}
172
+ >
173
+ <Save className="h-4 w-4 mr-1" />
174
+ {mutation.isPending ? 'Saving...' : isEdit ? 'Update' : 'Create'}
175
+ </Button>
176
+ <Button
177
+ type="button"
178
+ variant="outline"
179
+ onClick={() => navigate(-1)}
180
+ >
181
+ Cancel
182
+ </Button>
183
+ </div>
184
+ </form>
185
+ </Page.Body>
186
+ </Page>
187
+ );
188
+ }
189
+
190
+ export default ${componentName};
191
+ `;
192
+ }
@@ -0,0 +1,17 @@
1
+ export { listPageTemplate } from './list.js';
2
+ export { detailPageTemplate } from './detail.js';
3
+ export { formPageTemplate } from './form.js';
4
+ export { wizardPageTemplate } from './wizard.js';
5
+ export { dashboardPageTemplate } from './dashboard.js';
6
+ export { settingsPageTemplate } from './settings.js';
7
+
8
+ export type PagePattern = 'list' | 'detail' | 'form' | 'wizard' | 'dashboard' | 'settings';
9
+
10
+ export const PAGE_PATTERNS: readonly PagePattern[] = [
11
+ 'list',
12
+ 'detail',
13
+ 'form',
14
+ 'wizard',
15
+ 'dashboard',
16
+ 'settings',
17
+ ] as const;
@@ -0,0 +1,138 @@
1
+ /**
2
+ * List page pattern template — data table page with pagination, sorting, filtering.
3
+ */
4
+ export function listPageTemplate(pageName: string): string {
5
+ const componentName = `${pageName}Page`;
6
+ return `import { useState, useCallback } from 'react';
7
+ import { useSearchParams } from 'react-router-dom';
8
+ import { Plus, Search, Filter } from 'lucide-react';
9
+ import { useQuery } from '@tanstack/react-query';
10
+ import { useTib } from '@/hooks/useTib';
11
+ import { Page } from '@/components/layout/Page';
12
+ import { PageHeader } from '@/components/layout/PageHeader';
13
+ import { DataTable } from '@/components/data/DataTable';
14
+ import { FilterBar } from '@/components/data/FilterBar';
15
+ import { Button } from '@/components/ui/button';
16
+ import { Input } from '@/components/ui/input';
17
+ import { EmptyState } from '@/components/state/EmptyState';
18
+ import { ErrorState } from '@/components/state/ErrorState';
19
+ import { LoadingState } from '@/components/state/LoadingState';
20
+ import type { ColumnDef } from '@tanstack/react-table';
21
+
22
+ interface ${componentName}Props {
23
+ workspaceId: string;
24
+ }
25
+
26
+ interface DataItem {
27
+ id: string;
28
+ name: string;
29
+ status: string;
30
+ createdAt: string;
31
+ updatedAt: string;
32
+ }
33
+
34
+ const FILTER_DEFS = [
35
+ { key: 'status', label: 'Status', type: 'select' as const, options: [
36
+ { value: 'active', label: 'Active' },
37
+ { value: 'inactive', label: 'Inactive' },
38
+ ]},
39
+ { key: 'search', label: 'Search', type: 'text' as const },
40
+ ];
41
+
42
+ const COLUMNS: ColumnDef<DataItem>[] = [
43
+ {
44
+ accessorKey: 'name',
45
+ header: 'Name',
46
+ enableSorting: true,
47
+ },
48
+ {
49
+ accessorKey: 'status',
50
+ header: 'Status',
51
+ enableSorting: true,
52
+ },
53
+ {
54
+ accessorKey: 'createdAt',
55
+ header: 'Created',
56
+ enableSorting: true,
57
+ },
58
+ {
59
+ accessorKey: 'updatedAt',
60
+ header: 'Updated',
61
+ enableSorting: true,
62
+ },
63
+ ];
64
+
65
+ export function ${componentName}({ workspaceId }: ${componentName}Props) {
66
+ const tib = useTib();
67
+ const [searchParams, setSearchParams] = useSearchParams();
68
+ const [filters, setFilters] = useState<Record<string, string | string[]>>({});
69
+
70
+ const { data, isLoading, isError, error, refetch } = useQuery({
71
+ queryKey: ['data-list', workspaceId, filters],
72
+ queryFn: () => tib.data.list({ workspaceId, ...filters }),
73
+ staleTime: 10_000,
74
+ });
75
+
76
+ const handleFilterChange = useCallback((next: Record<string, string | string[]>) => {
77
+ setFilters(next);
78
+ const params = new URLSearchParams();
79
+ for (const [key, value] of Object.entries(next)) {
80
+ if (Array.isArray(value)) {
81
+ params.set(key, value.join(','));
82
+ } else if (value) {
83
+ params.set(key, value);
84
+ }
85
+ }
86
+ setSearchParams(params, { replace: true });
87
+ }, [setSearchParams]);
88
+
89
+ return (
90
+ <Page title="${pageName}" description="Browse and manage your data">
91
+ <Page.Header
92
+ breadcrumb={[{ label: '${pageName}' }]}
93
+ actions={
94
+ <Button variant="teal" size="sm">
95
+ <Plus className="h-4 w-4 mr-1" />
96
+ New item
97
+ </Button>
98
+ }
99
+ />
100
+ <Page.Body>
101
+ <FilterBar
102
+ filters={FILTER_DEFS}
103
+ value={filters}
104
+ onChange={handleFilterChange}
105
+ className="mb-4"
106
+ />
107
+ {isLoading ? (
108
+ <LoadingState variant="page" />
109
+ ) : isError ? (
110
+ <ErrorState
111
+ message={error instanceof Error ? error.message : 'Failed to load data'}
112
+ onRetry={() => refetch()}
113
+ />
114
+ ) : !data?.items?.length ? (
115
+ <EmptyState
116
+ icon={Search}
117
+ title="No items found"
118
+ description="Create your first item to get started."
119
+ cta={<Button variant="teal">Create item</Button>}
120
+ />
121
+ ) : (
122
+ <DataTable
123
+ columns={COLUMNS}
124
+ data={data.items}
125
+ onRowClick={(row) => {/* navigate to detail */}}
126
+ />
127
+ )}
128
+ </Page.Body>
129
+ <Page.Footer>
130
+ {/* Pagination component goes here */}
131
+ </Page.Footer>
132
+ </Page>
133
+ );
134
+ }
135
+
136
+ export default ${componentName};
137
+ `;
138
+ }