@specverse/engine-realize 3.5.3

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 (420) hide show
  1. package/assets/examples/09-api/ai-spec.yaml +194 -0
  2. package/assets/examples/09-api/converted.yaml +95 -0
  3. package/assets/examples/09-api/diagram-architecture.mmd +10 -0
  4. package/assets/examples/09-api/diagram-er.mmd +10 -0
  5. package/assets/examples/09-api/documentation.html +104 -0
  6. package/assets/examples/09-api/documentation.md +95 -0
  7. package/assets/examples/09-api/inferred-spec.yaml +420 -0
  8. package/assets/examples/09-api/openapi.json +61 -0
  9. package/assets/examples/10-api/README.md +216 -0
  10. package/assets/examples/10-api/ai-spec.yaml +194 -0
  11. package/assets/examples/10-api/converted.yaml +96 -0
  12. package/assets/examples/10-api/diagram-architecture.mmd +10 -0
  13. package/assets/examples/10-api/diagram-er.mmd +10 -0
  14. package/assets/examples/10-api/documentation.html +104 -0
  15. package/assets/examples/10-api/documentation.md +95 -0
  16. package/assets/examples/10-api/inferred-spec.yaml +7 -0
  17. package/assets/examples/10-api/metadata.yaml +89 -0
  18. package/assets/examples/10-api/openapi.json +61 -0
  19. package/assets/examples/10-api/package-integration-test.js +177 -0
  20. package/assets/examples/10-api/usage-example.js +323 -0
  21. package/assets/examples/10-api/usage-example.ts +363 -0
  22. package/assets/examples/10-api/workflow-test.js +113 -0
  23. package/assets/examples/manifests/01-simple-default-mappings.yaml +36 -0
  24. package/assets/examples/manifests/02-capability-mappings.yaml +55 -0
  25. package/assets/examples/manifests/03-hybrid-mappings.yaml +109 -0
  26. package/assets/examples/manifests/README.md +245 -0
  27. package/assets/examples/manifests/backend-only.yaml +43 -0
  28. package/assets/examples/manifests/blog-api.md +78 -0
  29. package/assets/examples/manifests/blog-api.specly +79 -0
  30. package/assets/examples/manifests/frontend-only.yaml +27 -0
  31. package/assets/examples/manifests/fullstack-app.yaml +44 -0
  32. package/assets/examples/manifests/fullstack-monorepo.yaml +62 -0
  33. package/assets/examples/validate-examples-with-expected-failures.cjs +328 -0
  34. package/assets/examples/validate-examples.cjs +225 -0
  35. package/assets/examples-decomposed/cloud-native-manifest.example.yaml +8 -0
  36. package/assets/examples-decomposed/cloud-native-manifest.md +379 -0
  37. package/assets/examples-decomposed/cloud-native-manifest.specly +60 -0
  38. package/assets/examples-decomposed/docker-compose-manifest.example.yaml +8 -0
  39. package/assets/examples-decomposed/docker-compose-manifest.md +326 -0
  40. package/assets/examples-decomposed/docker-compose-manifest.specly +40 -0
  41. package/assets/examples-decomposed/kubernetes-deployment-manifest.example.yaml +8 -0
  42. package/assets/examples-decomposed/kubernetes-deployment-manifest.md +237 -0
  43. package/assets/examples-decomposed/kubernetes-deployment-manifest.specly +41 -0
  44. package/assets/templates/README.md +559 -0
  45. package/assets/templates/TEMPLATE-ENHANCEMENTS-V33.md +462 -0
  46. package/assets/templates/backend-only/CLAUDE.md +73 -0
  47. package/assets/templates/backend-only/README.md +197 -0
  48. package/assets/templates/backend-only/deployments/README.md +149 -0
  49. package/assets/templates/backend-only/deployments/development.specly +53 -0
  50. package/assets/templates/backend-only/deployments/production.specly +87 -0
  51. package/assets/templates/backend-only/docs/README.md +50 -0
  52. package/assets/templates/backend-only/docs/api/README.md +7 -0
  53. package/assets/templates/backend-only/docs/diagrams/README.md +85 -0
  54. package/assets/templates/backend-only/docs/example-documentation-template.md +269 -0
  55. package/assets/templates/backend-only/docs/guides/README.md +15 -0
  56. package/assets/templates/backend-only/dot.env.example +18 -0
  57. package/assets/templates/backend-only/generated/README.md +56 -0
  58. package/assets/templates/backend-only/generated/code/integration-test.template.js +320 -0
  59. package/assets/templates/backend-only/generated/code/package.json.template +34 -0
  60. package/assets/templates/backend-only/generated/docs/README.md +49 -0
  61. package/assets/templates/backend-only/gitignore +54 -0
  62. package/assets/templates/backend-only/manifests/README.md +72 -0
  63. package/assets/templates/backend-only/manifests/docker-compose.specly +91 -0
  64. package/assets/templates/backend-only/manifests/implementation.yaml +100 -0
  65. package/assets/templates/backend-only/manifests/kubernetes.specly +140 -0
  66. package/assets/templates/backend-only/package.json +59 -0
  67. package/assets/templates/backend-only/scripts/test-all.sh +160 -0
  68. package/assets/templates/backend-only/scripts/test-generated-code.sh +165 -0
  69. package/assets/templates/backend-only/specs/main.specly +67 -0
  70. package/assets/templates/default/CLAUDE.md +141 -0
  71. package/assets/templates/default/README.md +404 -0
  72. package/assets/templates/default/deployments/README.md +149 -0
  73. package/assets/templates/default/deployments/development.specly +53 -0
  74. package/assets/templates/default/deployments/production.specly +87 -0
  75. package/assets/templates/default/docs/README.md +50 -0
  76. package/assets/templates/default/docs/api/README.md +7 -0
  77. package/assets/templates/default/docs/diagrams/README.md +85 -0
  78. package/assets/templates/default/docs/example-documentation-template.md +269 -0
  79. package/assets/templates/default/docs/guides/README.md +15 -0
  80. package/assets/templates/default/dot.env.example +18 -0
  81. package/assets/templates/default/generated/README.md +56 -0
  82. package/assets/templates/default/generated/code/integration-test.template.js +320 -0
  83. package/assets/templates/default/generated/code/package.json.template +34 -0
  84. package/assets/templates/default/generated/docs/README.md +49 -0
  85. package/assets/templates/default/gitignore +54 -0
  86. package/assets/templates/default/manifests/README.md +72 -0
  87. package/assets/templates/default/manifests/docker-compose.specly +91 -0
  88. package/assets/templates/default/manifests/implementation.yaml +176 -0
  89. package/assets/templates/default/manifests/kubernetes.specly +140 -0
  90. package/assets/templates/default/package.json +61 -0
  91. package/assets/templates/default/scripts/test-all.sh +160 -0
  92. package/assets/templates/default/scripts/test-generated-code.sh +165 -0
  93. package/assets/templates/default/specs/main.specly +67 -0
  94. package/assets/templates/frontend-only/CLAUDE.md +75 -0
  95. package/assets/templates/frontend-only/README.md +231 -0
  96. package/assets/templates/frontend-only/deployments/README.md +149 -0
  97. package/assets/templates/frontend-only/deployments/development.specly +53 -0
  98. package/assets/templates/frontend-only/deployments/production.specly +87 -0
  99. package/assets/templates/frontend-only/docs/README.md +50 -0
  100. package/assets/templates/frontend-only/docs/api/README.md +7 -0
  101. package/assets/templates/frontend-only/docs/diagrams/README.md +85 -0
  102. package/assets/templates/frontend-only/docs/example-documentation-template.md +269 -0
  103. package/assets/templates/frontend-only/docs/guides/README.md +15 -0
  104. package/assets/templates/frontend-only/dot.env.example +18 -0
  105. package/assets/templates/frontend-only/generated/README.md +56 -0
  106. package/assets/templates/frontend-only/generated/code/integration-test.template.js +320 -0
  107. package/assets/templates/frontend-only/generated/code/package.json.template +34 -0
  108. package/assets/templates/frontend-only/generated/docs/README.md +49 -0
  109. package/assets/templates/frontend-only/gitignore +54 -0
  110. package/assets/templates/frontend-only/manifests/README.md +72 -0
  111. package/assets/templates/frontend-only/manifests/docker-compose.specly +91 -0
  112. package/assets/templates/frontend-only/manifests/implementation.yaml +58 -0
  113. package/assets/templates/frontend-only/manifests/kubernetes.specly +140 -0
  114. package/assets/templates/frontend-only/package.json +59 -0
  115. package/assets/templates/frontend-only/scripts/test-all.sh +160 -0
  116. package/assets/templates/frontend-only/scripts/test-generated-code.sh +165 -0
  117. package/assets/templates/frontend-only/specs/main.specly +57 -0
  118. package/assets/templates/full-stack/AI-GUIDE.md +60 -0
  119. package/assets/templates/full-stack/CLAUDE.md +141 -0
  120. package/assets/templates/full-stack/README.md +382 -0
  121. package/assets/templates/full-stack/archive/AI-GUIDE-legacy.md +392 -0
  122. package/assets/templates/full-stack/deployments/README.md +149 -0
  123. package/assets/templates/full-stack/deployments/development.specly +53 -0
  124. package/assets/templates/full-stack/deployments/production.specly +87 -0
  125. package/assets/templates/full-stack/docs/README.md +51 -0
  126. package/assets/templates/full-stack/docs/api/README.md +7 -0
  127. package/assets/templates/full-stack/docs/diagrams/README.md +85 -0
  128. package/assets/templates/full-stack/docs/example-documentation-template.md +269 -0
  129. package/assets/templates/full-stack/docs/guides/README.md +15 -0
  130. package/assets/templates/full-stack/generated/README.md +56 -0
  131. package/assets/templates/full-stack/generated/code/integration-test.template.js +320 -0
  132. package/assets/templates/full-stack/generated/code/package.json.template +34 -0
  133. package/assets/templates/full-stack/generated/docs/README.md +49 -0
  134. package/assets/templates/full-stack/gitignore +54 -0
  135. package/assets/templates/full-stack/manifests/README.md +72 -0
  136. package/assets/templates/full-stack/manifests/docker-compose.specly +91 -0
  137. package/assets/templates/full-stack/manifests/implementation.yaml +155 -0
  138. package/assets/templates/full-stack/manifests/kubernetes.specly +140 -0
  139. package/assets/templates/full-stack/package.json +45 -0
  140. package/assets/templates/full-stack/scripts/test-all.sh +160 -0
  141. package/assets/templates/full-stack/scripts/test-generated-code.sh +165 -0
  142. package/assets/templates/full-stack/specs/example-v33.specly +297 -0
  143. package/assets/templates/full-stack/specs/main-simple.specly +73 -0
  144. package/assets/templates/full-stack/specs/main.specly +408 -0
  145. package/dist/engines/code-generator.d.ts +86 -0
  146. package/dist/engines/code-generator.d.ts.map +1 -0
  147. package/dist/engines/code-generator.js +159 -0
  148. package/dist/engines/code-generator.js.map +1 -0
  149. package/dist/engines/engine-registry.d.ts +94 -0
  150. package/dist/engines/engine-registry.d.ts.map +1 -0
  151. package/dist/engines/engine-registry.js +163 -0
  152. package/dist/engines/engine-registry.js.map +1 -0
  153. package/dist/engines/index.d.ts +10 -0
  154. package/dist/engines/index.d.ts.map +1 -0
  155. package/dist/engines/index.js +12 -0
  156. package/dist/engines/index.js.map +1 -0
  157. package/dist/engines/typescript-engine.d.ts +74 -0
  158. package/dist/engines/typescript-engine.d.ts.map +1 -0
  159. package/dist/engines/typescript-engine.js +288 -0
  160. package/dist/engines/typescript-engine.js.map +1 -0
  161. package/dist/generators/index.d.ts +11 -0
  162. package/dist/generators/index.d.ts.map +1 -0
  163. package/dist/generators/index.js +11 -0
  164. package/dist/generators/index.js.map +1 -0
  165. package/dist/index.d.ts +48 -0
  166. package/dist/index.d.ts.map +1 -0
  167. package/dist/index.js +434 -0
  168. package/dist/index.js.map +1 -0
  169. package/dist/library/index.d.ts +12 -0
  170. package/dist/library/index.d.ts.map +1 -0
  171. package/dist/library/index.js +15 -0
  172. package/dist/library/index.js.map +1 -0
  173. package/dist/library/library.d.ts +132 -0
  174. package/dist/library/library.d.ts.map +1 -0
  175. package/dist/library/library.js +343 -0
  176. package/dist/library/library.js.map +1 -0
  177. package/dist/library/loader.d.ts +73 -0
  178. package/dist/library/loader.d.ts.map +1 -0
  179. package/dist/library/loader.js +150 -0
  180. package/dist/library/loader.js.map +1 -0
  181. package/dist/library/resolver.d.ts +104 -0
  182. package/dist/library/resolver.d.ts.map +1 -0
  183. package/dist/library/resolver.js +299 -0
  184. package/dist/library/resolver.js.map +1 -0
  185. package/dist/library/validator.d.ts +65 -0
  186. package/dist/library/validator.d.ts.map +1 -0
  187. package/dist/library/validator.js +203 -0
  188. package/dist/library/validator.js.map +1 -0
  189. package/dist/types/index.d.ts +7 -0
  190. package/dist/types/index.d.ts.map +1 -0
  191. package/dist/types/index.js +7 -0
  192. package/dist/types/index.js.map +1 -0
  193. package/dist/types/instance-factory.d.ts +289 -0
  194. package/dist/types/instance-factory.d.ts.map +1 -0
  195. package/dist/types/instance-factory.js +8 -0
  196. package/dist/types/instance-factory.js.map +1 -0
  197. package/dist/types/unified-mappings.d.ts +163 -0
  198. package/dist/types/unified-mappings.d.ts.map +1 -0
  199. package/dist/types/unified-mappings.js +110 -0
  200. package/dist/types/unified-mappings.js.map +1 -0
  201. package/dist/utils/ai-spec-loader.d.ts +77 -0
  202. package/dist/utils/ai-spec-loader.d.ts.map +1 -0
  203. package/dist/utils/ai-spec-loader.js +138 -0
  204. package/dist/utils/ai-spec-loader.js.map +1 -0
  205. package/dist/utils/index.d.ts +9 -0
  206. package/dist/utils/index.d.ts.map +1 -0
  207. package/dist/utils/index.js +9 -0
  208. package/dist/utils/index.js.map +1 -0
  209. package/dist/utils/manifest-loader.d.ts +107 -0
  210. package/dist/utils/manifest-loader.d.ts.map +1 -0
  211. package/dist/utils/manifest-loader.js +168 -0
  212. package/dist/utils/manifest-loader.js.map +1 -0
  213. package/dist/utils/mapping-migration.d.ts +53 -0
  214. package/dist/utils/mapping-migration.d.ts.map +1 -0
  215. package/dist/utils/mapping-migration.js +194 -0
  216. package/dist/utils/mapping-migration.js.map +1 -0
  217. package/libs/instance-factories/CURVED-INTERFACE.md +278 -0
  218. package/libs/instance-factories/README.md +433 -0
  219. package/libs/instance-factories/applications/generic-app.yaml +52 -0
  220. package/libs/instance-factories/applications/react-app.yaml +186 -0
  221. package/libs/instance-factories/applications/templates/generic/backend-env-generator.ts +31 -0
  222. package/libs/instance-factories/applications/templates/generic/backend-package-json-generator.ts +80 -0
  223. package/libs/instance-factories/applications/templates/generic/backend-tsconfig-generator.ts +69 -0
  224. package/libs/instance-factories/applications/templates/generic/main-generator.ts +308 -0
  225. package/libs/instance-factories/applications/templates/react/_view-components-source.ts +555 -0
  226. package/libs/instance-factories/applications/templates/react/api-client-generator.ts +436 -0
  227. package/libs/instance-factories/applications/templates/react/api-types-generator.ts +153 -0
  228. package/libs/instance-factories/applications/templates/react/app-tsx-generator.ts +94 -0
  229. package/libs/instance-factories/applications/templates/react/env-example-generator.ts +24 -0
  230. package/libs/instance-factories/applications/templates/react/field-helpers-generator.ts +106 -0
  231. package/libs/instance-factories/applications/templates/react/gitignore-generator.ts +38 -0
  232. package/libs/instance-factories/applications/templates/react/index-css-generator.ts +85 -0
  233. package/libs/instance-factories/applications/templates/react/index-html-generator.ts +30 -0
  234. package/libs/instance-factories/applications/templates/react/main-tsx-generator.ts +34 -0
  235. package/libs/instance-factories/applications/templates/react/package-json-generator.ts +54 -0
  236. package/libs/instance-factories/applications/templates/react/pattern-adapter-generator.ts +179 -0
  237. package/libs/instance-factories/applications/templates/react/react-pattern-adapter.tsx +1347 -0
  238. package/libs/instance-factories/applications/templates/react/relationship-field-generator.ts +150 -0
  239. package/libs/instance-factories/applications/templates/react/tailwind-adapter-generator.ts +704 -0
  240. package/libs/instance-factories/applications/templates/react/tailwind-adapter-wrapper-generator.ts +84 -0
  241. package/libs/instance-factories/applications/templates/react/tsconfig-generator.ts +35 -0
  242. package/libs/instance-factories/applications/templates/react/use-api-hooks-generator.ts +121 -0
  243. package/libs/instance-factories/applications/templates/react/view-dashboard-generator.ts +150 -0
  244. package/libs/instance-factories/applications/templates/react/view-detail-generator.ts +150 -0
  245. package/libs/instance-factories/applications/templates/react/view-form-generator.ts +362 -0
  246. package/libs/instance-factories/applications/templates/react/view-list-generator.ts +98 -0
  247. package/libs/instance-factories/applications/templates/react/view-router-generator.ts +89 -0
  248. package/libs/instance-factories/applications/templates/react/vite-config-generator.ts +49 -0
  249. package/libs/instance-factories/archived/fastify-prisma.yaml +104 -0
  250. package/libs/instance-factories/cli/commander-js.yaml +55 -0
  251. package/libs/instance-factories/cli/templates/commander/cli-entry-generator.d.ts +12 -0
  252. package/libs/instance-factories/cli/templates/commander/cli-entry-generator.d.ts.map +1 -0
  253. package/libs/instance-factories/cli/templates/commander/cli-entry-generator.js +115 -0
  254. package/libs/instance-factories/cli/templates/commander/cli-entry-generator.js.map +1 -0
  255. package/libs/instance-factories/cli/templates/commander/cli-entry-generator.ts +145 -0
  256. package/libs/instance-factories/cli/templates/commander/command-generator.d.ts +14 -0
  257. package/libs/instance-factories/cli/templates/commander/command-generator.d.ts.map +1 -0
  258. package/libs/instance-factories/cli/templates/commander/command-generator.js +182 -0
  259. package/libs/instance-factories/cli/templates/commander/command-generator.js.map +1 -0
  260. package/libs/instance-factories/cli/templates/commander/command-generator.ts +992 -0
  261. package/libs/instance-factories/communication/event-emitter.yaml +56 -0
  262. package/libs/instance-factories/communication/rabbitmq-events.yaml +87 -0
  263. package/libs/instance-factories/communication/templates/eventemitter/bus-generator.ts +93 -0
  264. package/libs/instance-factories/communication/templates/eventemitter/publisher-generator.ts +117 -0
  265. package/libs/instance-factories/communication/templates/eventemitter/subscriber-generator.ts +101 -0
  266. package/libs/instance-factories/controllers/fastify.yaml +127 -0
  267. package/libs/instance-factories/controllers/templates/fastify/meta-routes-generator.ts +103 -0
  268. package/libs/instance-factories/controllers/templates/fastify/routes-generator.ts +389 -0
  269. package/libs/instance-factories/controllers/templates/fastify/server-generator.ts +76 -0
  270. package/libs/instance-factories/infrastructure/docker-k8s.yaml +61 -0
  271. package/libs/instance-factories/infrastructure/templates/docker-k8s/infrastructure-generator.ts +46 -0
  272. package/libs/instance-factories/orms/prisma.yaml +89 -0
  273. package/libs/instance-factories/orms/templates/prisma/schema-generator.ts +563 -0
  274. package/libs/instance-factories/orms/templates/prisma/services-generator.ts +408 -0
  275. package/libs/instance-factories/scaffolding/generic-scaffold.yaml +65 -0
  276. package/libs/instance-factories/scaffolding/templates/generic/env-example-generator.ts +73 -0
  277. package/libs/instance-factories/scaffolding/templates/generic/env-generator.ts +85 -0
  278. package/libs/instance-factories/scaffolding/templates/generic/gitignore-generator.ts +69 -0
  279. package/libs/instance-factories/scaffolding/templates/generic/package-json-generator.ts +176 -0
  280. package/libs/instance-factories/scaffolding/templates/generic/readme-generator.ts +207 -0
  281. package/libs/instance-factories/scaffolding/templates/generic/tsconfig-generator.ts +78 -0
  282. package/libs/instance-factories/scaffolding/templates/generic/tsconfig-react-generator.ts +41 -0
  283. package/libs/instance-factories/sdks/python-sdk.yaml +66 -0
  284. package/libs/instance-factories/sdks/templates/python/sdk-generator.ts +50 -0
  285. package/libs/instance-factories/sdks/templates/typescript/sdk-generator.ts +49 -0
  286. package/libs/instance-factories/sdks/typescript-sdk.yaml +59 -0
  287. package/libs/instance-factories/services/prisma-services.yaml +71 -0
  288. package/libs/instance-factories/services/templates/prisma/behavior-generator.ts +303 -0
  289. package/libs/instance-factories/services/templates/prisma/controller-generator.ts +532 -0
  290. package/libs/instance-factories/services/templates/prisma/service-generator.ts +315 -0
  291. package/libs/instance-factories/shared/path-resolver.ts +111 -0
  292. package/libs/instance-factories/storage/mongodb.yaml +79 -0
  293. package/libs/instance-factories/storage/postgresql.yaml +75 -0
  294. package/libs/instance-factories/storage/redis.yaml +79 -0
  295. package/libs/instance-factories/storage/templates/mongodb/config-generator.ts +15 -0
  296. package/libs/instance-factories/storage/templates/mongodb/docker-generator.ts +18 -0
  297. package/libs/instance-factories/storage/templates/postgresql/config-generator.ts +54 -0
  298. package/libs/instance-factories/storage/templates/postgresql/docker-generator.ts +55 -0
  299. package/libs/instance-factories/storage/templates/redis/config-generator.ts +16 -0
  300. package/libs/instance-factories/storage/templates/redis/docker-generator.ts +18 -0
  301. package/libs/instance-factories/test-generation.ts +192 -0
  302. package/libs/instance-factories/testing/templates/vitest/tests-generator.ts +51 -0
  303. package/libs/instance-factories/testing/vitest-tests.yaml +63 -0
  304. package/libs/instance-factories/tools/templates/mcp/mcp-server-generator.ts +136 -0
  305. package/libs/instance-factories/tools/templates/mcp/static/docs/DEPLOYMENT_GUIDE.md +630 -0
  306. package/libs/instance-factories/tools/templates/mcp/static/docs/HYBRID_RESOURCE_SYSTEM.md +330 -0
  307. package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/EXTENSION_DEPLOYMENT.md +552 -0
  308. package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/LOCAL_DEPLOYMENT.md +164 -0
  309. package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/WEB_DEPLOYMENT.md +247 -0
  310. package/libs/instance-factories/tools/templates/mcp/static/package.json +92 -0
  311. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-enterprise.js +284 -0
  312. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-extension.js +139 -0
  313. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-local.js +74 -0
  314. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-web.js +156 -0
  315. package/libs/instance-factories/tools/templates/mcp/static/scripts/copy-canonical-files.js +41 -0
  316. package/libs/instance-factories/tools/templates/mcp/static/scripts/test-deployments.js +259 -0
  317. package/libs/instance-factories/tools/templates/mcp/static/scripts/test-hybrid-resources.js +231 -0
  318. package/libs/instance-factories/tools/templates/mcp/static/scripts/test-hybrid-simple.js +196 -0
  319. package/libs/instance-factories/tools/templates/mcp/static/src/controllers/MCPServerController.ts +293 -0
  320. package/libs/instance-factories/tools/templates/mcp/static/src/events/EventEmitter.ts +90 -0
  321. package/libs/instance-factories/tools/templates/mcp/static/src/index.ts +24 -0
  322. package/libs/instance-factories/tools/templates/mcp/static/src/interfaces/ResourceProvider.ts +15 -0
  323. package/libs/instance-factories/tools/templates/mcp/static/src/models/LibrarySuggestion.ts +106 -0
  324. package/libs/instance-factories/tools/templates/mcp/static/src/models/SpecVerseResource.ts +75 -0
  325. package/libs/instance-factories/tools/templates/mcp/static/src/server/mcp-server.ts +239 -0
  326. package/libs/instance-factories/tools/templates/mcp/static/src/services/CLIProxyService.ts +1501 -0
  327. package/libs/instance-factories/tools/templates/mcp/static/src/services/EmbeddedResourcesAdapter.ts +211 -0
  328. package/libs/instance-factories/tools/templates/mcp/static/src/services/EntityModuleService.ts +308 -0
  329. package/libs/instance-factories/tools/templates/mcp/static/src/services/HybridResourcesProvider.ts +210 -0
  330. package/libs/instance-factories/tools/templates/mcp/static/src/services/LibraryToolsService.ts +356 -0
  331. package/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorBridge.ts +524 -0
  332. package/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorToolsService.ts +530 -0
  333. package/libs/instance-factories/tools/templates/mcp/static/src/services/PromptToolsService.ts +594 -0
  334. package/libs/instance-factories/tools/templates/mcp/static/src/services/ResourcesProviderService.ts +170 -0
  335. package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/CLIProxyService.init.test.ts +544 -0
  336. package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/CLIProxyService.test.ts +189 -0
  337. package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/ResourcesProviderService.test.ts +89 -0
  338. package/libs/instance-factories/tools/templates/mcp/static/src/types/index.ts +110 -0
  339. package/libs/instance-factories/tools/templates/mcp/static/tsconfig.json +28 -0
  340. package/libs/instance-factories/tools/templates/vscode/static/extension.ts +1195 -0
  341. package/libs/instance-factories/tools/templates/vscode/static/language-configuration.json +34 -0
  342. package/libs/instance-factories/tools/templates/vscode/static/schemas/specverse-v3-schema.json +4279 -0
  343. package/libs/instance-factories/tools/templates/vscode/static/syntaxes/specverse.tmLanguage.json +138 -0
  344. package/libs/instance-factories/tools/templates/vscode/static/themes/README.md +74 -0
  345. package/libs/instance-factories/tools/templates/vscode/static/themes/complete-specverse-colors.json +122 -0
  346. package/libs/instance-factories/tools/templates/vscode/static/themes/specverse-basic-theme.json +65 -0
  347. package/libs/instance-factories/tools/templates/vscode/static/themes/specverse-complete-theme.json +123 -0
  348. package/libs/instance-factories/tools/templates/vscode/static/themes/specverse-theme-colors.json +64 -0
  349. package/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.ts +214 -0
  350. package/libs/instance-factories/validation/templates/zod/validation-generator.ts +46 -0
  351. package/libs/instance-factories/validation/zod.yaml +56 -0
  352. package/libs/instance-factories/views/index.d.ts +13 -0
  353. package/libs/instance-factories/views/index.d.ts.map +1 -0
  354. package/libs/instance-factories/views/index.js +18 -0
  355. package/libs/instance-factories/views/index.js.map +1 -0
  356. package/libs/instance-factories/views/index.ts +45 -0
  357. package/libs/instance-factories/views/react-components.yaml +129 -0
  358. package/libs/instance-factories/views/templates/ARCHITECTURE.md +198 -0
  359. package/libs/instance-factories/views/templates/react/adapters/antd-adapter.ts +869 -0
  360. package/libs/instance-factories/views/templates/react/adapters/mui-adapter.ts +953 -0
  361. package/libs/instance-factories/views/templates/react/adapters/shadcn-adapter.ts +806 -0
  362. package/libs/instance-factories/views/templates/react/app-generator.ts +55 -0
  363. package/libs/instance-factories/views/templates/react/components-generator.ts +391 -0
  364. package/libs/instance-factories/views/templates/react/forms-generator.ts +343 -0
  365. package/libs/instance-factories/views/templates/react/frontend-package-json-generator.ts +54 -0
  366. package/libs/instance-factories/views/templates/react/hooks-generator.ts +122 -0
  367. package/libs/instance-factories/views/templates/react/index-css-generator.ts +209 -0
  368. package/libs/instance-factories/views/templates/react/index-html-generator.ts +34 -0
  369. package/libs/instance-factories/views/templates/react/main-tsx-generator.ts +29 -0
  370. package/libs/instance-factories/views/templates/react/react-component-generator.d.ts +152 -0
  371. package/libs/instance-factories/views/templates/react/react-component-generator.d.ts.map +1 -0
  372. package/libs/instance-factories/views/templates/react/react-component-generator.js +398 -0
  373. package/libs/instance-factories/views/templates/react/react-component-generator.js.map +1 -0
  374. package/libs/instance-factories/views/templates/react/react-component-generator.ts +533 -0
  375. package/libs/instance-factories/views/templates/react/router-generator.ts +197 -0
  376. package/libs/instance-factories/views/templates/react/router-generic-generator.ts +103 -0
  377. package/libs/instance-factories/views/templates/react/spec-json-generator.ts +17 -0
  378. package/libs/instance-factories/views/templates/react/types-generator.ts +76 -0
  379. package/libs/instance-factories/views/templates/react/views-metadata-generator.ts +42 -0
  380. package/libs/instance-factories/views/templates/react/vite-config-generator.ts +38 -0
  381. package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.d.ts.map +1 -0
  382. package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.js.map +1 -0
  383. package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.ts +474 -0
  384. package/libs/instance-factories/views/templates/shared/__tests__/composite-patterns.test.ts +242 -0
  385. package/libs/instance-factories/views/templates/shared/adapter-types.d.ts +77 -0
  386. package/libs/instance-factories/views/templates/shared/adapter-types.d.ts.map +1 -0
  387. package/libs/instance-factories/views/templates/shared/adapter-types.js +47 -0
  388. package/libs/instance-factories/views/templates/shared/adapter-types.js.map +1 -0
  389. package/libs/instance-factories/views/templates/shared/adapter-types.ts +142 -0
  390. package/libs/instance-factories/views/templates/shared/atomic-components-registry.d.ts +63 -0
  391. package/libs/instance-factories/views/templates/shared/atomic-components-registry.d.ts.map +1 -0
  392. package/libs/instance-factories/views/templates/shared/atomic-components-registry.js +822 -0
  393. package/libs/instance-factories/views/templates/shared/atomic-components-registry.js.map +1 -0
  394. package/libs/instance-factories/views/templates/shared/atomic-components-registry.ts +908 -0
  395. package/libs/instance-factories/views/templates/shared/base-generator.d.ts +247 -0
  396. package/libs/instance-factories/views/templates/shared/base-generator.d.ts.map +1 -0
  397. package/libs/instance-factories/views/templates/shared/base-generator.js +363 -0
  398. package/libs/instance-factories/views/templates/shared/base-generator.js.map +1 -0
  399. package/libs/instance-factories/views/templates/shared/base-generator.ts +608 -0
  400. package/libs/instance-factories/views/templates/shared/component-metadata.d.ts +254 -0
  401. package/libs/instance-factories/views/templates/shared/component-metadata.d.ts.map +1 -0
  402. package/libs/instance-factories/views/templates/shared/component-metadata.js +602 -0
  403. package/libs/instance-factories/views/templates/shared/component-metadata.js.map +1 -0
  404. package/libs/instance-factories/views/templates/shared/component-metadata.ts +803 -0
  405. package/libs/instance-factories/views/templates/shared/composite-pattern-types.ts +250 -0
  406. package/libs/instance-factories/views/templates/shared/composite-patterns.ts +535 -0
  407. package/libs/instance-factories/views/templates/shared/index.ts +68 -0
  408. package/libs/instance-factories/views/templates/shared/pattern-validator.ts +279 -0
  409. package/libs/instance-factories/views/templates/shared/property-mapper.d.ts +149 -0
  410. package/libs/instance-factories/views/templates/shared/property-mapper.d.ts.map +1 -0
  411. package/libs/instance-factories/views/templates/shared/property-mapper.js +580 -0
  412. package/libs/instance-factories/views/templates/shared/property-mapper.js.map +1 -0
  413. package/libs/instance-factories/views/templates/shared/property-mapper.ts +700 -0
  414. package/libs/instance-factories/views/templates/shared/syntax-mapper.d.ts +143 -0
  415. package/libs/instance-factories/views/templates/shared/syntax-mapper.d.ts.map +1 -0
  416. package/libs/instance-factories/views/templates/shared/syntax-mapper.js +420 -0
  417. package/libs/instance-factories/views/templates/shared/syntax-mapper.js.map +1 -0
  418. package/libs/instance-factories/views/templates/shared/syntax-mapper.ts +539 -0
  419. package/package.json +42 -0
  420. package/schema/SPECVERSE-SCHEMA.json +4274 -0
