@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,468 @@
1
+ import { OpenApiConverter } from "@typia/utils";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import { toEndpoints } from "../../utils/toEndpoints";
5
+ import { IAutoViewProductPlan } from "../structures/IAutoViewProductPlan";
6
+ import { buildDeterministicPlan } from "./buildDeterministicPlan";
7
+ import { renderResourcePage } from "./renderResourcePage";
8
+
9
+ function doc(paths: Record<string, unknown>, schemas: Record<string, unknown> = {}) {
10
+ return OpenApiConverter.upgradeDocument({
11
+ openapi: "3.0.0",
12
+ info: { title: "t", version: "1.0.0" },
13
+ paths,
14
+ components: { schemas },
15
+ } as never);
16
+ }
17
+
18
+ const ok = { 200: { description: "ok" } };
19
+ const fixture = doc(
20
+ {
21
+ "/pets": {
22
+ get: {
23
+ operationId: "index",
24
+ responses: {
25
+ 200: {
26
+ description: "ok",
27
+ content: {
28
+ "application/json": {
29
+ schema: { type: "array", items: { $ref: "#/components/schemas/Pet" } },
30
+ },
31
+ },
32
+ },
33
+ },
34
+ },
35
+ post: {
36
+ operationId: "create",
37
+ requestBody: {
38
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Pet" } } },
39
+ },
40
+ responses: ok,
41
+ },
42
+ },
43
+ "/pets/{petId}": {
44
+ get: {
45
+ operationId: "at",
46
+ parameters: [{ name: "petId", in: "path", required: true, schema: { type: "string" } }],
47
+ responses: {
48
+ 200: {
49
+ description: "ok",
50
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Pet" } } },
51
+ },
52
+ },
53
+ },
54
+ },
55
+ },
56
+ {
57
+ Pet: {
58
+ type: "object",
59
+ properties: {
60
+ id: { type: "string", format: "uuid" },
61
+ name: { type: "string" },
62
+ status: { type: "string", enum: ["available", "pending", "sold"] },
63
+ },
64
+ required: ["id", "name"],
65
+ },
66
+ },
67
+ );
68
+
69
+ const endpoints = toEndpoints(fixture);
70
+ const plan = buildDeterministicPlan(fixture, "user");
71
+ const screenAt = (path: string): IAutoViewProductPlan.IScreen =>
72
+ plan.screens.find((s) => s.path === path)!;
73
+ const render = (path: string) =>
74
+ renderResourcePage(screenAt(path), endpoints, fixture, plan.screens);
75
+
76
+ describe("renderResourcePage", () => {
77
+ it("table: a resource-named wrapper with INLINE items renders element columns", () => {
78
+ // DigitalOcean shape: `{ droplets: [ { id, name } ] }` — the array is named
79
+ // after the resource (not `data`) and its element is inline (no $ref). The
80
+ // table must show id/name columns and read rows off `res.droplets`, not show
81
+ // a single `droplets` cell.
82
+ const d = doc(
83
+ {
84
+ "/droplets": {
85
+ get: {
86
+ operationId: "index",
87
+ responses: { 200: { description: "ok", content: { "application/json": { schema: { type: "object", properties: { droplets: { type: "array", items: { type: "object", properties: { id: { type: "integer" }, name: { type: "string" } } } } } } } } } },
88
+ },
89
+ },
90
+ },
91
+ {},
92
+ );
93
+ const eps = toEndpoints(d);
94
+ const plan = buildDeterministicPlan(d, "user");
95
+ const screen = plan.screens.find((s) => s.path === "/droplets")!;
96
+ expect(screen.uiPattern).toBe("table"); // not downgraded to a detail
97
+ const tsx = renderResourcePage(screen, eps, d, plan.screens);
98
+ expect(tsx).toContain("ResourceTable");
99
+ expect(tsx).toContain('"name":"id"');
100
+ expect(tsx).toContain('"name":"name"');
101
+ expect(tsx).toContain("const data = (res.droplets ?? []) as readonly unknown[];");
102
+ });
103
+
104
+ it("table: an inline collection key that is not a JS identifier uses bracket access", () => {
105
+ // DigitalOcean's `GET /v2/1-clicks` returns `{ "1-clicks": [...] }`. The key
106
+ // starts with a digit, so `res.1-clicks` is a hard syntax error — the reader
107
+ // must fall back to bracket access `res["1-clicks"]`.
108
+ const d = doc(
109
+ {
110
+ "/1-clicks": {
111
+ get: {
112
+ operationId: "list",
113
+ responses: { 200: { description: "ok", content: { "application/json": { schema: { type: "object", properties: { "1-clicks": { type: "array", items: { type: "object", properties: { kind: { type: "string" } } } } } } } } } },
114
+ },
115
+ },
116
+ },
117
+ {},
118
+ );
119
+ const eps = toEndpoints(d);
120
+ const plan = buildDeterministicPlan(d, "user");
121
+ const screen = plan.screens.find((s) => s.uiPattern === "table")!;
122
+ const tsx = renderResourcePage(screen, eps, d, plan.screens);
123
+ expect(tsx).toContain('const data = (res["1-clicks"] ?? []) as readonly unknown[];');
124
+ expect(tsx).not.toContain("res.1-clicks");
125
+ });
126
+
127
+ it("table: one column per row-type field, array response read directly", () => {
128
+ const tsx = render("/pets");
129
+ expect(tsx).toContain('"use client"');
130
+ expect(tsx).toContain("ResourceTable");
131
+ // every Pet property becomes a column — dense by construction
132
+ expect(tsx).toContain('"name":"id"');
133
+ expect(tsx).toContain('"name":"name"');
134
+ expect(tsx).toContain('"name":"status","kind":"enum"');
135
+ // array response → rows read straight off `res`, not `res.data`
136
+ // array response read directly off `res` (guarded against a non-array)
137
+ expect(tsx).toContain("const data = (Array.isArray(res) ? res : []) as readonly unknown[];");
138
+ // a detail screen exists → rows link out
139
+ expect(tsx).toContain("rowHref=");
140
+ });
141
+
142
+ it("detail: sources path params from useParams and passes them typed", () => {
143
+ const tsx = render("/pets/[petsId]");
144
+ expect(tsx).toContain('import { useParams } from "next/navigation"');
145
+ // route segment is the canonical `petsId`; the SDK param stays `petId`,
146
+ // sourced by position from the route.
147
+ expect(tsx).toContain('const petId = String((params as Record<string, unknown>).petsId ?? "")');
148
+ expect(tsx).toContain("api.functional.pets.getByPetid"); // SDK accessor from path
149
+ expect(tsx).toContain("{ petId: petId }"); // path param passed at top level
150
+ expect(tsx).toContain("ResourceDetail");
151
+ expect(tsx).toContain('"name":"status"');
152
+ });
153
+
154
+ it("form: inputs carry required flag and body is cast to the SDK param type", () => {
155
+ const tsx = render("/pets/new");
156
+ expect(tsx).toContain("ResourceForm");
157
+ expect(tsx).toContain("FieldInput");
158
+ expect(tsx).toContain('"name":"id","kind":"string","format":"uuid","required":true');
159
+ expect(tsx).toContain("body: values");
160
+ expect(tsx).toContain("as Parameters<typeof api.functional.");
161
+ });
162
+
163
+ it("landing: links to every table screen", () => {
164
+ const tsx = render("/");
165
+ expect(tsx).toContain("ResourceLanding");
166
+ expect(tsx).toContain('"href":"/pets"');
167
+ });
168
+
169
+ it("landing: weights each link by hub-ness (distinct nested child resources)", () => {
170
+ // `files` parents two child resources (metadata, versions) → weight 2;
171
+ // `tags` is only ever read on its own → weight 0.
172
+ const hub = doc(
173
+ {
174
+ "/files": { get: { operationId: "list", responses: { 200: { description: "ok", content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/F" } } } } } } } },
175
+ "/files/{fileId}": { get: { operationId: "at", parameters: [{ name: "fileId", in: "path", required: true, schema: { type: "string" } }], responses: { 200: { description: "ok", content: { "application/json": { schema: { $ref: "#/components/schemas/F" } } } } } } },
176
+ "/files/{fileId}/metadata": { get: { operationId: "meta", parameters: [{ name: "fileId", in: "path", required: true, schema: { type: "string" } }], responses: { 200: { description: "ok", content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/F" } } } } } } } },
177
+ "/files/{fileId}/versions": { get: { operationId: "vers", parameters: [{ name: "fileId", in: "path", required: true, schema: { type: "string" } }], responses: { 200: { description: "ok", content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/F" } } } } } } } },
178
+ "/tags": { get: { operationId: "list", responses: { 200: { description: "ok", content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/F" } } } } } } } },
179
+ },
180
+ { F: { type: "object", properties: { id: { type: "string" } }, required: ["id"] } },
181
+ );
182
+ const eps = toEndpoints(hub);
183
+ const plan = buildDeterministicPlan(hub, "user");
184
+ const tsx = renderResourcePage(plan.screens.find((s) => s.path === "/")!, eps, hub, plan.screens);
185
+ // files parents metadata + versions → weight 2; tags → weight 0
186
+ expect(tsx).toMatch(/"href":"\/files"[^}]*"weight":2/);
187
+ expect(tsx).toMatch(/"href":"\/tags"[^}]*"weight":0/);
188
+ });
189
+
190
+ it("catalog: an image+title row schema renders a CatalogGrid, not a table", () => {
191
+ // The catalog classifier is purely structural: rows carrying an image field
192
+ // and a title field read as a gallery (products, photos, profiles). The spec
193
+ // must bake the DETECTED field names so the grid reads the right columns.
194
+ const d = doc(
195
+ {
196
+ "/products": {
197
+ get: { operationId: "list", responses: { 200: { description: "ok", content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/Product" } } } } } } },
198
+ },
199
+ "/products/{id}": {
200
+ get: { operationId: "at", parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], responses: { 200: { description: "ok", content: { "application/json": { schema: { $ref: "#/components/schemas/Product" } } } } } },
201
+ },
202
+ },
203
+ {
204
+ Product: { type: "object", properties: { id: { type: "string" }, name: { type: "string" }, image_url: { type: "string" }, price: { type: "number" } }, required: ["id", "name"] },
205
+ },
206
+ );
207
+ const eps = toEndpoints(d);
208
+ const p = buildDeterministicPlan(d, "user");
209
+ const screen = p.screens.find((s) => s.path === "/products")!;
210
+ expect(screen.uiPattern).toBe("catalog");
211
+ const tsx = renderResourcePage(screen, eps, d, p.screens);
212
+ expect(tsx).toContain("CatalogGrid");
213
+ expect(tsx).toContain('"imageField":"image_url"');
214
+ expect(tsx).toContain('"titleField":"name"');
215
+ // cards link through to the detail screen
216
+ expect(tsx).toContain("rowHref=");
217
+ });
218
+
219
+ it("dashboard: the home landing fetches live counts and a recent preview per hub resource", () => {
220
+ const tsx = render("/");
221
+ expect(tsx).toContain("ResourceDashboard");
222
+ // live count card wiring: each stat screen's list op is called and reduced
223
+ // to a count + a recent-title preview, failure degrading to a null count.
224
+ expect(tsx).toContain("{ count: n, recent }");
225
+ expect(tsx).toContain("count: null");
226
+ });
227
+
228
+ it("embedded: a detail with a direct child collection embeds it as a master-detail preview", () => {
229
+ const d = doc(
230
+ {
231
+ "/sales/{id}": {
232
+ get: { operationId: "at", parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], responses: { 200: { description: "ok", content: { "application/json": { schema: { $ref: "#/components/schemas/Sale" } } } } } },
233
+ },
234
+ "/sales/{id}/questions": {
235
+ get: { operationId: "questions", parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], responses: { 200: { description: "ok", content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/Question" } } } } } } },
236
+ },
237
+ "/sales/{id}/questions/{questionId}": {
238
+ get: { operationId: "questionAt", parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }, { name: "questionId", in: "path", required: true, schema: { type: "string" } }], responses: { 200: { description: "ok", content: { "application/json": { schema: { $ref: "#/components/schemas/Question" } } } } } },
239
+ },
240
+ },
241
+ {
242
+ Sale: { type: "object", properties: { id: { type: "string" }, total: { type: "number" } }, required: ["id"] },
243
+ Question: { type: "object", properties: { id: { type: "string" }, body: { type: "string" } }, required: ["id"] },
244
+ },
245
+ );
246
+ const eps = toEndpoints(d);
247
+ const p = buildDeterministicPlan(d, "user");
248
+ const detail = p.screens.find((s) => s.uiPattern === "detail" && s.path.startsWith("/sales/["))!;
249
+ const tsx = renderResourcePage(detail, eps, d, p.screens);
250
+ // the child list is fetched on the SAME page and rendered inline below the record
251
+ expect(tsx).toContain("EmbeddedCollection");
252
+ expect(tsx).toContain("questions");
253
+ // the child op's id param is sourced from this page's route segment
254
+ expect(tsx).toMatch(/const id = String\(\(params as Record<string, unknown>\)\./);
255
+ });
256
+
257
+ it("is deterministic — same screen yields identical TSX", () => {
258
+ expect(render("/pets")).toEqual(render("/pets"));
259
+ });
260
+
261
+ it("fills a required query param with a concrete default (not `query: {}`)", () => {
262
+ // /pet/findByStatus needs a `status` query param; sending `query: {}` 400s.
263
+ const qdoc = doc(
264
+ {
265
+ "/pet/findByStatus": {
266
+ get: {
267
+ operationId: "findByStatus",
268
+ parameters: [
269
+ { name: "status", in: "query", required: true, schema: { type: "string", enum: ["available", "pending", "sold"] } },
270
+ ],
271
+ responses: {
272
+ 200: { description: "ok", content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/Pet" } } } } },
273
+ },
274
+ },
275
+ },
276
+ },
277
+ { Pet: { type: "object", properties: { id: { type: "string" } }, required: ["id"] } },
278
+ );
279
+ const qeps = toEndpoints(qdoc);
280
+ const qplan = buildDeterministicPlan(qdoc, "user");
281
+ const screen = qplan.screens.find((s) => s.endpoints.some((e) => e.includes("findByStatus")))!;
282
+ const tsx = renderResourcePage(screen, qeps, qdoc, qplan.screens);
283
+ expect(tsx).toContain('query: { "status": "available" }');
284
+ expect(tsx).not.toContain("query: {}");
285
+ });
286
+
287
+ it("fills a required query param typed as a bare const (not `{}`)", () => {
288
+ // Box's /collaborations exposes `status` as a single-value `{ const: ... }`;
289
+ // it must resolve to that literal, not the empty-object fallback.
290
+ const qdoc = doc(
291
+ {
292
+ "/collaborations": {
293
+ get: {
294
+ operationId: "list",
295
+ parameters: [
296
+ { name: "status", in: "query", required: true, schema: { const: "pending" } },
297
+ ],
298
+ responses: {
299
+ 200: { description: "ok", content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/Pet" } } } } },
300
+ },
301
+ },
302
+ },
303
+ },
304
+ { Pet: { type: "object", properties: { id: { type: "string" } }, required: ["id"] } },
305
+ );
306
+ const qeps = toEndpoints(qdoc);
307
+ const qplan = buildDeterministicPlan(qdoc, "user");
308
+ const screen = qplan.screens.find((s) => s.endpoints.some((e) => e.includes("collaborations")))!;
309
+ const tsx = renderResourcePage(screen, qeps, qdoc, qplan.screens);
310
+ expect(tsx).toContain('query: { "status": "pending" }');
311
+ });
312
+
313
+ it("detail edit link substitutes the id value into the route, not a literal bracket", () => {
314
+ const editable = doc(
315
+ {
316
+ "/orders/{id}": {
317
+ get: {
318
+ operationId: "at",
319
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
320
+ responses: {
321
+ 200: {
322
+ description: "ok",
323
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Order" } } },
324
+ },
325
+ },
326
+ },
327
+ put: {
328
+ operationId: "update",
329
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
330
+ requestBody: {
331
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Order" } } },
332
+ },
333
+ responses: ok,
334
+ },
335
+ },
336
+ },
337
+ { Order: { type: "object", properties: { id: { type: "string" }, total: { type: "number" } } } },
338
+ );
339
+ const eps = toEndpoints(editable);
340
+ const p = buildDeterministicPlan(editable, "user");
341
+ const detail = p.screens.find((s) => s.uiPattern === "detail")!;
342
+ const tsx = renderResourcePage(detail, eps, editable, p.screens);
343
+ // edit link present and points at the substituted value, never a literal `[id]`
344
+ expect(tsx).toContain("/edit");
345
+ expect(tsx).toContain("${id}/edit");
346
+ expect(tsx).not.toMatch(/\[id\][^`]*\/edit/); // no `[id]` left in the href
347
+ });
348
+
349
+ it("nested detail: route with more segments than op params never references an undeclared var", () => {
350
+ // GitHub shape: `GET /projects/columns/{column_id}` is flat (one param) but
351
+ // the plan nests it under `projects`, yielding a route with TWO `[segment]`s.
352
+ // The op param must source from the TRAILING segment, and the edit link /
353
+ // delete redirect must read the ancestor segment from useParams — never emit
354
+ // a bare `columnsId` (undeclared → tsc error) or a literal `[segment]`
355
+ // (Next.js runtime "Dynamic href" 404).
356
+ const nested = doc(
357
+ {
358
+ "/projects/{project_id}/columns": {
359
+ get: { operationId: "listColumns", parameters: [{ name: "project_id", in: "path", required: true, schema: { type: "integer" } }], responses: { 200: { description: "ok", content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/Column" } } } } } } },
360
+ },
361
+ "/projects/columns/{column_id}": {
362
+ get: { operationId: "getColumn", parameters: [{ name: "column_id", in: "path", required: true, schema: { type: "integer" } }], responses: { 200: { description: "ok", content: { "application/json": { schema: { $ref: "#/components/schemas/Column" } } } } } },
363
+ delete: { operationId: "delColumn", parameters: [{ name: "column_id", in: "path", required: true, schema: { type: "integer" } }], responses: { 204: { description: "deleted" } } },
364
+ },
365
+ },
366
+ {
367
+ Column: { type: "object", properties: { id: { type: "integer" }, name: { type: "string" } } },
368
+ },
369
+ );
370
+ const eps = toEndpoints(nested);
371
+ const p = buildDeterministicPlan(nested, "user");
372
+ const screen = p.screens.find(
373
+ (s) => s.uiPattern === "detail" && s.path.includes("columns") && (s.path.match(/\[/g)?.length ?? 0) >= 2,
374
+ );
375
+ expect(screen).toBeDefined(); // the plan did nest columns under projects
376
+ const tsx = renderResourcePage(screen!, eps, nested, p.screens);
377
+ // op param sources from the trailing segment, not the ancestor
378
+ expect(tsx).toContain("const column_id = Number((params as Record<string, unknown>).columnsId)");
379
+ // no undeclared bare segment var, no literal bracket left in any nav target
380
+ expect(tsx).not.toMatch(/\$\{columnsId\}/); // would be an undeclared identifier
381
+ expect(tsx).not.toMatch(/(href=|router\.push\()[^`"]*\[[A-Za-z]/); // no literal `[seg]` in nav
382
+ });
383
+
384
+ it("detail renders delete/action endpoints as real SDK-wired buttons", () => {
385
+ const acted = doc(
386
+ {
387
+ "/orders/{id}": {
388
+ get: {
389
+ operationId: "at",
390
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
391
+ responses: {
392
+ 200: { description: "ok", content: { "application/json": { schema: { $ref: "#/components/schemas/O" } } } },
393
+ },
394
+ },
395
+ delete: {
396
+ operationId: "erase",
397
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
398
+ responses: ok,
399
+ },
400
+ },
401
+ "/orders/{id}/cancel": {
402
+ post: {
403
+ operationId: "cancel",
404
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
405
+ responses: ok,
406
+ },
407
+ },
408
+ },
409
+ { O: { type: "object", properties: { id: { type: "string" } } } },
410
+ );
411
+ const eps = toEndpoints(acted);
412
+ const p = buildDeterministicPlan(acted, "user");
413
+ const detail = p.screens.find((s) => s.uiPattern === "detail" && s.path.includes("["))!;
414
+ const tsx = renderResourcePage(detail, eps, acted, p.screens);
415
+ // a confirmed Delete button wired to the erase accessor (delete asks first,
416
+ // then navigates to the list on success)
417
+ expect(tsx).toContain('<ConfirmButton label="Delete"');
418
+ expect(tsx).toMatch(/onConfirm=\{\(\) => del\(\(\) => api\.functional\..*erase/i);
419
+ // the POST action surfaces as a button labeled from the path verb ("cancel")
420
+ expect(tsx).toContain(">Cancel</Button>");
421
+ expect(tsx).toContain("api.functional.orders.cancel.postById(connection");
422
+ });
423
+
424
+ it("nested table whose route lacks the parent params renders static (no fetch)", () => {
425
+ const nestedDoc = doc(
426
+ {
427
+ "/owners/{ownerId}/pets": {
428
+ get: {
429
+ operationId: "index",
430
+ parameters: [
431
+ { name: "ownerId", in: "path", required: true, schema: { type: "string" } },
432
+ ],
433
+ responses: {
434
+ 200: {
435
+ description: "ok",
436
+ content: {
437
+ "application/json": {
438
+ schema: { type: "array", items: { $ref: "#/components/schemas/Pet" } },
439
+ },
440
+ },
441
+ },
442
+ },
443
+ },
444
+ },
445
+ },
446
+ { Pet: { type: "object", properties: { id: { type: "string" }, name: { type: "string" } } } },
447
+ );
448
+ const eps = toEndpoints(nestedDoc);
449
+ // A table screen at a bracket-less path pointing at the nested list endpoint.
450
+ const nestedScreen: IAutoViewProductPlan.IScreen = {
451
+ path: "/pets",
452
+ title: "Pets",
453
+ purpose: "Browse pets.",
454
+ actor: "user",
455
+ endpoints: [eps[0]!.accessor.join(".")],
456
+ uiPattern: "table",
457
+ notes: "",
458
+ };
459
+ const tsx = renderResourcePage(nestedScreen, eps, nestedDoc, [nestedScreen]);
460
+ // dense headers still present, but no doomed fetch fires
461
+ expect(tsx).toContain('"name":"id"');
462
+ expect(tsx).toContain('"name":"name"');
463
+ expect(tsx).not.toContain("useEffect");
464
+ expect(tsx).toContain("rows={[]}");
465
+ expect(tsx).toContain("emptyHint=");
466
+ expect(tsx).toContain("ownerId");
467
+ });
468
+ });