@autoview/cli 0.1.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 (297) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +407 -0
  3. package/lib/AutoViewAgent.d.ts +109 -0
  4. package/lib/AutoViewAgent.js +123 -0
  5. package/lib/AutoViewAgent.js.map +1 -0
  6. package/lib/agent/emitMcpServer.d.ts +15 -0
  7. package/lib/agent/emitMcpServer.js +157 -0
  8. package/lib/agent/emitMcpServer.js.map +1 -0
  9. package/lib/agent/emitReport.d.ts +14 -0
  10. package/lib/agent/emitReport.js +85 -0
  11. package/lib/agent/emitReport.js.map +1 -0
  12. package/lib/agent/toolSurface.d.ts +130 -0
  13. package/lib/agent/toolSurface.js +342 -0
  14. package/lib/agent/toolSurface.js.map +1 -0
  15. package/lib/agent/verifyAgentTasks.d.ts +87 -0
  16. package/lib/agent/verifyAgentTasks.js +126 -0
  17. package/lib/agent/verifyAgentTasks.js.map +1 -0
  18. package/lib/cli/main.d.ts +2 -0
  19. package/lib/cli/main.js +295 -0
  20. package/lib/cli/main.js.map +1 -0
  21. package/lib/compiler/AutoViewInterfaceCompiler.d.ts +27 -0
  22. package/lib/compiler/AutoViewInterfaceCompiler.js +68 -0
  23. package/lib/compiler/AutoViewInterfaceCompiler.js.map +1 -0
  24. package/lib/constants/AutoViewFrontendTemplate.d.ts +1 -0
  25. package/lib/constants/AutoViewFrontendTemplate.js +46 -0
  26. package/lib/constants/AutoViewFrontendTemplate.js.map +1 -0
  27. package/lib/constants/AutoViewSystemPromptConstant.d.ts +5 -0
  28. package/lib/constants/AutoViewSystemPromptConstant.js +4 -0
  29. package/lib/constants/AutoViewSystemPromptConstant.js.map +1 -0
  30. package/lib/context/IAutoViewAgentContext.d.ts +60 -0
  31. package/lib/context/IAutoViewAgentContext.js +3 -0
  32. package/lib/context/IAutoViewAgentContext.js.map +1 -0
  33. package/lib/fromSwagger.d.ts +53 -0
  34. package/lib/fromSwagger.js +513 -0
  35. package/lib/fromSwagger.js.map +1 -0
  36. package/lib/generateDeterministic.d.ts +26 -0
  37. package/lib/generateDeterministic.js +75 -0
  38. package/lib/generateDeterministic.js.map +1 -0
  39. package/lib/index.d.ts +15 -0
  40. package/lib/index.js +41 -0
  41. package/lib/index.js.map +1 -0
  42. package/lib/orchestrate/orchestrateAutoView.d.ts +17 -0
  43. package/lib/orchestrate/orchestrateAutoView.js +491 -0
  44. package/lib/orchestrate/orchestrateAutoView.js.map +1 -0
  45. package/lib/orchestrate/orchestrateAutoViewProductPlan.d.ts +37 -0
  46. package/lib/orchestrate/orchestrateAutoViewProductPlan.js +109 -0
  47. package/lib/orchestrate/orchestrateAutoViewProductPlan.js.map +1 -0
  48. package/lib/orchestrate/orchestrateAutoViewRender.d.ts +133 -0
  49. package/lib/orchestrate/orchestrateAutoViewRender.js +943 -0
  50. package/lib/orchestrate/orchestrateAutoViewRender.js.map +1 -0
  51. package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.d.ts +24 -0
  52. package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.js +92 -0
  53. package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.js.map +1 -0
  54. package/lib/orchestrate/orchestrateAutoViewReview.d.ts +48 -0
  55. package/lib/orchestrate/orchestrateAutoViewReview.js +328 -0
  56. package/lib/orchestrate/orchestrateAutoViewReview.js.map +1 -0
  57. package/lib/orchestrate/orchestrateAutoViewScaffold.d.ts +45 -0
  58. package/lib/orchestrate/orchestrateAutoViewScaffold.js +586 -0
  59. package/lib/orchestrate/orchestrateAutoViewScaffold.js.map +1 -0
  60. package/lib/orchestrate/orchestrateAutoViewSdkStudy.d.ts +26 -0
  61. package/lib/orchestrate/orchestrateAutoViewSdkStudy.js +85 -0
  62. package/lib/orchestrate/orchestrateAutoViewSdkStudy.js.map +1 -0
  63. package/lib/orchestrate/structures/IAutoViewProductPlan.d.ts +96 -0
  64. package/lib/orchestrate/structures/IAutoViewProductPlan.js +3 -0
  65. package/lib/orchestrate/structures/IAutoViewProductPlan.js.map +1 -0
  66. package/lib/orchestrate/structures/IAutoViewProductPlanApplication.d.ts +38 -0
  67. package/lib/orchestrate/structures/IAutoViewProductPlanApplication.js +3 -0
  68. package/lib/orchestrate/structures/IAutoViewProductPlanApplication.js.map +1 -0
  69. package/lib/orchestrate/structures/IAutoViewRenderApplication.d.ts +38 -0
  70. package/lib/orchestrate/structures/IAutoViewRenderApplication.js +3 -0
  71. package/lib/orchestrate/structures/IAutoViewRenderApplication.js.map +1 -0
  72. package/lib/orchestrate/structures/IAutoViewReviewApplication.d.ts +40 -0
  73. package/lib/orchestrate/structures/IAutoViewReviewApplication.js +3 -0
  74. package/lib/orchestrate/structures/IAutoViewReviewApplication.js.map +1 -0
  75. package/lib/orchestrate/structures/IAutoViewSdkMap.d.ts +63 -0
  76. package/lib/orchestrate/structures/IAutoViewSdkMap.js +3 -0
  77. package/lib/orchestrate/structures/IAutoViewSdkMap.js.map +1 -0
  78. package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.d.ts +37 -0
  79. package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.js +3 -0
  80. package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.js.map +1 -0
  81. package/lib/orchestrate/utils/HistoryMessage.d.ts +10 -0
  82. package/lib/orchestrate/utils/HistoryMessage.js +25 -0
  83. package/lib/orchestrate/utils/HistoryMessage.js.map +1 -0
  84. package/lib/orchestrate/utils/auditFrontendRuntime.d.ts +53 -0
  85. package/lib/orchestrate/utils/auditFrontendRuntime.js +362 -0
  86. package/lib/orchestrate/utils/auditFrontendRuntime.js.map +1 -0
  87. package/lib/orchestrate/utils/buildDeterministicPlan.d.ts +4 -0
  88. package/lib/orchestrate/utils/buildDeterministicPlan.js +233 -0
  89. package/lib/orchestrate/utils/buildDeterministicPlan.js.map +1 -0
  90. package/lib/orchestrate/utils/buildDeterministicSdkMap.d.ts +22 -0
  91. package/lib/orchestrate/utils/buildDeterministicSdkMap.js +154 -0
  92. package/lib/orchestrate/utils/buildDeterministicSdkMap.js.map +1 -0
  93. package/lib/orchestrate/utils/cacheNodeModules.d.ts +31 -0
  94. package/lib/orchestrate/utils/cacheNodeModules.js +134 -0
  95. package/lib/orchestrate/utils/cacheNodeModules.js.map +1 -0
  96. package/lib/orchestrate/utils/describeEndpointPropsShape.d.ts +37 -0
  97. package/lib/orchestrate/utils/describeEndpointPropsShape.js +192 -0
  98. package/lib/orchestrate/utils/describeEndpointPropsShape.js.map +1 -0
  99. package/lib/orchestrate/utils/describeEndpointRequestBodyShape.d.ts +22 -0
  100. package/lib/orchestrate/utils/describeEndpointRequestBodyShape.js +29 -0
  101. package/lib/orchestrate/utils/describeEndpointRequestBodyShape.js.map +1 -0
  102. package/lib/orchestrate/utils/describeEndpointResponseShape.d.ts +19 -0
  103. package/lib/orchestrate/utils/describeEndpointResponseShape.js +30 -0
  104. package/lib/orchestrate/utils/describeEndpointResponseShape.js.map +1 -0
  105. package/lib/orchestrate/utils/executeCachedBatch.d.ts +22 -0
  106. package/lib/orchestrate/utils/executeCachedBatch.js +64 -0
  107. package/lib/orchestrate/utils/executeCachedBatch.js.map +1 -0
  108. package/lib/orchestrate/utils/loadShoppingFixture.d.ts +33 -0
  109. package/lib/orchestrate/utils/loadShoppingFixture.js +17 -0
  110. package/lib/orchestrate/utils/loadShoppingFixture.js.map +1 -0
  111. package/lib/orchestrate/utils/normalizeProductPlanPaths.d.ts +24 -0
  112. package/lib/orchestrate/utils/normalizeProductPlanPaths.js +77 -0
  113. package/lib/orchestrate/utils/normalizeProductPlanPaths.js.map +1 -0
  114. package/lib/orchestrate/utils/renderJsonSchema.d.ts +23 -0
  115. package/lib/orchestrate/utils/renderJsonSchema.js +122 -0
  116. package/lib/orchestrate/utils/renderJsonSchema.js.map +1 -0
  117. package/lib/orchestrate/utils/renderResourcePage.d.ts +36 -0
  118. package/lib/orchestrate/utils/renderResourcePage.js +1415 -0
  119. package/lib/orchestrate/utils/renderResourcePage.js.map +1 -0
  120. package/lib/orchestrate/utils/validateFrontendTypecheck.d.ts +109 -0
  121. package/lib/orchestrate/utils/validateFrontendTypecheck.js +274 -0
  122. package/lib/orchestrate/utils/validateFrontendTypecheck.js.map +1 -0
  123. package/lib/preview/renderPreview.d.ts +22 -0
  124. package/lib/preview/renderPreview.js +198 -0
  125. package/lib/preview/renderPreview.js.map +1 -0
  126. package/lib/typings/compiler.d.ts +39 -0
  127. package/lib/typings/compiler.js +3 -0
  128. package/lib/typings/compiler.js.map +1 -0
  129. package/lib/typings/events.d.ts +106 -0
  130. package/lib/typings/events.js +3 -0
  131. package/lib/typings/events.js.map +1 -0
  132. package/lib/typings/index.d.ts +10 -0
  133. package/lib/typings/index.js +27 -0
  134. package/lib/typings/index.js.map +1 -0
  135. package/lib/typings/misc.d.ts +78 -0
  136. package/lib/typings/misc.js +3 -0
  137. package/lib/typings/misc.js.map +1 -0
  138. package/lib/utils/ArrayUtil.d.ts +8 -0
  139. package/lib/utils/ArrayUtil.js +30 -0
  140. package/lib/utils/ArrayUtil.js.map +1 -0
  141. package/lib/utils/StringUtil.d.ts +11 -0
  142. package/lib/utils/StringUtil.js +28 -0
  143. package/lib/utils/StringUtil.js.map +1 -0
  144. package/lib/utils/classifyEndpoints.d.ts +62 -0
  145. package/lib/utils/classifyEndpoints.js +216 -0
  146. package/lib/utils/classifyEndpoints.js.map +1 -0
  147. package/lib/utils/endpointFilter.d.ts +26 -0
  148. package/lib/utils/endpointFilter.js +0 -0
  149. package/lib/utils/endpointFilter.js.map +1 -0
  150. package/lib/utils/extractFields.d.ts +85 -0
  151. package/lib/utils/extractFields.js +231 -0
  152. package/lib/utils/extractFields.js.map +1 -0
  153. package/lib/utils/index.d.ts +13 -0
  154. package/lib/utils/index.js +30 -0
  155. package/lib/utils/index.js.map +1 -0
  156. package/lib/utils/normalizeForNestia.d.ts +34 -0
  157. package/lib/utils/normalizeForNestia.js +133 -0
  158. package/lib/utils/normalizeForNestia.js.map +1 -0
  159. package/lib/utils/resourcePlan.d.ts +39 -0
  160. package/lib/utils/resourcePlan.js +95 -0
  161. package/lib/utils/resourcePlan.js.map +1 -0
  162. package/lib/utils/sliceDocument.d.ts +17 -0
  163. package/lib/utils/sliceDocument.js +114 -0
  164. package/lib/utils/sliceDocument.js.map +1 -0
  165. package/lib/utils/toEndpoints.d.ts +90 -0
  166. package/lib/utils/toEndpoints.js +227 -0
  167. package/lib/utils/toEndpoints.js.map +1 -0
  168. package/lib/verify/runWorkflows.d.ts +25 -0
  169. package/lib/verify/runWorkflows.js +366 -0
  170. package/lib/verify/runWorkflows.js.map +1 -0
  171. package/lib/verify/workflows.d.ts +53 -0
  172. package/lib/verify/workflows.js +107 -0
  173. package/lib/verify/workflows.js.map +1 -0
  174. package/package.json +82 -0
  175. package/prompts/AUTOVIEW_RENDER.md +398 -0
  176. package/prompts/AUTOVIEW_REVIEW.md +60 -0
  177. package/prompts/AUTOVIEW_SDK_STUDY.md +89 -0
  178. package/src/AutoViewAgent.ts +222 -0
  179. package/src/agent/emitMcpServer.integration.test.ts +168 -0
  180. package/src/agent/emitMcpServer.test.ts +51 -0
  181. package/src/agent/emitMcpServer.ts +178 -0
  182. package/src/agent/emitReport.ts +117 -0
  183. package/src/agent/toolSurface.test.ts +243 -0
  184. package/src/agent/toolSurface.ts +501 -0
  185. package/src/agent/verifyAgentTasks.test.ts +106 -0
  186. package/src/agent/verifyAgentTasks.ts +171 -0
  187. package/src/cli/main.ts +363 -0
  188. package/src/compiler/AutoViewInterfaceCompiler.ts +69 -0
  189. package/src/constants/AutoViewFrontendTemplate.ts +42 -0
  190. package/src/constants/AutoViewSystemPromptConstant.ts +6 -0
  191. package/src/context/IAutoViewAgentContext.ts +84 -0
  192. package/src/fromSwagger.test.ts +269 -0
  193. package/src/fromSwagger.ts +500 -0
  194. package/src/generateDeterministic.test.ts +39 -0
  195. package/src/generateDeterministic.ts +77 -0
  196. package/src/index.ts +30 -0
  197. package/src/orchestrate/orchestrateAutoView.ts +590 -0
  198. package/src/orchestrate/orchestrateAutoViewProductPlan.ts +121 -0
  199. package/src/orchestrate/orchestrateAutoViewRender.ts +1117 -0
  200. package/src/orchestrate/orchestrateAutoViewRenderDeterministic.ts +101 -0
  201. package/src/orchestrate/orchestrateAutoViewReview.ts +272 -0
  202. package/src/orchestrate/orchestrateAutoViewScaffold.ts +627 -0
  203. package/src/orchestrate/orchestrateAutoViewSdkStudy.ts +90 -0
  204. package/src/orchestrate/renderNavTs.test.ts +74 -0
  205. package/src/orchestrate/structures/IAutoViewProductPlan.ts +119 -0
  206. package/src/orchestrate/structures/IAutoViewProductPlanApplication.ts +41 -0
  207. package/src/orchestrate/structures/IAutoViewRenderApplication.ts +40 -0
  208. package/src/orchestrate/structures/IAutoViewReviewApplication.ts +42 -0
  209. package/src/orchestrate/structures/IAutoViewSdkMap.ts +72 -0
  210. package/src/orchestrate/structures/IAutoViewSdkStudyApplication.ts +40 -0
  211. package/src/orchestrate/utils/HistoryMessage.ts +41 -0
  212. package/src/orchestrate/utils/auditFrontendRuntime.test.ts +18 -0
  213. package/src/orchestrate/utils/auditFrontendRuntime.ts +454 -0
  214. package/src/orchestrate/utils/buildDeterministicPlan.test.ts +170 -0
  215. package/src/orchestrate/utils/buildDeterministicPlan.ts +289 -0
  216. package/src/orchestrate/utils/buildDeterministicSdkMap.test.ts +90 -0
  217. package/src/orchestrate/utils/buildDeterministicSdkMap.ts +169 -0
  218. package/src/orchestrate/utils/cacheNodeModules.ts +136 -0
  219. package/src/orchestrate/utils/describeEndpointPropsShape.test.ts +86 -0
  220. package/src/orchestrate/utils/describeEndpointPropsShape.ts +202 -0
  221. package/src/orchestrate/utils/describeEndpointRequestBodyShape.test.ts +87 -0
  222. package/src/orchestrate/utils/describeEndpointRequestBodyShape.ts +31 -0
  223. package/src/orchestrate/utils/describeEndpointResponseShape.test.ts +70 -0
  224. package/src/orchestrate/utils/describeEndpointResponseShape.ts +32 -0
  225. package/src/orchestrate/utils/executeCachedBatch.ts +59 -0
  226. package/src/orchestrate/utils/loadShoppingFixture.ts +52 -0
  227. package/src/orchestrate/utils/normalizeProductPlanPaths.ts +92 -0
  228. package/src/orchestrate/utils/renderJsonSchema.test.ts +162 -0
  229. package/src/orchestrate/utils/renderJsonSchema.ts +133 -0
  230. package/src/orchestrate/utils/renderResourcePage.test.ts +468 -0
  231. package/src/orchestrate/utils/renderResourcePage.ts +1624 -0
  232. package/src/orchestrate/utils/validateFrontendTypecheck.test.ts +32 -0
  233. package/src/orchestrate/utils/validateFrontendTypecheck.ts +335 -0
  234. package/src/preview/renderPreview.ts +273 -0
  235. package/src/typings/compiler.ts +47 -0
  236. package/src/typings/events.ts +155 -0
  237. package/src/typings/index.ts +10 -0
  238. package/src/typings/misc.ts +93 -0
  239. package/src/utils/ArrayUtil.ts +16 -0
  240. package/src/utils/StringUtil.ts +29 -0
  241. package/src/utils/classifyEndpoints.test.ts +86 -0
  242. package/src/utils/classifyEndpoints.ts +291 -0
  243. package/src/utils/endpointFilter.test.ts +50 -0
  244. package/src/utils/endpointFilter.ts +0 -0
  245. package/src/utils/extractFields.test.ts +82 -0
  246. package/src/utils/extractFields.ts +306 -0
  247. package/src/utils/index.ts +13 -0
  248. package/src/utils/normalizeForNestia.test.ts +93 -0
  249. package/src/utils/normalizeForNestia.ts +139 -0
  250. package/src/utils/resourcePlan.test.ts +104 -0
  251. package/src/utils/resourcePlan.ts +180 -0
  252. package/src/utils/sliceDocument.test.ts +85 -0
  253. package/src/utils/sliceDocument.ts +119 -0
  254. package/src/utils/toEndpoints.test.ts +251 -0
  255. package/src/utils/toEndpoints.ts +343 -0
  256. package/src/verify/runWorkflows.ts +403 -0
  257. package/src/verify/workflows.test.ts +117 -0
  258. package/src/verify/workflows.ts +154 -0
  259. package/template/CLAUDE.md +140 -0
  260. package/template/Dockerfile +31 -0
  261. package/template/PROMPT.md +80 -0
  262. package/template/SANDBOX.md +70 -0
  263. package/template/app/api/health/route.ts +10 -0
  264. package/template/app/globals.css +97 -0
  265. package/template/app/layout.tsx +30 -0
  266. package/template/app/page.tsx +19 -0
  267. package/template/components/AppShell.tsx +114 -0
  268. package/template/components/auto/CatalogGrid.tsx +159 -0
  269. package/template/components/auto/ConfirmButton.tsx +67 -0
  270. package/template/components/auto/EmbeddedCollection.tsx +144 -0
  271. package/template/components/auto/ResourceDashboard.tsx +104 -0
  272. package/template/components/auto/ResourceDetail.tsx +93 -0
  273. package/template/components/auto/ResourceForm.tsx +235 -0
  274. package/template/components/auto/ResourceIcon.tsx +88 -0
  275. package/template/components/auto/ResourceLanding.tsx +155 -0
  276. package/template/components/auto/ResourceTable.tsx +223 -0
  277. package/template/components/auto/formatValue.tsx +186 -0
  278. package/template/components/auto/types.ts +42 -0
  279. package/template/components/ui/badge.tsx +40 -0
  280. package/template/components/ui/button.tsx +57 -0
  281. package/template/components/ui/card.tsx +86 -0
  282. package/template/components/ui/dialog.tsx +119 -0
  283. package/template/components/ui/input.tsx +23 -0
  284. package/template/components/ui/label.tsx +24 -0
  285. package/template/components/ui/pagination.tsx +117 -0
  286. package/template/components/ui/select.tsx +92 -0
  287. package/template/components/ui/sheet.tsx +135 -0
  288. package/template/components/ui/skeleton.tsx +15 -0
  289. package/template/components/ui/table.tsx +120 -0
  290. package/template/components/ui/tabs.tsx +55 -0
  291. package/template/lib/utils.ts +35 -0
  292. package/template/next.config.mjs +52 -0
  293. package/template/package.json +46 -0
  294. package/template/postcss.config.js +6 -0
  295. package/template/scripts/start-shopping-backend.sh +56 -0
  296. package/template/tailwind.config.ts +96 -0
  297. package/template/tsconfig.json +29 -0