@@ -0,0 +1,1347 @@
1
+ /**
2
+ * React Pattern Adapter
3
+ *
4
+ * Maps tech-independent composite view patterns from @specverse/lang
5
+ * to React-specific implementations with hooks and Tailwind styling.
6
+ *
7
+ * This adapter implements the unified view architecture by:
8
+ * 1. Using COMPOSITE_VIEW_PATTERNS as the single source of truth
9
+ * 2. Mapping semantic CURVED operations to React hooks/API calls
10
+ * 3. Rendering using ATOMIC_COMPONENTS_REGISTRY via Tailwind
11
+ *
12
+ * Stage 2: React Adapter Refactor
13
+ */
14
+
15
+ import { useMemo } from 'react';
16
+ import {
17
+ COMPOSITE_VIEW_PATTERNS,
18
+ ATOMIC_COMPONENTS_REGISTRY,
19
+ type CompositeViewPattern,
20
+ type CURVEDOperation
21
+ } from '@specverse/lang/browser';
22
+ import { createUniversalTailwindAdapter } from './tailwind-adapter-generator';
23
+
24
+ /**
25
+ * React-specific protocol mapping for CURVED operations
26
+ *
27
+ * Maps semantic operations to HTTP methods and endpoints.
28
+ * This is what lives in instance factories for code generation,
29
+ * but for runtime we need it here.
30
+ */
31
+ export const REACT_PROTOCOL_MAPPING: Record<CURVEDOperation, {
32
+ method: string;
33
+ pathPattern: string;
34
+ }> = {
35
+ create: {
36
+ method: 'POST',
37
+ pathPattern: '/api/{resource}'
38
+ },
39
+ update: {
40
+ method: 'PUT',
41
+ pathPattern: '/api/{resource}/{id}'
42
+ },
43
+ retrieve: {
44
+ method: 'GET',
45
+ pathPattern: '/api/{resource}/{id}'
46
+ },
47
+ retrieve_many: {
48
+ method: 'GET',
49
+ pathPattern: '/api/{resource}'
50
+ },
51
+ validate: {
52
+ method: 'POST',
53
+ pathPattern: '/api/{resource}/validate'
54
+ },
55
+ evolve: {
56
+ method: 'POST',
57
+ pathPattern: '/api/{resource}/{id}/evolve'
58
+ },
59
+ delete: {
60
+ method: 'DELETE',
61
+ pathPattern: '/api/{resource}/{id}'
62
+ }
63
+ };
64
+
65
+ /**
66
+ * React Pattern Adapter Configuration
67
+ */
68
+ export interface ReactPatternAdapterConfig {
69
+ // API base URL
70
+ apiBaseUrl?: string;
71
+
72
+ // Custom protocol mapping (overrides defaults)
73
+ protocolMapping?: Partial<typeof REACT_PROTOCOL_MAPPING>;
74
+
75
+ // Tailwind adapter instance
76
+ tailwindAdapter?: ReturnType<typeof createUniversalTailwindAdapter>;
77
+ }
78
+
79
+ /**
80
+ * Pattern rendering context with React-specific data
81
+ */
82
+ export interface PatternRenderContext {
83
+ // Pattern being rendered
84
+ pattern: CompositeViewPattern;
85
+
86
+ // View spec from API
87
+ viewSpec: any;
88
+
89
+ // Model data (entity instances)
90
+ modelData: Record<string, any[]>;
91
+
92
+ // Model schemas (structure definitions with attributes and relationships)
93
+ modelSchemas?: Record<string, any>;
94
+
95
+ // Primary model name
96
+ primaryModel?: string;
97
+
98
+ // Selected entity (for detail/dashboard views)
99
+ selectedEntity?: any;
100
+
101
+ // Entities for primary model
102
+ primaryEntities?: any[];
103
+
104
+ // Protocol mapping
105
+ protocolMapping: typeof REACT_PROTOCOL_MAPPING;
106
+
107
+ // Tailwind adapter
108
+ tailwindAdapter: ReturnType<typeof createUniversalTailwindAdapter>;
109
+ }
110
+
111
+ /**
112
+ * React Pattern Adapter
113
+ *
114
+ * Provides React-specific rendering of tech-independent composite patterns.
115
+ */
116
+ export class ReactPatternAdapter {
117
+ private tailwindAdapter: ReturnType<typeof createUniversalTailwindAdapter>;
118
+
119
+ constructor(config: ReactPatternAdapterConfig = {}) {
120
+ this.tailwindAdapter = config.tailwindAdapter || createUniversalTailwindAdapter();
121
+ // Store for future use
122
+ // this._config = config;
123
+ // this._protocolMapping = { ...REACT_PROTOCOL_MAPPING, ...config.protocolMapping };
124
+ }
125
+
126
+ /**
127
+ * Detect pattern type from view spec
128
+ */
129
+ detectPattern(viewSpec: any): CompositeViewPattern | null {
130
+ const viewType = viewSpec.type?.toLowerCase();
131
+
132
+ // Map view type to pattern ID
133
+ const typeToPattern: Record<string, string> = {
134
+ 'form': 'form-view',
135
+ 'list': 'list-view',
136
+ 'detail': 'detail-view',
137
+ 'dashboard': 'dashboard-view'
138
+ };
139
+
140
+ const patternId = typeToPattern[viewType];
141
+ if (!patternId) return null;
142
+
143
+ return COMPOSITE_VIEW_PATTERNS[patternId];
144
+ }
145
+
146
+ /**
147
+ * Render a pattern to HTML
148
+ */
149
+ renderPattern(context: PatternRenderContext): string {
150
+ const { pattern } = context;
151
+
152
+ // Render based on pattern category
153
+ switch (pattern.category) {
154
+ case 'data-entry':
155
+ return this.renderFormView(context);
156
+ case 'data-display':
157
+ if (pattern.id === 'list-view') {
158
+ return this.renderListView(context);
159
+ } else if (pattern.id === 'detail-view') {
160
+ return this.renderDetailView(context);
161
+ }
162
+ return this.renderGenericDataDisplay(context);
163
+ case 'dashboard':
164
+ return this.renderDashboardView(context);
165
+ default:
166
+ return this.renderFallback(context);
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Render FormView pattern
172
+ */
173
+ private renderFormView(context: PatternRenderContext): string {
174
+ const { viewSpec, modelData, modelSchemas, primaryModel } = context;
175
+ let components = viewSpec.uiComponents || {};
176
+
177
+ // FALLBACK: Generate default form component if none defined
178
+ if (Object.keys(components).length === 0 && primaryModel) {
179
+ components = {
180
+ [`${primaryModel}Form`]: {
181
+ type: 'form',
182
+ properties: {
183
+ model: primaryModel,
184
+ sections: [
185
+ {
186
+ title: `${primaryModel} Details`,
187
+ fields: this.inferFieldsFromSchema(modelSchemas, primaryModel)
188
+ }
189
+ ]
190
+ }
191
+ }
192
+ };
193
+ }
194
+
195
+ let html = '<div class="space-y-4">';
196
+
197
+ // Render form components
198
+ for (const [componentName, componentDef] of Object.entries(components)) {
199
+ const def = componentDef as any;
200
+ const type = def.type?.toLowerCase();
201
+ const properties = def.properties || def;
202
+
203
+ if (type === 'form') {
204
+ html += this.renderFormComponent(componentName, def, modelData, modelSchemas, primaryModel, this.tailwindAdapter);
205
+ } else if (this.tailwindAdapter.components[type]) {
206
+ html += this.renderAtomicComponent(componentName, type, properties, this.tailwindAdapter);
207
+ }
208
+ }
209
+
210
+ html += '</div>';
211
+ return html;
212
+ }
213
+
214
+ /**
215
+ * Render ListView pattern
216
+ */
217
+ private renderListView(context: PatternRenderContext): string {
218
+ const { viewSpec, modelData, primaryModel } = context;
219
+ let components = viewSpec.uiComponents || {};
220
+
221
+ // FALLBACK: Generate default table component if none defined
222
+ if (Object.keys(components).length === 0 && primaryModel) {
223
+ const columns = this.inferFieldsFromModel(modelData, primaryModel);
224
+ components = {
225
+ [`${primaryModel}Table`]: {
226
+ type: 'table',
227
+ properties: {
228
+ model: primaryModel,
229
+ columns: columns
230
+ }
231
+ }
232
+ };
233
+ }
234
+
235
+ let html = '<div class="space-y-4">';
236
+
237
+ // Render list components (filters, table/list, pagination)
238
+ for (const [componentName, componentDef] of Object.entries(components)) {
239
+ const def = componentDef as any;
240
+ const type = def.type?.toLowerCase();
241
+ const properties = def.properties || def;
242
+
243
+ if (type === 'table') {
244
+ html += this.renderTableComponent(componentName, def, modelData, primaryModel, this.tailwindAdapter);
245
+ } else if (type === 'list') {
246
+ html += this.renderListComponent(componentName, def, modelData, primaryModel, this.tailwindAdapter);
247
+ } else if (this.tailwindAdapter.components[type]) {
248
+ html += this.renderAtomicComponent(componentName, type, properties, this.tailwindAdapter);
249
+ }
250
+ }
251
+
252
+ html += '</div>';
253
+ return html;
254
+ }
255
+
256
+ /**
257
+ * Render DetailView pattern
258
+ */
259
+ private renderDetailView(context: PatternRenderContext): string {
260
+ const { viewSpec, selectedEntity, primaryModel, modelData, modelSchemas } = context;
261
+ let components = viewSpec.uiComponents || {};
262
+
263
+ if (!selectedEntity) {
264
+ return '<div class="p-4 text-gray-500 dark:text-gray-400">No entity selected</div>';
265
+ }
266
+
267
+ // FALLBACK: Generate default components if none defined
268
+ if (Object.keys(components).length === 0 && primaryModel) {
269
+ // Get fields from schema or fall back to data inference
270
+ const fields = this.inferFieldsFromSchema(modelSchemas, primaryModel);
271
+
272
+ // Start with content component
273
+ components = {
274
+ [`${primaryModel}Content`]: {
275
+ type: 'content',
276
+ fields: fields,
277
+ properties: {
278
+ model: primaryModel
279
+ }
280
+ }
281
+ };
282
+
283
+ // Add list components for hasMany relationships (from schema)
284
+ if (modelSchemas && modelSchemas[primaryModel]?.relationships) {
285
+ const schemaRelationships = modelSchemas[primaryModel].relationships;
286
+
287
+ for (const [relName, relDef] of Object.entries(schemaRelationships)) {
288
+ const relDefObj = relDef as any;
289
+
290
+ // Only include hasMany relationships (these show as lists in detail view)
291
+ if (relDefObj.type === 'hasMany') {
292
+ const targetModel = relDefObj.targetModel || relDefObj.model || relName.charAt(0).toUpperCase() + relName.slice(1);
293
+
294
+ // Infer fields for the related model
295
+ const relatedFields = this.inferFieldsFromSchema(modelSchemas, targetModel);
296
+
297
+ components[`${relName}List`] = {
298
+ type: 'list',
299
+ fields: relatedFields.slice(0, 5), // Limit to first 5 fields for table
300
+ properties: {
301
+ model: targetModel,
302
+ relationship: relName
303
+ }
304
+ };
305
+ }
306
+ }
307
+ }
308
+ }
309
+
310
+ let html = '<div class="space-y-4">';
311
+
312
+ // Render detail components
313
+ for (const [componentName, componentDef] of Object.entries(components)) {
314
+ const def = componentDef as any;
315
+ const type = def.type?.toLowerCase();
316
+ const properties = def.properties || def;
317
+
318
+ if (type === 'content') {
319
+ html += this.renderContentComponent(componentName, def, selectedEntity, this.tailwindAdapter);
320
+ } else if (type === 'list') {
321
+ html += this.renderDetailListComponent(componentName, def, modelData, selectedEntity, primaryModel, this.tailwindAdapter);
322
+ } else if (type === 'card') {
323
+ html += this.renderCardComponent(componentName, def, selectedEntity, this.tailwindAdapter);
324
+ } else if (this.tailwindAdapter.components[type]) {
325
+ html += this.renderAtomicComponent(componentName, type, properties, this.tailwindAdapter);
326
+ }
327
+ }
328
+
329
+ html += '</div>';
330
+ return html;
331
+ }
332
+
333
+ /**
334
+ * Render DashboardView pattern
335
+ */
336
+ private renderDashboardView(context: PatternRenderContext): string {
337
+ const { viewSpec, modelData } = context;
338
+ const components = viewSpec.uiComponents || {};
339
+
340
+ let html = '<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">';
341
+
342
+ // Render dashboard components (metrics, charts, etc.)
343
+ for (const [componentName, componentDef] of Object.entries(components)) {
344
+ const def = componentDef as any;
345
+ const type = def.type?.toLowerCase();
346
+ const properties = def.properties || def;
347
+
348
+ if (type === 'card' && properties.variant === 'metric') {
349
+ html += this.renderMetricCard(componentName, def, modelData, this.tailwindAdapter);
350
+ } else if (this.tailwindAdapter.components[type]) {
351
+ html += this.renderAtomicComponent(componentName, type, properties, this.tailwindAdapter);
352
+ }
353
+ }
354
+
355
+ html += '</div>';
356
+ return html;
357
+ }
358
+
359
+ /**
360
+ * Render generic data display (fallback)
361
+ */
362
+ private renderGenericDataDisplay(_context: PatternRenderContext): string {
363
+ return '<div class="p-4 text-gray-500 dark:text-gray-400">Generic data display</div>';
364
+ }
365
+
366
+ /**
367
+ * Render fallback for unknown patterns
368
+ */
369
+ private renderFallback(context: PatternRenderContext): string {
370
+ const { pattern } = context;
371
+ return `<div class="p-4 bg-yellow-50 dark:bg-yellow-900/30 border border-yellow-200 dark:border-yellow-700 rounded">
372
+ <p class="text-yellow-800 dark:text-yellow-200">Pattern not yet implemented: ${pattern.name}</p>
373
+ </div>`;
374
+ }
375
+
376
+ /**
377
+ * Helper: Render form component
378
+ *
379
+ * Generates a complete form with:
380
+ * - Inferred field types from model data (text, number, boolean)
381
+ * - Proper input types and validation
382
+ * - Required field indicators
383
+ * - Submit and reset buttons
384
+ * - Professional styling matching admin-demo
385
+ */
386
+ private renderFormComponent(
387
+ componentName: string,
388
+ componentDef: any,
389
+ modelData: Record<string, any[]>,
390
+ modelSchemas: Record<string, any> | undefined,
391
+ primaryModel: string | undefined,
392
+ _tailwindAdapter: ReturnType<typeof createUniversalTailwindAdapter>
393
+ ): string {
394
+ const properties = componentDef.properties || componentDef;
395
+ const sections = properties.sections || [];
396
+ const model = properties.model || primaryModel;
397
+
398
+ // Infer field types from model schema or data
399
+ const fieldTypes = this.inferFieldTypes(modelSchemas, modelData, model);
400
+
401
+ let html = `
402
+ <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
403
+ <div class="px-4 py-2 bg-gray-50 dark:bg-gray-700 border-b border-gray-200 dark:border-gray-600">
404
+ <h4 class="font-semibold text-sm text-gray-700 dark:text-gray-200">${componentName}</h4>
405
+ </div>
406
+ <div class="p-6">
407
+ <form class="space-y-6">
408
+ `;
409
+
410
+ // Render form sections
411
+ for (const section of sections) {
412
+ const fields = section.fields || [];
413
+ html += `
414
+ <div>
415
+ ${section.title ? `<h5 class="font-semibold text-gray-900 dark:text-gray-100 mb-4">${section.title}</h5>` : ''}
416
+ <div class="space-y-4">
417
+ `;
418
+
419
+ // Show helpful message if no fields
420
+ if (fields.length === 0) {
421
+ html += `
422
+ <div class="text-sm text-gray-500 dark:text-gray-400 italic p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-700 rounded">
423
+ No fields available for this form. Model data may be empty or all fields are system-generated.
424
+ </div>
425
+ `;
426
+ }
427
+
428
+ for (const field of fields) {
429
+ const fieldName = typeof field === 'string' ? field : field.name || field.fieldName;
430
+ const fieldLabel = typeof field === 'string'
431
+ ? this.humanizeFieldName(fieldName)
432
+ : field.label || this.humanizeFieldName(fieldName);
433
+ const fieldType = fieldTypes[fieldName] || 'string';
434
+ const isRequired = field.required !== false; // Default to required
435
+
436
+ html += `<div>`;
437
+ html += `
438
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">
439
+ ${fieldLabel}
440
+ ${isRequired ? '<span class="text-red-500 dark:text-red-400 ml-1">*</span>' : ''}
441
+ </label>
442
+ `;
443
+
444
+ // Generate appropriate input based on field type
445
+ if (fieldType === 'boolean') {
446
+ html += `
447
+ <div class="flex items-center">
448
+ <input
449
+ type="checkbox"
450
+ name="${fieldName}"
451
+ class="h-4 w-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded focus:ring-blue-500"
452
+ />
453
+ <span class="ml-2 text-sm text-gray-600 dark:text-gray-300">Yes/No</span>
454
+ </div>
455
+ `;
456
+ } else if (fieldType === 'number') {
457
+ html += `
458
+ <input
459
+ type="number"
460
+ name="${fieldName}"
461
+ placeholder="Enter ${fieldLabel.toLowerCase()}"
462
+ ${isRequired ? 'required' : ''}
463
+ class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
464
+ />
465
+ `;
466
+ } else {
467
+ // Text input (default)
468
+ html += `
469
+ <input
470
+ type="text"
471
+ name="${fieldName}"
472
+ placeholder="Enter ${fieldLabel.toLowerCase()}"
473
+ ${isRequired ? 'required' : ''}
474
+ class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
475
+ />
476
+ `;
477
+ }
478
+
479
+ html += `</div>`;
480
+ }
481
+
482
+ html += `
483
+ </div>
484
+ </div>
485
+ `;
486
+ }
487
+
488
+ // Add relationship fields (belongsTo) as select dropdowns
489
+ const relationshipFields = this.inferRelationshipFields(modelSchemas, modelData, model);
490
+ if (relationshipFields.length > 0) {
491
+ html += `
492
+ <div>
493
+ <h5 class="font-semibold text-gray-900 dark:text-gray-100 mb-4">Relationships</h5>
494
+ <div class="space-y-4">
495
+ `;
496
+
497
+ for (const relField of relationshipFields) {
498
+ const relatedEntities = modelData[relField.targetModel] || [];
499
+
500
+ html += `
501
+ <div>
502
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">
503
+ ${this.humanizeFieldName(relField.name)}
504
+ </label>
505
+ <select
506
+ name="${relField.foreignKey}"
507
+ class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
508
+ >
509
+ <option value="">-- Select ${relField.targetModel} --</option>
510
+ ${relatedEntities.map((entity: any) => {
511
+ const displayName = entity.data?.name || entity.data?.title || entity.id;
512
+ return `<option value="${entity.id}">${displayName}</option>`;
513
+ }).join('')}
514
+ </select>
515
+ </div>
516
+ `;
517
+ }
518
+
519
+ html += `
520
+ </div>
521
+ </div>
522
+ `;
523
+ }
524
+
525
+ // Add form action buttons
526
+ html += `
527
+ <div class="flex gap-3 pt-4 border-t border-gray-200 dark:border-gray-600">
528
+ <button
529
+ type="submit"
530
+ class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded font-medium transition-colors"
531
+ >
532
+ Create ${model || 'Entity'}
533
+ </button>
534
+ <button
535
+ type="reset"
536
+ class="px-6 py-2 bg-gray-200 dark:bg-gray-600 hover:bg-gray-300 dark:hover:bg-gray-500 text-gray-800 dark:text-gray-100 rounded font-medium transition-colors"
537
+ >
538
+ Reset
539
+ </button>
540
+ </div>
541
+ </form>
542
+ </div>
543
+ </div>
544
+ `;
545
+
546
+ return html;
547
+ }
548
+
549
+ /**
550
+ * Helper: Infer relationship fields from model schema or data
551
+ *
552
+ * Reads relationship definitions from the model schema (like original FormView).
553
+ * Falls back to inferring from foreign keys in data if no schema available.
554
+ *
555
+ * Strategy:
556
+ * 1. Read from schema.relationships (preferred - matches original FormView)
557
+ * 2. If entities exist, scan their fields for foreign keys
558
+ * 3. If no entities, look at available models and infer common relationships
559
+ */
560
+ private inferRelationshipFields(
561
+ modelSchemas: Record<string, any> | undefined,
562
+ modelData: Record<string, any[]>,
563
+ modelName: string | undefined
564
+ ): Array<{
565
+ name: string;
566
+ foreignKey: string;
567
+ targetModel: string;
568
+ }> {
569
+ const relationships: Array<{
570
+ name: string;
571
+ foreignKey: string;
572
+ targetModel: string;
573
+ }> = [];
574
+
575
+ if (!modelName) return relationships;
576
+
577
+ // Strategy 1: Read from schema.relationships (like original FormView)
578
+ if (modelSchemas && modelSchemas[modelName]?.relationships) {
579
+ const schemaRelationships = modelSchemas[modelName].relationships;
580
+
581
+ // Extract belongsTo relationships (these need form dropdowns)
582
+ for (const [relName, relDef] of Object.entries(schemaRelationships)) {
583
+ const relDefObj = relDef as any;
584
+
585
+ // Only include belongsTo relationships in forms
586
+ if (relDefObj.type === 'belongsTo') {
587
+ relationships.push({
588
+ name: relName,
589
+ foreignKey: relDefObj.foreignKey || `${relName}Id`,
590
+ targetModel: relDefObj.targetModel || relDefObj.model || relName.charAt(0).toUpperCase() + relName.slice(1)
591
+ });
592
+ }
593
+ }
594
+
595
+ return relationships;
596
+ }
597
+
598
+ // Strategy 2: If we have entities, scan for foreign keys
599
+ const entities = modelData[modelName] || [];
600
+ if (entities.length > 0 && entities[0]?.data) {
601
+ const firstEntity = entities[0];
602
+
603
+ // Look for foreign key fields (ending in "Id")
604
+ for (const fieldName of Object.keys(firstEntity.data)) {
605
+ if (fieldName.endsWith('Id') && fieldName !== 'id') {
606
+ // Extract relationship name (e.g., "authorId" -> "author")
607
+ const relName = fieldName.slice(0, -2);
608
+ // Capitalize to get model name (e.g., "author" -> "Author")
609
+ const targetModel = relName.charAt(0).toUpperCase() + relName.slice(1);
610
+
611
+ // Check if this model exists in modelData
612
+ if (modelData[targetModel]) {
613
+ relationships.push({
614
+ name: relName,
615
+ foreignKey: fieldName,
616
+ targetModel: targetModel
617
+ });
618
+ }
619
+ }
620
+ }
621
+ }
622
+
623
+ // Strategy 3: If no entities or no relationships found, infer from available models
624
+ // This ensures forms are usable even when creating the first entity
625
+ if (relationships.length === 0) {
626
+ const availableModels = Object.keys(modelData);
627
+
628
+ // Common relationship patterns based on model names
629
+ for (const targetModel of availableModels) {
630
+ if (targetModel !== modelName) {
631
+ // Create lowercase version for foreign key
632
+ const relName = targetModel.charAt(0).toLowerCase() + targetModel.slice(1);
633
+ const foreignKey = `${relName}Id`;
634
+
635
+ // Add as potential relationship
636
+ relationships.push({
637
+ name: relName,
638
+ foreignKey: foreignKey,
639
+ targetModel: targetModel
640
+ });
641
+ }
642
+ }
643
+ }
644
+
645
+ return relationships;
646
+ }
647
+
648
+ /**
649
+ * Helper: Humanize field name
650
+ * Converts camelCase/snake_case to readable labels
651
+ */
652
+ private humanizeFieldName(fieldName: string): string {
653
+ return fieldName
654
+ .replace(/([A-Z])/g, ' $1') // Add space before capitals
655
+ .replace(/_/g, ' ') // Replace underscores with spaces
656
+ .replace(/^./, (str) => str.toUpperCase()) // Capitalize first letter
657
+ .trim();
658
+ }
659
+
660
+ /**
661
+ * Helper: Infer field types from model schema or data
662
+ * Prefers schema definitions, falls back to examining actual data
663
+ */
664
+ private inferFieldTypes(modelSchemas: Record<string, any> | undefined, modelData: Record<string, any[]>, modelName: string | undefined): Record<string, string> {
665
+ const types: Record<string, string> = {};
666
+
667
+ if (!modelName) return types;
668
+
669
+ // Strategy 1: Use schema if available
670
+ if (modelSchemas && modelSchemas[modelName]?.attributes) {
671
+ const attributes = modelSchemas[modelName].attributes;
672
+ for (const [fieldName, attrDef] of Object.entries(attributes)) {
673
+ const typeStr = typeof attrDef === 'string' ? attrDef : (attrDef as any)?.type || 'string';
674
+
675
+ if (typeStr.toLowerCase().includes('bool')) {
676
+ types[fieldName] = 'boolean';
677
+ } else if (typeStr.toLowerCase().includes('int') || typeStr.toLowerCase().includes('number') || typeStr.toLowerCase().includes('float')) {
678
+ types[fieldName] = 'number';
679
+ } else {
680
+ types[fieldName] = 'string';
681
+ }
682
+ }
683
+ return types;
684
+ }
685
+
686
+ // Strategy 2: Examine actual data if no schema
687
+ if (!modelData[modelName] || modelData[modelName].length === 0) return types;
688
+
689
+ const firstEntity = modelData[modelName][0];
690
+ if (!firstEntity || !firstEntity.data) return types;
691
+
692
+ for (const [fieldName, value] of Object.entries(firstEntity.data)) {
693
+ if (typeof value === 'boolean') {
694
+ types[fieldName] = 'boolean';
695
+ } else if (typeof value === 'number') {
696
+ types[fieldName] = 'number';
697
+ } else {
698
+ types[fieldName] = 'string';
699
+ }
700
+ }
701
+
702
+ return types;
703
+ }
704
+
705
+ /**
706
+ * Helper: Infer field names from model schema
707
+ * Reads schema.attributes to get field definitions
708
+ */
709
+ private inferFieldsFromSchema(modelSchemas: Record<string, any> | undefined, modelName: string | undefined): string[] {
710
+ if (!modelName || !modelSchemas || !modelSchemas[modelName]) {
711
+ // No schema - return common default fields
712
+ return ['name', 'title', 'description'];
713
+ }
714
+
715
+ const schema = modelSchemas[modelName];
716
+ if (!schema.attributes) {
717
+ return ['name', 'title', 'description'];
718
+ }
719
+
720
+ // Get field names from schema attributes, filter out system fields
721
+ const fields = Object.keys(schema.attributes).filter(field =>
722
+ field !== 'id' &&
723
+ field !== 'createdAt' &&
724
+ field !== 'updatedAt' &&
725
+ !this.isAutoGeneratedField(field, schema.attributes[field])
726
+ );
727
+
728
+ return fields.length > 0 ? fields : ['name', 'title', 'description'];
729
+ }
730
+
731
+ /**
732
+ * Helper: Check if field is auto-generated (shouldn't show in forms)
733
+ */
734
+ private isAutoGeneratedField(fieldName: string, attrDef: any): boolean {
735
+ // System fields
736
+ if (['id', 'createdAt', 'updatedAt'].includes(fieldName)) return true;
737
+
738
+ // Check attribute definition for auto flag
739
+ if (typeof attrDef === 'object' && attrDef.auto) return true;
740
+
741
+ return false;
742
+ }
743
+
744
+ /**
745
+ * Helper: Render table component
746
+ *
747
+ * Generates a data table with:
748
+ * - Column headers with proper formatting
749
+ * - Data type-aware value rendering (objects, booleans, etc.)
750
+ * - Empty state handling
751
+ * - Hover effects and professional styling
752
+ * - Responsive scrolling
753
+ */
754
+ private renderTableComponent(
755
+ componentName: string,
756
+ componentDef: any,
757
+ modelData: Record<string, any[]>,
758
+ primaryModel: string | undefined,
759
+ _tailwindAdapter: ReturnType<typeof createUniversalTailwindAdapter>
760
+ ): string {
761
+ const properties = componentDef.properties || componentDef;
762
+ const tableModel = properties.model || primaryModel;
763
+ const entities = modelData[tableModel || ''] || [];
764
+ const columns = properties.columns || [];
765
+
766
+ let html = `
767
+ <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
768
+ <div class="px-4 py-2 bg-gray-50 dark:bg-gray-700 border-b border-gray-200 dark:border-gray-600">
769
+ <h4 class="font-semibold text-sm text-gray-700 dark:text-gray-200">${componentName}</h4>
770
+ </div>
771
+ <div class="overflow-auto max-h-96">
772
+ <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
773
+ <thead class="bg-gray-50 dark:bg-gray-700 sticky top-0">
774
+ <tr>
775
+ ${columns.map((col: string) => `
776
+ <th class="px-4 py-2 text-left text-xs font-semibold text-gray-700 dark:text-gray-200 uppercase tracking-wider">
777
+ ${this.humanizeFieldName(col)}
778
+ </th>
779
+ `).join('')}
780
+ </tr>
781
+ </thead>
782
+ <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
783
+ `;
784
+
785
+ if (entities.length === 0) {
786
+ html += `
787
+ <tr>
788
+ <td colspan="${columns.length}" class="px-4 py-8 text-center text-sm text-gray-500 dark:text-gray-400 italic">
789
+ No ${tableModel || 'data'} entities yet
790
+ </td>
791
+ </tr>
792
+ `;
793
+ } else {
794
+ for (const entity of entities) {
795
+ html += `<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">`;
796
+
797
+ for (const col of columns) {
798
+ const value = entity.data?.[col];
799
+ let displayValue: string;
800
+
801
+ // Format value based on type
802
+ if (value === undefined || value === null || value === '') {
803
+ displayValue = '<span class="text-gray-400 dark:text-gray-500">—</span>';
804
+ } else if (typeof value === 'boolean') {
805
+ displayValue = `
806
+ <span class="inline-block px-2 py-1 rounded text-xs font-medium ${
807
+ value
808
+ ? 'bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-200'
809
+ : 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200'
810
+ }">
811
+ ${value ? 'Yes' : 'No'}
812
+ </span>
813
+ `;
814
+ } else if (typeof value === 'object') {
815
+ // Show [Object] for objects in tables
816
+ displayValue = '<span class="text-gray-500 dark:text-gray-400 italic">[Object]</span>';
817
+ } else {
818
+ // Escape HTML for safe rendering
819
+ displayValue = String(value)
820
+ .replace(/&/g, '&amp;')
821
+ .replace(/</g, '&lt;')
822
+ .replace(/>/g, '&gt;')
823
+ .replace(/"/g, '&quot;');
824
+ }
825
+
826
+ html += `<td class="px-4 py-2 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">${displayValue}</td>`;
827
+ }
828
+
829
+ html += `</tr>`;
830
+ }
831
+ }
832
+
833
+ html += `
834
+ </tbody>
835
+ </table>
836
+ </div>
837
+ </div>
838
+ `;
839
+
840
+ return html;
841
+ }
842
+
843
+ /**
844
+ * Helper: Render list component
845
+ *
846
+ * Generates a list view with:
847
+ * - Flexible field display (comma-separated or multi-line)
848
+ * - Data type-aware rendering
849
+ * - Empty state handling
850
+ * - Professional card-based styling
851
+ */
852
+ private renderListComponent(
853
+ componentName: string,
854
+ componentDef: any,
855
+ modelData: Record<string, any[]>,
856
+ primaryModel: string | undefined,
857
+ _tailwindAdapter: ReturnType<typeof createUniversalTailwindAdapter>
858
+ ): string {
859
+ const properties = componentDef.properties || componentDef;
860
+ const listModel = properties.model || primaryModel;
861
+ const entities = modelData[listModel || ''] || [];
862
+ const fields = properties.fields || [];
863
+
864
+ let html = `
865
+ <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
866
+ <div class="px-4 py-2 bg-gray-50 dark:bg-gray-700 border-b border-gray-200 dark:border-gray-600">
867
+ <h4 class="font-semibold text-sm text-gray-700 dark:text-gray-200">${componentName}</h4>
868
+ </div>
869
+ <div class="p-4">
870
+ `;
871
+
872
+ if (entities.length === 0) {
873
+ html += `
874
+ <div class="text-sm text-gray-500 dark:text-gray-400 italic text-center py-4">
875
+ No ${listModel || 'items'} yet
876
+ </div>
877
+ `;
878
+ } else {
879
+ html += '<ul class="space-y-2">';
880
+
881
+ for (const entity of entities) {
882
+ html += `
883
+ <li class="p-3 border border-gray-200 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
884
+ <div class="text-sm text-gray-900 dark:text-gray-100">
885
+ `;
886
+
887
+ // Render each field
888
+ const fieldValues: string[] = [];
889
+ for (const field of fields) {
890
+ const value = entity.data?.[field];
891
+
892
+ if (value === undefined || value === null || value === '') {
893
+ continue; // Skip empty values
894
+ }
895
+
896
+ let displayValue: string;
897
+
898
+ if (typeof value === 'boolean') {
899
+ displayValue = value ? 'Yes' : 'No';
900
+ } else if (typeof value === 'object') {
901
+ displayValue = '[Object]';
902
+ } else {
903
+ displayValue = String(value)
904
+ .replace(/&/g, '&amp;')
905
+ .replace(/</g, '&lt;')
906
+ .replace(/>/g, '&gt;')
907
+ .replace(/"/g, '&quot;');
908
+ }
909
+
910
+ fieldValues.push(`<strong>${this.humanizeFieldName(field)}:</strong> ${displayValue}`);
911
+ }
912
+
913
+ html += fieldValues.join(' • ');
914
+
915
+ html += `
916
+ </div>
917
+ </li>
918
+ `;
919
+ }
920
+
921
+ html += '</ul>';
922
+ }
923
+
924
+ html += `
925
+ </div>
926
+ </div>
927
+ `;
928
+
929
+ return html;
930
+ }
931
+
932
+ /**
933
+ * Helper: Render content component (for detail views)
934
+ *
935
+ * Displays specific fields from an entity with rich formatting.
936
+ * This matches the original DetailView's "content" component behavior.
937
+ */
938
+ private renderContentComponent(
939
+ componentName: string,
940
+ componentDef: any,
941
+ selectedEntity: any,
942
+ _tailwindAdapter: ReturnType<typeof createUniversalTailwindAdapter>
943
+ ): string {
944
+ let fields = componentDef.fields || [];
945
+ const entityData = selectedEntity?.data || selectedEntity || {};
946
+
947
+ // FALLBACK: If no fields, infer from entity data
948
+ if (fields.length === 0 && entityData) {
949
+ fields = Object.keys(entityData).filter(key =>
950
+ key !== 'id' &&
951
+ !key.endsWith('Id') &&
952
+ !['createdAt', 'updatedAt'].includes(key)
953
+ );
954
+ }
955
+
956
+ let html = `
957
+ <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
958
+ <div class="px-4 py-2 bg-gray-50 dark:bg-gray-700 border-b border-gray-200 dark:border-gray-600">
959
+ <h4 class="font-semibold text-sm text-gray-700 dark:text-gray-200 capitalize">${componentName}</h4>
960
+ </div>
961
+ <div class="p-4">
962
+ `;
963
+
964
+ // Show debug info if still no fields
965
+ if (fields.length === 0) {
966
+ html += `
967
+ <p class="text-sm text-yellow-600 dark:text-yellow-400 italic">
968
+ No fields available (entity keys: ${Object.keys(entityData).join(', ')})
969
+ </p>
970
+ `;
971
+ } else {
972
+ html += '<div class="space-y-3">';
973
+
974
+ let displayedFields = 0;
975
+ for (const fieldName of fields) {
976
+ // Skip id fields
977
+ if (fieldName === 'id') continue;
978
+
979
+ const value = entityData[fieldName];
980
+
981
+ // Format the value (show even if null/undefined/empty)
982
+ let formattedValue: string;
983
+
984
+ if (value === undefined || value === null) {
985
+ formattedValue = '<span class="text-gray-400 dark:text-gray-500 italic">Not set</span>';
986
+ } else if (typeof value === 'object') {
987
+ // Objects: Show as formatted JSON
988
+ const jsonStr = JSON.stringify(value, null, 2)
989
+ .replace(/&/g, '&amp;')
990
+ .replace(/</g, '&lt;')
991
+ .replace(/>/g, '&gt;')
992
+ .replace(/"/g, '&quot;');
993
+ formattedValue = `
994
+ <pre class="bg-gray-50 dark:bg-gray-900 p-2 rounded border border-gray-200 dark:border-gray-600 text-xs overflow-auto text-gray-900 dark:text-gray-100">${jsonStr}</pre>
995
+ `;
996
+ } else if (typeof value === 'boolean') {
997
+ // Booleans: Show as Yes/No badge
998
+ formattedValue = `
999
+ <span class="inline-block px-2 py-1 rounded text-xs font-medium ${
1000
+ value
1001
+ ? 'bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-200'
1002
+ : 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200'
1003
+ }">
1004
+ ${value ? 'Yes' : 'No'}
1005
+ </span>
1006
+ `;
1007
+ } else if (value === '') {
1008
+ formattedValue = '<span class="text-gray-400 dark:text-gray-500 italic">Empty</span>';
1009
+ } else {
1010
+ // Other values: Escape HTML and show as text
1011
+ formattedValue = String(value)
1012
+ .replace(/&/g, '&amp;')
1013
+ .replace(/</g, '&lt;')
1014
+ .replace(/>/g, '&gt;')
1015
+ .replace(/"/g, '&quot;');
1016
+ }
1017
+
1018
+ html += `
1019
+ <div class="grid grid-cols-[120px_1fr] gap-4 items-start">
1020
+ <label class="font-semibold text-sm text-gray-700 dark:text-gray-200 capitalize text-left">${fieldName}</label>
1021
+ <div class="text-sm text-gray-900 dark:text-gray-100">${formattedValue}</div>
1022
+ </div>
1023
+ `;
1024
+ displayedFields++;
1025
+ }
1026
+
1027
+ if (displayedFields === 0) {
1028
+ html += '<p class="text-sm text-gray-500 dark:text-gray-400 italic">No fields to display</p>';
1029
+ }
1030
+
1031
+ html += '</div>'; // Close space-y-3
1032
+ }
1033
+
1034
+ html += `
1035
+ </div>
1036
+ </div>
1037
+ `;
1038
+
1039
+ return html;
1040
+ }
1041
+
1042
+ /**
1043
+ * Helper: Render list component in detail view (for related entities)
1044
+ *
1045
+ * Shows related entities in a table format.
1046
+ * This matches the original DetailView's "list" component behavior.
1047
+ */
1048
+ private renderDetailListComponent(
1049
+ componentName: string,
1050
+ componentDef: any,
1051
+ modelData: Record<string, any[]>,
1052
+ selectedEntity: any,
1053
+ primaryModel: string | undefined,
1054
+ _tailwindAdapter: ReturnType<typeof createUniversalTailwindAdapter>
1055
+ ): string {
1056
+ const properties = componentDef.properties || componentDef;
1057
+ let fields = componentDef.fields || properties.fields || [];
1058
+ let relatedModel = properties.model;
1059
+
1060
+ // FALLBACK: Infer model from component name if not specified
1061
+ // E.g., "commentsList" -> "Comment", "postsList" -> "Post"
1062
+ if (!relatedModel) {
1063
+ const lowerName = componentName.toLowerCase();
1064
+ // Try to extract model name from component name
1065
+ for (const modelName of Object.keys(modelData)) {
1066
+ if (lowerName.includes(modelName.toLowerCase())) {
1067
+ relatedModel = modelName;
1068
+ break;
1069
+ }
1070
+ }
1071
+ }
1072
+
1073
+ // Get all entities for the related model
1074
+ const allRelatedEntities = relatedModel ? modelData[relatedModel] || [] : [];
1075
+
1076
+ // Filter by foreign key relationship
1077
+ // For example, if viewing Post (primaryModel), filter Comments by postId
1078
+ const foreignKey = primaryModel ? `${primaryModel.charAt(0).toLowerCase()}${primaryModel.slice(1)}Id` : null;
1079
+ const relatedEntities = foreignKey && selectedEntity?.id
1080
+ ? allRelatedEntities.filter((e: any) => e.data?.[foreignKey] === selectedEntity.id)
1081
+ : allRelatedEntities;
1082
+
1083
+ // If no fields specified, infer from first entity
1084
+ if (fields.length === 0 && relatedEntities.length > 0) {
1085
+ const firstEntity = relatedEntities[0];
1086
+ if (firstEntity.data) {
1087
+ fields = Object.keys(firstEntity.data)
1088
+ .filter(key => key !== 'id' && !key.endsWith('Id'))
1089
+ .slice(0, 5); // Limit to first 5 fields
1090
+ }
1091
+ }
1092
+
1093
+ let html = `
1094
+ <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
1095
+ <div class="px-4 py-2 bg-gray-50 dark:bg-gray-700 border-b border-gray-200 dark:border-gray-600">
1096
+ <h4 class="font-semibold text-sm text-gray-700 dark:text-gray-200 capitalize">${componentName}</h4>
1097
+ </div>
1098
+ <div class="p-4">
1099
+ `;
1100
+
1101
+ if (relatedEntities.length === 0) {
1102
+ html += `
1103
+ <p class="text-sm text-gray-500 dark:text-gray-400 italic">
1104
+ No ${relatedModel || 'related'} entities ${foreignKey ? `with ${foreignKey} = ${selectedEntity?.id}` : ''}
1105
+ </p>
1106
+ `;
1107
+ } else if (fields.length === 0) {
1108
+ html += `
1109
+ <p class="text-sm text-gray-500 dark:text-gray-400 italic">
1110
+ ${relatedEntities.length} ${relatedModel} entities found but no fields to display
1111
+ </p>
1112
+ `;
1113
+ } else {
1114
+ html += `
1115
+ <div class="overflow-auto max-h-96">
1116
+ <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
1117
+ <thead class="bg-gray-50 dark:bg-gray-700 sticky top-0">
1118
+ <tr>
1119
+ ${fields.map((fieldName: string) => `
1120
+ <th class="px-4 py-2 text-left text-xs font-semibold text-gray-700 dark:text-gray-200 uppercase tracking-wider">
1121
+ ${fieldName}
1122
+ </th>
1123
+ `).join('')}
1124
+ </tr>
1125
+ </thead>
1126
+ <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
1127
+ ${relatedEntities.map((entity: any) => `
1128
+ <tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
1129
+ ${fields.map((fieldName: string) => `
1130
+ <td class="px-4 py-2 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">
1131
+ ${entity.data?.[fieldName] ?? '—'}
1132
+ </td>
1133
+ `).join('')}
1134
+ </tr>
1135
+ `).join('')}
1136
+ </tbody>
1137
+ </table>
1138
+ </div>
1139
+ `;
1140
+ }
1141
+
1142
+ html += `
1143
+ </div>
1144
+ </div>
1145
+ `;
1146
+
1147
+ return html;
1148
+ }
1149
+
1150
+ /**
1151
+ * Helper: Render card component
1152
+ *
1153
+ * Displays entity fields with sophisticated formatting:
1154
+ * - Objects: Formatted JSON in code block
1155
+ * - Booleans: Yes/No badges
1156
+ * - Other values: Plain text
1157
+ */
1158
+ private renderCardComponent(
1159
+ componentName: string,
1160
+ _componentDef: any,
1161
+ selectedEntity: any,
1162
+ _tailwindAdapter: ReturnType<typeof createUniversalTailwindAdapter>
1163
+ ): string {
1164
+ const entityData = selectedEntity.data || {};
1165
+ const fields = Object.keys(entityData);
1166
+
1167
+ let html = `
1168
+ <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
1169
+ <div class="px-4 py-2 bg-gray-50 dark:bg-gray-700 border-b border-gray-200 dark:border-gray-600">
1170
+ <h4 class="font-semibold text-sm text-gray-700 dark:text-gray-200">${componentName}</h4>
1171
+ </div>
1172
+ <div class="p-4">
1173
+ <div class="space-y-3">
1174
+ ${fields.map(field => {
1175
+ const value = entityData[field];
1176
+
1177
+ // Skip undefined/null values
1178
+ if (value === undefined || value === null) return '';
1179
+
1180
+ // Format value based on type
1181
+ let formattedValue: string;
1182
+
1183
+ if (typeof value === 'object') {
1184
+ // Objects: Show as formatted JSON
1185
+ const jsonStr = JSON.stringify(value, null, 2)
1186
+ .replace(/&/g, '&amp;')
1187
+ .replace(/</g, '&lt;')
1188
+ .replace(/>/g, '&gt;')
1189
+ .replace(/"/g, '&quot;');
1190
+ formattedValue = `
1191
+ <pre class="bg-gray-50 dark:bg-gray-900 p-2 rounded border border-gray-200 dark:border-gray-600 text-xs overflow-auto text-gray-900 dark:text-gray-100">${jsonStr}</pre>
1192
+ `;
1193
+ } else if (typeof value === 'boolean') {
1194
+ // Booleans: Show as Yes/No badge
1195
+ formattedValue = `
1196
+ <span class="inline-block px-2 py-1 rounded text-xs font-medium ${
1197
+ value
1198
+ ? 'bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-200'
1199
+ : 'bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200'
1200
+ }">
1201
+ ${value ? 'Yes' : 'No'}
1202
+ </span>
1203
+ `;
1204
+ } else {
1205
+ // Other values: Escape HTML and show as text
1206
+ const escaped = String(value)
1207
+ .replace(/&/g, '&amp;')
1208
+ .replace(/</g, '&lt;')
1209
+ .replace(/>/g, '&gt;')
1210
+ .replace(/"/g, '&quot;');
1211
+ formattedValue = escaped || '—';
1212
+ }
1213
+
1214
+ return `
1215
+ <div class="grid grid-cols-[140px_1fr] gap-4 items-start">
1216
+ <label class="font-semibold text-sm text-gray-700 dark:text-gray-200 capitalize">${field}:</label>
1217
+ <div class="text-sm text-gray-900 dark:text-gray-100">${formattedValue}</div>
1218
+ </div>
1219
+ `;
1220
+ }).join('')}
1221
+ </div>
1222
+ </div>
1223
+ </div>
1224
+ `;
1225
+
1226
+ return html;
1227
+ }
1228
+
1229
+ /**
1230
+ * Helper: Render metric card
1231
+ */
1232
+ private renderMetricCard(
1233
+ componentName: string,
1234
+ componentDef: any,
1235
+ modelData: Record<string, any[]>,
1236
+ _tailwindAdapter: ReturnType<typeof createUniversalTailwindAdapter>
1237
+ ): string {
1238
+ const properties = componentDef.properties || componentDef;
1239
+ const metric = properties.metric;
1240
+ const metricModel = properties.model;
1241
+ const entities = modelData[metricModel] || [];
1242
+
1243
+ // Calculate metric value
1244
+ let metricValue = '0';
1245
+ if (entities.length > 0 && metric) {
1246
+ const values = entities
1247
+ .map((e: any) => e.data?.[metric])
1248
+ .filter((v: any) => v !== undefined && v !== null);
1249
+
1250
+ if (values.length > 0 && typeof values[0] === 'number') {
1251
+ const sum = values.reduce((acc: number, v: number) => acc + v, 0);
1252
+ metricValue = String(Math.round(sum));
1253
+ } else {
1254
+ metricValue = String(values.length);
1255
+ }
1256
+ }
1257
+
1258
+ return `
1259
+ <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-6">
1260
+ <p class="text-sm font-medium text-gray-600 dark:text-gray-400 uppercase tracking-wide">
1261
+ ${metric || componentName}
1262
+ </p>
1263
+ <p class="mt-2 text-3xl font-semibold text-gray-900 dark:text-gray-100">
1264
+ ${metricValue}
1265
+ </p>
1266
+ </div>
1267
+ `;
1268
+ }
1269
+
1270
+ /**
1271
+ * Helper: Render atomic component
1272
+ */
1273
+ private renderAtomicComponent(
1274
+ componentName: string,
1275
+ type: string,
1276
+ properties: Record<string, any>,
1277
+ _tailwindAdapter: ReturnType<typeof createUniversalTailwindAdapter>
1278
+ ): string {
1279
+ if (!this.tailwindAdapter.components[type]) {
1280
+ return `<div class="p-4 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-700 rounded">
1281
+ <p class="text-red-800 dark:text-red-200">Unknown component type: ${type}</p>
1282
+ </div>`;
1283
+ }
1284
+
1285
+ return `
1286
+ <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
1287
+ <div class="px-4 py-2 bg-gray-50 dark:bg-gray-700 border-b border-gray-200 dark:border-gray-600">
1288
+ <h4 class="font-semibold text-sm text-gray-700 dark:text-gray-200">${componentName}</h4>
1289
+ </div>
1290
+ <div class="p-4">
1291
+ ${this.tailwindAdapter.components[type].render({ properties })}
1292
+ </div>
1293
+ </div>
1294
+ `;
1295
+ }
1296
+
1297
+ /**
1298
+ * Helper: Infer field names from model data
1299
+ *
1300
+ * Extracts field names from the first entity in modelData for the given model.
1301
+ * Filters out system fields like id, createdAt, updatedAt, and foreign key fields.
1302
+ *
1303
+ * @param modelData - Map of model names to entity arrays
1304
+ * @param modelName - Name of the model to extract fields from
1305
+ * @returns Array of field names suitable for display
1306
+ */
1307
+ private inferFieldsFromModel(modelData: Record<string, any[]>, modelName: string | undefined): string[] {
1308
+ if (!modelName || !modelData[modelName]) {
1309
+ // No model data - return common default fields
1310
+ return ['name', 'title', 'description'];
1311
+ }
1312
+
1313
+ const entities = modelData[modelName];
1314
+ if (entities.length === 0) {
1315
+ // No entities yet - return common default fields for this model
1316
+ return ['name', 'title', 'description'];
1317
+ }
1318
+
1319
+ const firstEntity = entities[0];
1320
+ if (!firstEntity || !firstEntity.data) {
1321
+ return ['name', 'title', 'description'];
1322
+ }
1323
+
1324
+ // Get field names from entity data, filter out system fields
1325
+ const fields = Object.keys(firstEntity.data).filter(field =>
1326
+ field !== 'id' &&
1327
+ !field.endsWith('Id') &&
1328
+ field !== 'createdAt' &&
1329
+ field !== 'updatedAt'
1330
+ );
1331
+
1332
+ // If after filtering we have no fields, return common defaults
1333
+ return fields.length > 0 ? fields : ['name', 'title', 'description'];
1334
+ }
1335
+ }
1336
+
1337
+ /**
1338
+ * Hook: Use pattern adapter
1339
+ */
1340
+ export function usePatternAdapter(config: ReactPatternAdapterConfig = {}) {
1341
+ return useMemo(() => new ReactPatternAdapter(config), [config]);
1342
+ }
1343
+
1344
+ /**
1345
+ * Export pattern registry for external use
1346
+ */
1347
+ export { COMPOSITE_VIEW_PATTERNS, ATOMIC_COMPONENTS_REGISTRY };