@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,81 @@
1
+ /**
2
+ * Tenant-scoped-table migration pattern template.
3
+ * Produces: CREATE TABLE with tenancy trio (tenant_id, workspace_id, org_id),
4
+ * RLS policy, updated_at trigger, and standard indexes.
5
+ */
6
+ export function tenantScopedTableTemplate(domain, name) {
7
+ const tableName = name.replace(/-/g, '_');
8
+ const timestamp = new Date().toISOString().replace(/[-:T.]/g, '').slice(0, 14);
9
+ return `-- Migration: ${timestamp}_${name}
10
+ -- Domain: ${domain}
11
+ -- Pattern: tenant-scoped-table
12
+ -- Creates a tenant-scoped table with RLS, audit columns, and updated_at trigger.
13
+
14
+ CREATE TABLE IF NOT EXISTS ${tableName} (
15
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
16
+ tenant_id UUID NOT NULL,
17
+ workspace_id UUID,
18
+ org_id UUID,
19
+
20
+ -- Domain-specific columns
21
+ name VARCHAR(255) NOT NULL,
22
+ status VARCHAR(50) NOT NULL DEFAULT 'active',
23
+ metadata JSONB DEFAULT '{}'::jsonb,
24
+
25
+ -- Audit columns
26
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
27
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
28
+ created_by UUID,
29
+ updated_by UUID,
30
+ deleted_at TIMESTAMPTZ
31
+ );
32
+
33
+ -- ── Indexes ──────────────────────────────────────────────────
34
+
35
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_tenant_id
36
+ ON ${tableName}(tenant_id);
37
+
38
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_workspace_id
39
+ ON ${tableName}(workspace_id)
40
+ WHERE workspace_id IS NOT NULL;
41
+
42
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_status
43
+ ON ${tableName}(tenant_id, status);
44
+
45
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_created_at
46
+ ON ${tableName}(created_at DESC);
47
+
48
+ -- ── Row-Level Security ───────────────────────────────────────
49
+
50
+ ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;
51
+
52
+ CREATE POLICY ${tableName}_tenant_isolation
53
+ ON ${tableName}
54
+ FOR ALL
55
+ TO authenticated
56
+ USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
57
+
58
+ -- ── Updated-at trigger ───────────────────────────────────────
59
+
60
+ CREATE OR REPLACE FUNCTION update_${tableName}_updated_at()
61
+ RETURNS TRIGGER AS $$
62
+ BEGIN
63
+ NEW.updated_at = now();
64
+ RETURN NEW;
65
+ END;
66
+ $$ LANGUAGE plpgsql;
67
+
68
+ CREATE TRIGGER trg_${tableName}_updated_at
69
+ BEFORE UPDATE ON ${tableName}
70
+ FOR EACH ROW
71
+ EXECUTE FUNCTION update_${tableName}_updated_at();
72
+
73
+ -- ── Soft-delete policy ───────────────────────────────────────
74
+
75
+ CREATE POLICY ${tableName}_soft_delete
76
+ ON ${tableName}
77
+ FOR SELECT
78
+ TO authenticated
79
+ USING (deleted_at IS NULL);
80
+ `;
81
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Dashboard page pattern template — dashboard page with widget grid layout.
3
+ */
4
+ export declare function dashboardPageTemplate(pageName: string): string;
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Dashboard page pattern template — dashboard page with widget grid layout.
3
+ */
4
+ export function dashboardPageTemplate(pageName) {
5
+ const componentName = `${pageName}Page`;
6
+ return `import { useQuery } from '@tanstack/react-query';
7
+ import { useTib } from '@/hooks/useTib';
8
+ import { Page } from '@/components/layout/Page';
9
+ import { PageHeader } from '@/components/layout/PageHeader';
10
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
11
+ import { ErrorState } from '@/components/state/ErrorState';
12
+ import { LoadingState } from '@/components/state/LoadingState';
13
+ import { EmptyState } from '@/components/state/EmptyState';
14
+ import { BarChart3, Activity, Users, Clock } from 'lucide-react';
15
+
16
+ interface ${componentName}Props {
17
+ workspaceId: string;
18
+ }
19
+
20
+ interface DashboardStats {
21
+ totalItems: number;
22
+ activeItems: number;
23
+ recentActivity: number;
24
+ avgCompletion: string;
25
+ }
26
+
27
+ interface RecentItem {
28
+ id: string;
29
+ name: string;
30
+ status: string;
31
+ updatedAt: string;
32
+ }
33
+
34
+ export function ${componentName}({ workspaceId }: ${componentName}Props) {
35
+ const tib = useTib();
36
+
37
+ const { data: stats, isLoading: statsLoading, isError: statsError, refetch: refetchStats } = useQuery({
38
+ queryKey: ['dashboard-stats', workspaceId],
39
+ queryFn: () => tib.dashboard.get() as Promise<DashboardStats>,
40
+ staleTime: 30_000,
41
+ });
42
+
43
+ const { data: recent, isLoading: recentLoading } = useQuery({
44
+ queryKey: ['dashboard-recent', workspaceId],
45
+ queryFn: () => tib.data.list({ workspaceId, limit: 5 }) as Promise<{ items: RecentItem[] }>,
46
+ staleTime: 30_000,
47
+ });
48
+
49
+ const isLoading = statsLoading || recentLoading;
50
+
51
+ return (
52
+ <Page title="Dashboard" description="Overview of your workspace">
53
+ <Page.Header
54
+ breadcrumb={[{ label: '${pageName}' }]}
55
+ />
56
+ <Page.Body>
57
+ {statsError ? (
58
+ <ErrorState
59
+ message="Failed to load dashboard data"
60
+ onRetry={() => refetchStats()}
61
+ />
62
+ ) : isLoading ? (
63
+ <LoadingState variant="page" />
64
+ ) : !stats ? (
65
+ <EmptyState
66
+ icon={BarChart3}
67
+ title="No data yet"
68
+ description="Start creating items to see your dashboard."
69
+ />
70
+ ) : (
71
+ <div className="space-y-6">
72
+ {/* Stat cards grid */}
73
+ <div className="layout-cards">
74
+ <Card>
75
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
76
+ <CardTitle className="text-sm font-medium text-muted-foreground">
77
+ Total Items
78
+ </CardTitle>
79
+ <BarChart3 className="h-4 w-4 text-muted-foreground" />
80
+ </CardHeader>
81
+ <CardContent>
82
+ <p className="text-2xl font-semibold">{stats.totalItems}</p>
83
+ </CardContent>
84
+ </Card>
85
+
86
+ <Card>
87
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
88
+ <CardTitle className="text-sm font-medium text-muted-foreground">
89
+ Active
90
+ </CardTitle>
91
+ <Activity className="h-4 w-4 text-muted-foreground" />
92
+ </CardHeader>
93
+ <CardContent>
94
+ <p className="text-2xl font-semibold">{stats.activeItems}</p>
95
+ </CardContent>
96
+ </Card>
97
+
98
+ <Card>
99
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
100
+ <CardTitle className="text-sm font-medium text-muted-foreground">
101
+ Recent Activity
102
+ </CardTitle>
103
+ <Users className="h-4 w-4 text-muted-foreground" />
104
+ </CardHeader>
105
+ <CardContent>
106
+ <p className="text-2xl font-semibold">{stats.recentActivity}</p>
107
+ </CardContent>
108
+ </Card>
109
+
110
+ <Card>
111
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
112
+ <CardTitle className="text-sm font-medium text-muted-foreground">
113
+ Avg Completion
114
+ </CardTitle>
115
+ <Clock className="h-4 w-4 text-muted-foreground" />
116
+ </CardHeader>
117
+ <CardContent>
118
+ <p className="text-2xl font-semibold">{stats.avgCompletion}</p>
119
+ </CardContent>
120
+ </Card>
121
+ </div>
122
+
123
+ {/* Recent items */}
124
+ <Card>
125
+ <CardHeader>
126
+ <CardTitle className="text-lg font-semibold">Recent Items</CardTitle>
127
+ </CardHeader>
128
+ <CardContent>
129
+ {!recent?.items?.length ? (
130
+ <EmptyState
131
+ icon={BarChart3}
132
+ title="No recent items"
133
+ description="Items you create will appear here."
134
+ />
135
+ ) : (
136
+ <div className="space-y-3">
137
+ {recent.items.map((item) => (
138
+ <div
139
+ key={item.id}
140
+ className="flex items-center justify-between rounded-lg border border-border p-3"
141
+ >
142
+ <div>
143
+ <p className="text-sm font-medium">{item.name}</p>
144
+ <p className="text-xs text-muted-foreground">
145
+ Updated {new Date(item.updatedAt).toLocaleDateString()}
146
+ </p>
147
+ </div>
148
+ <span className="text-xs text-muted-foreground">{item.status}</span>
149
+ </div>
150
+ ))}
151
+ </div>
152
+ )}
153
+ </CardContent>
154
+ </Card>
155
+ </div>
156
+ )}
157
+ </Page.Body>
158
+ </Page>
159
+ );
160
+ }
161
+
162
+ export default ${componentName};
163
+ `;
164
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Detail page pattern template — single entity detail page with tabs for related data.
3
+ */
4
+ export declare function detailPageTemplate(pageName: string): string;
@@ -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) {
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,4 @@
1
+ /**
2
+ * Form page pattern template — create/edit form with Zod validation.
3
+ */
4
+ export declare function formPageTemplate(pageName: string): string;
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Form page pattern template — create/edit form with Zod validation.
3
+ */
4
+ export function formPageTemplate(pageName) {
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,8 @@
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
+ export type PagePattern = 'list' | 'detail' | 'form' | 'wizard' | 'dashboard' | 'settings';
8
+ export declare const PAGE_PATTERNS: readonly PagePattern[];
@@ -0,0 +1,14 @@
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
+ export const PAGE_PATTERNS = [
8
+ 'list',
9
+ 'detail',
10
+ 'form',
11
+ 'wizard',
12
+ 'dashboard',
13
+ 'settings',
14
+ ];
@@ -0,0 +1,4 @@
1
+ /**
2
+ * List page pattern template — data table page with pagination, sorting, filtering.
3
+ */
4
+ export declare function listPageTemplate(pageName: string): string;