@@ -0,0 +1,159 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import * as React from "react";
5
+
6
+ import { Button } from "@/components/ui/button";
7
+ import { Card, CardContent } from "@/components/ui/card";
8
+ import { Skeleton } from "@/components/ui/skeleton";
9
+
10
+ import { formatCell } from "./formatValue";
11
+ import type { ColumnSpec } from "./types";
12
+
13
+ /** Which fields drive a catalog card, baked by the generator from the schema. */
14
+ export interface CatalogSpec {
15
+ /** Field holding the image URL. */
16
+ imageField: string;
17
+ /** True when {@link imageField} is a `string[]` — use its first element. */
18
+ imageIsArray: boolean;
19
+ /** Field holding the card's title. */
20
+ titleField: string;
21
+ /** A few secondary columns shown under the title. */
22
+ meta: ColumnSpec[];
23
+ }
24
+
25
+ export interface CatalogGridProps {
26
+ title: string;
27
+ description?: string;
28
+ spec: CatalogSpec;
29
+ rows: readonly unknown[];
30
+ loading: boolean;
31
+ error?: string | null;
32
+ onRetry?: () => void;
33
+ rowHref?: (row: Record<string, unknown>) => string | null;
34
+ emptyHint?: string;
35
+ }
36
+
37
+ function imageUrl(row: Record<string, unknown>, spec: CatalogSpec): string | null {
38
+ const v = row[spec.imageField];
39
+ const raw = spec.imageIsArray ? (Array.isArray(v) ? v[0] : undefined) : v;
40
+ return typeof raw === "string" && raw.length > 0 ? raw : null;
41
+ }
42
+
43
+ /**
44
+ * Universal catalog screen — a grid of image cards, one per record. Chosen over
45
+ * the table when the row schema carries an image + a title (a product, a photo,
46
+ * a profile). Schema-driven: the image / title / meta fields are decided by the
47
+ * generator from the element type, so any image-bearing collection reads as a
48
+ * gallery instead of a dense table. Same loading / error / empty / data states
49
+ * as the table.
50
+ */
51
+ export function CatalogGrid(props: CatalogGridProps) {
52
+ const { title, description, spec, loading, error, onRetry, rowHref, emptyHint } =
53
+ props;
54
+ const rows = Array.isArray(props.rows) ? props.rows : [];
55
+
56
+ const body = (() => {
57
+ if (error !== null && error !== undefined) {
58
+ return (
59
+ <Card className="border-destructive/40 bg-destructive/5 p-5">
60
+ <p className="text-sm text-destructive">{error}</p>
61
+ {onRetry ? (
62
+ <Button variant="outline" size="sm" className="mt-3" onClick={onRetry}>
63
+ Retry
64
+ </Button>
65
+ ) : null}
66
+ </Card>
67
+ );
68
+ }
69
+ if (loading) {
70
+ return (
71
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
72
+ {Array.from({ length: 8 }).map((_, i) => (
73
+ <div key={i} className="space-y-2">
74
+ <Skeleton className="aspect-[4/3] w-full" />
75
+ <Skeleton className="h-4 w-2/3" />
76
+ </div>
77
+ ))}
78
+ </div>
79
+ );
80
+ }
81
+ if (rows.length === 0) {
82
+ return (
83
+ <Card className="p-6">
84
+ <p className="text-sm text-muted-foreground">
85
+ {emptyHint ?? "The list came back empty."}
86
+ </p>
87
+ </Card>
88
+ );
89
+ }
90
+ return (
91
+ <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
92
+ {rows.map((raw, i) => {
93
+ const row = (raw ?? {}) as Record<string, unknown>;
94
+ const img = imageUrl(row, spec);
95
+ const href = rowHref ? rowHref(row) : null;
96
+ const card = (
97
+ <Card className="h-full overflow-hidden transition-colors hover:border-primary/50">
98
+ <div className="aspect-[4/3] bg-muted">
99
+ {img ? (
100
+ // eslint-disable-next-line @next/next/no-img-element
101
+ <img
102
+ src={img}
103
+ alt=""
104
+ loading="lazy"
105
+ className="h-full w-full object-cover"
106
+ />
107
+ ) : (
108
+ <div className="flex h-full items-center justify-center text-muted-foreground">
109
+
110
+ </div>
111
+ )}
112
+ </div>
113
+ <CardContent className="p-3">
114
+ <p className="truncate text-sm font-medium">
115
+ {String(row[spec.titleField] ?? "—")}
116
+ </p>
117
+ {spec.meta.length > 0 ? (
118
+ <div className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
119
+ {spec.meta.map((c) => (
120
+ <span key={c.name} className="inline-flex items-center">
121
+ {formatCell(row[c.name], c)}
122
+ </span>
123
+ ))}
124
+ </div>
125
+ ) : null}
126
+ </CardContent>
127
+ </Card>
128
+ );
129
+ return href ? (
130
+ <Link key={i} href={href} className="block">
131
+ {card}
132
+ </Link>
133
+ ) : (
134
+ <div key={i}>{card}</div>
135
+ );
136
+ })}
137
+ </div>
138
+ );
139
+ })();
140
+
141
+ return (
142
+ <div className="px-5 py-6 md:px-8">
143
+ <div className="mb-5 flex items-end justify-between gap-4">
144
+ <div>
145
+ <h1 className="text-xl font-semibold tracking-tight">{title}</h1>
146
+ {description ? (
147
+ <p className="mt-1 text-sm text-muted-foreground">{description}</p>
148
+ ) : null}
149
+ </div>
150
+ {!loading && !error ? (
151
+ <span className="hidden text-xs text-muted-foreground sm:inline">
152
+ {rows.length} item{rows.length === 1 ? "" : "s"}
153
+ </span>
154
+ ) : null}
155
+ </div>
156
+ {body}
157
+ </div>
158
+ );
159
+ }
@@ -0,0 +1,67 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ import { Button } from "@/components/ui/button";
6
+ import {
7
+ Dialog,
8
+ DialogClose,
9
+ DialogContent,
10
+ DialogDescription,
11
+ DialogFooter,
12
+ DialogHeader,
13
+ DialogTitle,
14
+ DialogTrigger,
15
+ } from "@/components/ui/dialog";
16
+
17
+ export interface ConfirmButtonProps {
18
+ label: string;
19
+ onConfirm: () => void;
20
+ disabled?: boolean;
21
+ title?: string;
22
+ description?: string;
23
+ }
24
+
25
+ /**
26
+ * A destructive button that asks for confirmation before firing. Used for
27
+ * DELETE actions so a single click cannot irreversibly destroy a record — the
28
+ * generated detail page wires `onConfirm` to the SDK delete call.
29
+ */
30
+ export function ConfirmButton(props: ConfirmButtonProps) {
31
+ const { label, onConfirm, disabled, title, description } = props;
32
+ const [open, setOpen] = React.useState(false);
33
+ return (
34
+ <Dialog open={open} onOpenChange={setOpen}>
35
+ <DialogTrigger asChild>
36
+ <Button variant="destructive" size="sm" disabled={disabled}>
37
+ {label}
38
+ </Button>
39
+ </DialogTrigger>
40
+ <DialogContent>
41
+ <DialogHeader>
42
+ <DialogTitle>{title ?? "Are you sure?"}</DialogTitle>
43
+ <DialogDescription>
44
+ {description ?? "This action cannot be undone."}
45
+ </DialogDescription>
46
+ </DialogHeader>
47
+ <DialogFooter>
48
+ <DialogClose asChild>
49
+ <Button variant="outline" size="sm">
50
+ Cancel
51
+ </Button>
52
+ </DialogClose>
53
+ <Button
54
+ variant="destructive"
55
+ size="sm"
56
+ onClick={() => {
57
+ setOpen(false);
58
+ onConfirm();
59
+ }}
60
+ >
61
+ {label}
62
+ </Button>
63
+ </DialogFooter>
64
+ </DialogContent>
65
+ </Dialog>
66
+ );
67
+ }
@@ -0,0 +1,144 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import * as React from "react";
5
+
6
+ import {
7
+ Card,
8
+ CardContent,
9
+ CardHeader,
10
+ CardTitle,
11
+ } from "@/components/ui/card";
12
+ import { Skeleton } from "@/components/ui/skeleton";
13
+ import {
14
+ Table,
15
+ TableBody,
16
+ TableCell,
17
+ TableHead,
18
+ TableHeader,
19
+ TableRow,
20
+ } from "@/components/ui/table";
21
+ import { humanizeLabel } from "@/lib/utils";
22
+
23
+ import { formatCell } from "./formatValue";
24
+ import type { ColumnSpec } from "./types";
25
+
26
+ /** Rows shown inline; the full set lives on the child's own list screen. */
27
+ const PREVIEW_LIMIT = 5;
28
+ /** Columns shown inline — a detail embed stays compact, not the full table. */
29
+ const PREVIEW_COLUMNS = 5;
30
+
31
+ export interface EmbeddedCollectionProps {
32
+ title: string;
33
+ columns: ColumnSpec[];
34
+ /** Fetch + extract the child rows. Supplied by the generated detail page. */
35
+ load: () => Promise<readonly unknown[]>;
36
+ /** Link to the child's full list screen ("View all"). */
37
+ href?: string;
38
+ }
39
+
40
+ /**
41
+ * A child collection rendered inline inside a parent's detail page — the master
42
+ * half of a master-detail view. Fetches its own rows, shows a compact preview
43
+ * table, and links to the full list screen. This is how a nested resource reads
44
+ * as part of its parent (an order's items, a folder's files) instead of a
45
+ * separate page you have to navigate to.
46
+ */
47
+ export function EmbeddedCollection(props: EmbeddedCollectionProps) {
48
+ const { title, columns, load, href } = props;
49
+ const [rows, setRows] = React.useState<readonly unknown[]>([]);
50
+ const [loading, setLoading] = React.useState(true);
51
+ const [error, setError] = React.useState<string | null>(null);
52
+
53
+ React.useEffect(() => {
54
+ let alive = true;
55
+ setLoading(true);
56
+ setError(null);
57
+ load()
58
+ .then((r) => {
59
+ if (alive) setRows(Array.isArray(r) ? r : []);
60
+ })
61
+ .catch((e) => {
62
+ if (alive) setError(e instanceof Error ? e.message : String(e));
63
+ })
64
+ .finally(() => {
65
+ if (alive) setLoading(false);
66
+ });
67
+ return () => {
68
+ alive = false;
69
+ };
70
+ }, []);
71
+
72
+ const cols = columns.slice(0, PREVIEW_COLUMNS);
73
+ const preview = rows.slice(0, PREVIEW_LIMIT);
74
+
75
+ return (
76
+ <Card>
77
+ <CardHeader className="flex flex-row items-center justify-between gap-3 space-y-0 py-3">
78
+ <CardTitle className="text-sm font-semibold">
79
+ {title}
80
+ {!loading && !error ? (
81
+ <span className="ml-2 text-xs font-normal text-muted-foreground">
82
+ {rows.length}
83
+ {rows.length >= PREVIEW_LIMIT ? "+" : ""}
84
+ </span>
85
+ ) : null}
86
+ </CardTitle>
87
+ {href ? (
88
+ <Link
89
+ href={href}
90
+ className="shrink-0 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground"
91
+ >
92
+ View all →
93
+ </Link>
94
+ ) : null}
95
+ </CardHeader>
96
+ <CardContent className="p-0">
97
+ {error ? (
98
+ <p className="px-5 py-4 text-sm text-destructive">{error}</p>
99
+ ) : loading ? (
100
+ <div className="space-y-2 px-5 py-4">
101
+ {Array.from({ length: 3 }).map((_, i) => (
102
+ <Skeleton key={i} className="h-6 w-full" />
103
+ ))}
104
+ </div>
105
+ ) : preview.length === 0 ? (
106
+ <p className="px-5 py-4 text-sm text-muted-foreground">
107
+ No {title.toLowerCase()} yet.
108
+ </p>
109
+ ) : (
110
+ <div className="overflow-x-auto border-t">
111
+ <Table>
112
+ <TableHeader>
113
+ <TableRow className="hover:bg-transparent">
114
+ {cols.map((c) => (
115
+ <TableHead
116
+ key={c.name}
117
+ className="h-9 whitespace-nowrap text-xs uppercase tracking-wide text-muted-foreground"
118
+ >
119
+ {humanizeLabel(c.name)}
120
+ </TableHead>
121
+ ))}
122
+ </TableRow>
123
+ </TableHeader>
124
+ <TableBody>
125
+ {preview.map((raw, i) => {
126
+ const row = (raw ?? {}) as Record<string, unknown>;
127
+ return (
128
+ <TableRow key={i}>
129
+ {cols.map((c) => (
130
+ <TableCell key={c.name} className="max-w-xs align-top text-sm">
131
+ {formatCell(row[c.name], c)}
132
+ </TableCell>
133
+ ))}
134
+ </TableRow>
135
+ );
136
+ })}
137
+ </TableBody>
138
+ </Table>
139
+ </div>
140
+ )}
141
+ </CardContent>
142
+ </Card>
143
+ );
144
+ }
@@ -0,0 +1,104 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import * as React from "react";
5
+
6
+ import { Card } from "@/components/ui/card";
7
+ import { ResourceIcon } from "@/components/auto/ResourceIcon";
8
+ import { ResourceLinks, type LandingLink } from "@/components/auto/ResourceLanding";
9
+
10
+ export interface DashboardStat {
11
+ href: string;
12
+ title: string;
13
+ /** Live record count for the resource; `null` while loading or on error. */
14
+ count: number | null;
15
+ /** Titles of the first few records, for an at-a-glance "recent" preview. */
16
+ recent?: string[];
17
+ }
18
+
19
+ export interface ResourceDashboardProps {
20
+ title: string;
21
+ subtitle?: string;
22
+ /** Top hub resources surfaced as count cards. Empty → just the link grid. */
23
+ stats: DashboardStat[];
24
+ links: LandingLink[];
25
+ }
26
+
27
+ /**
28
+ * Home dashboard. A strip of live count cards for the key hub resources sits
29
+ * above the full grouped resource-link grid. Turns the bare link hub into an
30
+ * actual operator landing — "how much is in each resource" at a glance — while
31
+ * keeping navigation to every resource. Counts are fetched by the generated
32
+ * page (one list call per stat); this component only renders.
33
+ */
34
+ export function ResourceDashboard(props: ResourceDashboardProps) {
35
+ const { title, subtitle, stats, links } = props;
36
+ // Largest count, so each card's bar reads as magnitude relative to the others.
37
+ const max = Math.max(1, ...stats.map((s) => s.count ?? 0));
38
+ return (
39
+ <div className="px-5 py-8 md:px-8">
40
+ <div className="mb-7">
41
+ <h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
42
+ {subtitle ? (
43
+ <p className="mt-1.5 text-sm text-muted-foreground">{subtitle}</p>
44
+ ) : null}
45
+ </div>
46
+
47
+ {stats.length > 0 ? (
48
+ <div className="mb-9 grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
49
+ {stats.map((stat) => (
50
+ <Link key={stat.href} href={stat.href} className="group">
51
+ <Card className="flex h-full flex-col p-4 transition-colors group-hover:border-primary/50 group-hover:bg-muted/30">
52
+ <div className="flex items-center justify-between gap-2">
53
+ <div className="flex min-w-0 items-center gap-2">
54
+ <ResourceIcon
55
+ name={stat.href}
56
+ className="h-4 w-4 shrink-0 text-muted-foreground"
57
+ />
58
+ <p className="truncate text-xs font-medium uppercase tracking-wider text-muted-foreground">
59
+ {stat.title}
60
+ </p>
61
+ </div>
62
+ <span className="shrink-0 text-muted-foreground opacity-0 transition group-hover:opacity-100">
63
+
64
+ </span>
65
+ </div>
66
+ <p className="mt-2 text-3xl font-semibold tabular-nums">
67
+ {stat.count === null ? (
68
+ <span className="text-muted-foreground">—</span>
69
+ ) : (
70
+ stat.count.toLocaleString()
71
+ )}
72
+ </p>
73
+ {stat.count !== null ? (
74
+ <div className="mt-2 h-1 w-full overflow-hidden rounded-full bg-muted">
75
+ <div
76
+ className="h-full rounded-full bg-primary/60"
77
+ style={{
78
+ width: `${Math.max(4, Math.round((stat.count / max) * 100))}%`,
79
+ }}
80
+ />
81
+ </div>
82
+ ) : null}
83
+ {stat.recent && stat.recent.length > 0 ? (
84
+ <ul className="mt-3 space-y-1 border-t pt-2">
85
+ {stat.recent.slice(0, 2).map((r, i) => (
86
+ <li
87
+ key={i}
88
+ className="truncate text-xs text-muted-foreground"
89
+ >
90
+ {r}
91
+ </li>
92
+ ))}
93
+ </ul>
94
+ ) : null}
95
+ </Card>
96
+ </Link>
97
+ ))}
98
+ </div>
99
+ ) : null}
100
+
101
+ <ResourceLinks links={links} />
102
+ </div>
103
+ );
104
+ }
@@ -0,0 +1,93 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ import { Button } from "@/components/ui/button";
6
+ import {
7
+ Card,
8
+ CardContent,
9
+ CardDescription,
10
+ CardHeader,
11
+ CardTitle,
12
+ } from "@/components/ui/card";
13
+ import { Skeleton } from "@/components/ui/skeleton";
14
+ import { humanizeLabel } from "@/lib/utils";
15
+
16
+ import { formatCell } from "./formatValue";
17
+ import type { ColumnSpec } from "./types";
18
+
19
+ export interface ResourceDetailProps {
20
+ title: string;
21
+ /** One row per field of the response type — the whole record. */
22
+ fields: ColumnSpec[];
23
+ /** The single record the detail endpoint returned. `unknown` so the page never casts. */
24
+ data: unknown;
25
+ loading: boolean;
26
+ error?: string | null;
27
+ onRetry?: () => void;
28
+ /** Buttons rendered next to the title (edit link, delete, …). */
29
+ actions?: React.ReactNode;
30
+ }
31
+
32
+ /**
33
+ * Universal detail screen. Renders a definition list — one row per field of the
34
+ * response type, formatted by kind. Schema-driven, so every property of the
35
+ * record is shown, never an LLM-chosen subset.
36
+ */
37
+ export function ResourceDetail(props: ResourceDetailProps) {
38
+ const { title, fields, data, loading, error, onRetry, actions } = props;
39
+
40
+ const body = (() => {
41
+ if (error !== null && error !== undefined) {
42
+ return (
43
+ <Card className="border-destructive/40 bg-destructive/5">
44
+ <CardHeader>
45
+ <CardTitle className="text-base text-destructive">Couldn’t load</CardTitle>
46
+ <CardDescription className="break-words">{error}</CardDescription>
47
+ </CardHeader>
48
+ {onRetry ? (
49
+ <CardContent>
50
+ <Button variant="outline" size="sm" onClick={onRetry}>
51
+ Retry
52
+ </Button>
53
+ </CardContent>
54
+ ) : null}
55
+ </Card>
56
+ );
57
+ }
58
+ if (loading || data === null || data === undefined) {
59
+ return (
60
+ <div className="space-y-3">
61
+ {Array.from({ length: 6 }).map((_, i) => (
62
+ <Skeleton key={i} className="h-8 w-full" />
63
+ ))}
64
+ </div>
65
+ );
66
+ }
67
+ const record = data as Record<string, unknown>;
68
+ return (
69
+ <Card>
70
+ <CardContent className="divide-y p-0">
71
+ {fields.map((f) => (
72
+ <div key={f.name} className="grid grid-cols-3 gap-4 px-5 py-3">
73
+ <dt className="text-sm font-medium text-muted-foreground">{humanizeLabel(f.name)}</dt>
74
+ <dd className="col-span-2 break-words text-sm">
75
+ {formatCell(record[f.name], f)}
76
+ </dd>
77
+ </div>
78
+ ))}
79
+ </CardContent>
80
+ </Card>
81
+ );
82
+ })();
83
+
84
+ return (
85
+ <div className="px-5 py-6 md:px-8">
86
+ <div className="mb-5 flex max-w-3xl flex-wrap items-center justify-between gap-3">
87
+ <h1 className="text-xl font-semibold tracking-tight">{title}</h1>
88
+ <div className="flex flex-wrap items-center gap-2">{actions}</div>
89
+ </div>
90
+ <div className="max-w-3xl">{body}</div>
91
+ </div>
92
+ );
93
+ }