@specverse/engines 4.1.28 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/assets/examples/manifests/frontend-only.yaml +3 -6
  2. package/assets/examples/manifests/fullstack-app.yaml +5 -7
  3. package/assets/examples/manifests/fullstack-monorepo.yaml +3 -6
  4. package/dist/inference/comprehensive-engine.d.ts.map +1 -1
  5. package/dist/inference/comprehensive-engine.js +3 -19
  6. package/dist/inference/comprehensive-engine.js.map +1 -1
  7. package/dist/inference/core/rule-engine.d.ts +31 -0
  8. package/dist/inference/core/rule-engine.d.ts.map +1 -1
  9. package/dist/inference/core/rule-engine.js +117 -33
  10. package/dist/inference/core/rule-engine.js.map +1 -1
  11. package/dist/inference/core/rule-file-types.d.ts +0 -2
  12. package/dist/inference/core/rule-file-types.d.ts.map +1 -1
  13. package/dist/inference/core/rule-file-types.js +3 -6
  14. package/dist/inference/core/rule-file-types.js.map +1 -1
  15. package/dist/inference/core/rule-loader.d.ts +5 -15
  16. package/dist/inference/core/rule-loader.d.ts.map +1 -1
  17. package/dist/inference/core/rule-loader.js +43 -132
  18. package/dist/inference/core/rule-loader.js.map +1 -1
  19. package/dist/inference/core/types.d.ts +0 -6
  20. package/dist/inference/core/types.d.ts.map +1 -1
  21. package/dist/inference/core/types.js +0 -4
  22. package/dist/inference/core/types.js.map +1 -1
  23. package/dist/inference/logical/generators/component-type-resolver.d.ts +0 -26
  24. package/dist/inference/logical/generators/component-type-resolver.d.ts.map +1 -1
  25. package/dist/inference/logical/generators/component-type-resolver.js +0 -19
  26. package/dist/inference/logical/generators/component-type-resolver.js.map +1 -1
  27. package/dist/inference/logical/generators/specialist-view-expander.d.ts +1 -17
  28. package/dist/inference/logical/generators/specialist-view-expander.d.ts.map +1 -1
  29. package/dist/inference/logical/generators/specialist-view-expander.js +0 -15
  30. package/dist/inference/logical/generators/specialist-view-expander.js.map +1 -1
  31. package/dist/inference/logical/generators/view-generator.d.ts +4 -14
  32. package/dist/inference/logical/generators/view-generator.d.ts.map +1 -1
  33. package/dist/inference/logical/generators/view-generator.js +6 -26
  34. package/dist/inference/logical/generators/view-generator.js.map +1 -1
  35. package/dist/inference/logical/index.d.ts +2 -2
  36. package/dist/inference/logical/index.d.ts.map +1 -1
  37. package/dist/inference/logical/logical-engine.d.ts.map +1 -1
  38. package/dist/inference/logical/logical-engine.js +17 -80
  39. package/dist/inference/logical/logical-engine.js.map +1 -1
  40. package/dist/inference/quint-transpiler.d.ts +5 -3
  41. package/dist/inference/quint-transpiler.d.ts.map +1 -1
  42. package/dist/inference/quint-transpiler.js +11 -6
  43. package/dist/inference/quint-transpiler.js.map +1 -1
  44. package/dist/libs/instance-factories/applications/templates/generic/main-generator.js +3 -3
  45. package/dist/libs/instance-factories/applications/templates/react/api-client-generator.js +16 -6
  46. package/dist/libs/instance-factories/applications/templates/react-starter/app-tsx-generator.js +110 -0
  47. package/dist/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.js +121 -0
  48. package/dist/libs/instance-factories/applications/templates/react-starter/detail-body-composer.js +78 -0
  49. package/dist/libs/instance-factories/applications/templates/react-starter/form-body-composer.js +190 -0
  50. package/dist/libs/instance-factories/applications/templates/react-starter/helpers-emitter.js +45 -0
  51. package/dist/libs/instance-factories/applications/templates/react-starter/html-to-jsx.js +192 -0
  52. package/dist/libs/instance-factories/applications/templates/react-starter/list-body-composer.js +46 -0
  53. package/dist/libs/instance-factories/applications/templates/react-starter/orchestrator.js +30 -0
  54. package/dist/libs/instance-factories/applications/templates/react-starter/package-json-generator.js +38 -0
  55. package/dist/libs/instance-factories/applications/templates/react-starter/regen-safety.js +89 -0
  56. package/dist/libs/instance-factories/applications/templates/react-starter/view-emitter.js +56 -0
  57. package/dist/libs/instance-factories/applications/templates/react-starter/views-generator.js +66 -0
  58. package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +14 -11
  59. package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +11 -3
  60. package/dist/libs/instance-factories/services/templates/prisma/controller-generator.js +27 -17
  61. package/dist/libs/instance-factories/shared/path-resolver.js +1 -1
  62. package/dist/realize/index.d.ts.map +1 -1
  63. package/dist/realize/index.js +15 -22
  64. package/dist/realize/index.js.map +1 -1
  65. package/dist/registry/utils/manifest-adapter.d.ts +8 -1
  66. package/dist/registry/utils/manifest-adapter.d.ts.map +1 -1
  67. package/dist/registry/utils/manifest-adapter.js +8 -1
  68. package/dist/registry/utils/manifest-adapter.js.map +1 -1
  69. package/libs/instance-factories/applications/react-app-starter.yaml +150 -0
  70. package/libs/instance-factories/applications/templates/generic/main-generator.ts +3 -3
  71. package/libs/instance-factories/applications/templates/react/api-client-generator.ts +16 -6
  72. package/libs/instance-factories/applications/templates/react-starter/README.md +211 -0
  73. package/libs/instance-factories/applications/templates/react-starter/__tests__/dashboard-body-composer.test.ts +153 -0
  74. package/libs/instance-factories/applications/templates/react-starter/__tests__/detail-body-composer.test.ts +145 -0
  75. package/libs/instance-factories/applications/templates/react-starter/__tests__/form-body-composer.test.ts +175 -0
  76. package/libs/instance-factories/applications/templates/react-starter/__tests__/helpers-emitter.test.ts +55 -0
  77. package/libs/instance-factories/applications/templates/react-starter/__tests__/html-to-jsx.test.ts +140 -0
  78. package/libs/instance-factories/applications/templates/react-starter/__tests__/list-body-composer.test.ts +146 -0
  79. package/libs/instance-factories/applications/templates/react-starter/__tests__/orchestrator.test.ts +163 -0
  80. package/libs/instance-factories/applications/templates/react-starter/__tests__/parity-p2-factory-imports.test.ts +116 -0
  81. package/libs/instance-factories/applications/templates/react-starter/__tests__/parity-p3-rendered-output.test.ts +183 -0
  82. package/libs/instance-factories/applications/templates/react-starter/__tests__/regen-safety.test.ts +144 -0
  83. package/libs/instance-factories/applications/templates/react-starter/__tests__/starter-generators.test.ts +114 -0
  84. package/libs/instance-factories/applications/templates/react-starter/__tests__/view-emitter.test.ts +107 -0
  85. package/libs/instance-factories/applications/templates/react-starter/__tests__/views-generator.test.ts +139 -0
  86. package/libs/instance-factories/applications/templates/react-starter/app-tsx-generator.ts +141 -0
  87. package/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.ts +174 -0
  88. package/libs/instance-factories/applications/templates/react-starter/detail-body-composer.ts +135 -0
  89. package/libs/instance-factories/applications/templates/react-starter/form-body-composer.ts +306 -0
  90. package/libs/instance-factories/applications/templates/react-starter/helpers-emitter.ts +60 -0
  91. package/libs/instance-factories/applications/templates/react-starter/html-to-jsx.ts +334 -0
  92. package/libs/instance-factories/applications/templates/react-starter/list-body-composer.ts +120 -0
  93. package/libs/instance-factories/applications/templates/react-starter/orchestrator.ts +80 -0
  94. package/libs/instance-factories/applications/templates/react-starter/package-json-generator.ts +57 -0
  95. package/libs/instance-factories/applications/templates/react-starter/regen-safety.ts +157 -0
  96. package/libs/instance-factories/applications/templates/react-starter/skeletons/dashboard.tsx.template +47 -0
  97. package/libs/instance-factories/applications/templates/react-starter/skeletons/detail.tsx.template +94 -0
  98. package/libs/instance-factories/applications/templates/react-starter/skeletons/form.tsx.template +114 -0
  99. package/libs/instance-factories/applications/templates/react-starter/skeletons/list.tsx.template +72 -0
  100. package/libs/instance-factories/applications/templates/react-starter/view-emitter.ts +151 -0
  101. package/libs/instance-factories/applications/templates/react-starter/views-generator.ts +137 -0
  102. package/libs/instance-factories/cli/templates/commander/command-generator.ts +14 -11
  103. package/libs/instance-factories/controllers/templates/fastify/server-generator.ts +11 -3
  104. package/libs/instance-factories/services/templates/prisma/controller-generator.ts +27 -17
  105. package/libs/instance-factories/shared/path-resolver.ts +8 -2
  106. package/package.json +3 -3
  107. package/dist/libs/instance-factories/applications/templates/react/_view-components-source.js +0 -530
  108. package/dist/libs/instance-factories/applications/templates/react/app-tsx-generator.js +0 -73
  109. package/dist/libs/instance-factories/applications/templates/react/field-helpers-generator.js +0 -99
  110. package/dist/libs/instance-factories/applications/templates/react/package-json-generator.js +0 -49
  111. package/dist/libs/instance-factories/applications/templates/react/pattern-adapter-generator.js +0 -156
  112. package/dist/libs/instance-factories/applications/templates/react/react-pattern-adapter.js +0 -935
  113. package/dist/libs/instance-factories/applications/templates/react/relationship-field-generator.js +0 -143
  114. package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-generator.js +0 -646
  115. package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-wrapper-generator.js +0 -65
  116. package/dist/libs/instance-factories/applications/templates/react/view-dashboard-generator.js +0 -143
  117. package/dist/libs/instance-factories/applications/templates/react/view-detail-generator.js +0 -143
  118. package/dist/libs/instance-factories/applications/templates/react/view-form-generator.js +0 -355
  119. package/dist/libs/instance-factories/applications/templates/react/view-list-generator.js +0 -91
  120. package/dist/libs/instance-factories/applications/templates/react/view-router-generator.js +0 -79
  121. package/dist/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.js.bak +0 -244
  122. package/dist/libs/instance-factories/views/index.js +0 -48
  123. package/dist/libs/instance-factories/views/templates/react/adapters/antd-adapter.js +0 -742
  124. package/dist/libs/instance-factories/views/templates/react/adapters/mui-adapter.js +0 -824
  125. package/dist/libs/instance-factories/views/templates/react/adapters/shadcn-adapter.js +0 -719
  126. package/dist/libs/instance-factories/views/templates/react/app-generator.js +0 -45
  127. package/dist/libs/instance-factories/views/templates/react/components-generator.js +0 -820
  128. package/dist/libs/instance-factories/views/templates/react/forms-generator.js +0 -275
  129. package/dist/libs/instance-factories/views/templates/react/frontend-package-json-generator.js +0 -46
  130. package/dist/libs/instance-factories/views/templates/react/hooks-generator.js +0 -81
  131. package/dist/libs/instance-factories/views/templates/react/index-css-generator.js +0 -9
  132. package/dist/libs/instance-factories/views/templates/react/index-html-generator.js +0 -23
  133. package/dist/libs/instance-factories/views/templates/react/main-tsx-generator.js +0 -21
  134. package/dist/libs/instance-factories/views/templates/react/react-component-generator.js +0 -299
  135. package/dist/libs/instance-factories/views/templates/react/router-generator.js +0 -136
  136. package/dist/libs/instance-factories/views/templates/react/router-generic-generator.js +0 -107
  137. package/dist/libs/instance-factories/views/templates/react/shared-utils-generator.js +0 -187
  138. package/dist/libs/instance-factories/views/templates/react/spec-json-generator.js +0 -7
  139. package/dist/libs/instance-factories/views/templates/react/types-generator.js +0 -56
  140. package/dist/libs/instance-factories/views/templates/react/views-metadata-generator.js +0 -27
  141. package/dist/libs/instance-factories/views/templates/react/vite-config-generator.js +0 -29
  142. package/dist/libs/instance-factories/views/templates/runtime/runtime-view-renderer.js +0 -261
  143. package/dist/libs/instance-factories/views/templates/shared/adapter-types.js +0 -34
  144. package/dist/libs/instance-factories/views/templates/shared/atomic-components-registry.js +0 -800
  145. package/dist/libs/instance-factories/views/templates/shared/base-generator.js +0 -305
  146. package/dist/libs/instance-factories/views/templates/shared/component-metadata.js +0 -517
  147. package/dist/libs/instance-factories/views/templates/shared/composite-pattern-types.js +0 -0
  148. package/dist/libs/instance-factories/views/templates/shared/composite-patterns.js +0 -445
  149. package/dist/libs/instance-factories/views/templates/shared/index.js +0 -80
  150. package/dist/libs/instance-factories/views/templates/shared/pattern-validator.js +0 -210
  151. package/dist/libs/instance-factories/views/templates/shared/property-mapper.js +0 -492
  152. package/dist/libs/instance-factories/views/templates/shared/syntax-mapper.js +0 -321
  153. package/dist/realize/index.js.bak +0 -758
  154. package/libs/instance-factories/applications/react-app.yaml +0 -186
  155. package/libs/instance-factories/applications/templates/react/_view-components-source.ts +0 -555
  156. package/libs/instance-factories/applications/templates/react/app-tsx-generator.ts +0 -94
  157. package/libs/instance-factories/applications/templates/react/field-helpers-generator.ts +0 -106
  158. package/libs/instance-factories/applications/templates/react/package-json-generator.ts +0 -57
  159. package/libs/instance-factories/applications/templates/react/pattern-adapter-generator.ts +0 -179
  160. package/libs/instance-factories/applications/templates/react/react-pattern-adapter.tsx +0 -1347
  161. package/libs/instance-factories/applications/templates/react/relationship-field-generator.ts +0 -150
  162. package/libs/instance-factories/applications/templates/react/tailwind-adapter-generator.ts +0 -704
  163. package/libs/instance-factories/applications/templates/react/tailwind-adapter-wrapper-generator.ts +0 -84
  164. package/libs/instance-factories/applications/templates/react/view-dashboard-generator.ts +0 -150
  165. package/libs/instance-factories/applications/templates/react/view-detail-generator.ts +0 -150
  166. package/libs/instance-factories/applications/templates/react/view-form-generator.ts +0 -362
  167. package/libs/instance-factories/applications/templates/react/view-list-generator.ts +0 -98
  168. package/libs/instance-factories/applications/templates/react/view-router-generator.ts +0 -89
  169. package/libs/instance-factories/views/README.md +0 -62
  170. package/libs/instance-factories/views/index.d.ts +0 -13
  171. package/libs/instance-factories/views/index.d.ts.map +0 -1
  172. package/libs/instance-factories/views/index.js +0 -18
  173. package/libs/instance-factories/views/index.js.map +0 -1
  174. package/libs/instance-factories/views/index.ts +0 -45
  175. package/libs/instance-factories/views/react-components.yaml +0 -129
  176. package/libs/instance-factories/views/templates/ARCHITECTURE.md +0 -198
  177. package/libs/instance-factories/views/templates/react/adapters/antd-adapter.ts +0 -869
  178. package/libs/instance-factories/views/templates/react/adapters/mui-adapter.ts +0 -953
  179. package/libs/instance-factories/views/templates/react/adapters/shadcn-adapter.ts +0 -806
  180. package/libs/instance-factories/views/templates/react/app-generator.ts +0 -55
  181. package/libs/instance-factories/views/templates/react/components-generator.ts +0 -938
  182. package/libs/instance-factories/views/templates/react/forms-generator.ts +0 -325
  183. package/libs/instance-factories/views/templates/react/frontend-package-json-generator.ts +0 -57
  184. package/libs/instance-factories/views/templates/react/hooks-generator.ts +0 -106
  185. package/libs/instance-factories/views/templates/react/index-css-generator.ts +0 -14
  186. package/libs/instance-factories/views/templates/react/index-html-generator.ts +0 -34
  187. package/libs/instance-factories/views/templates/react/main-tsx-generator.ts +0 -29
  188. package/libs/instance-factories/views/templates/react/react-component-generator.d.ts +0 -152
  189. package/libs/instance-factories/views/templates/react/react-component-generator.d.ts.map +0 -1
  190. package/libs/instance-factories/views/templates/react/react-component-generator.js +0 -398
  191. package/libs/instance-factories/views/templates/react/react-component-generator.js.map +0 -1
  192. package/libs/instance-factories/views/templates/react/react-component-generator.ts +0 -533
  193. package/libs/instance-factories/views/templates/react/router-generator.ts +0 -197
  194. package/libs/instance-factories/views/templates/react/router-generic-generator.ts +0 -132
  195. package/libs/instance-factories/views/templates/react/shared-utils-generator.ts +0 -196
  196. package/libs/instance-factories/views/templates/react/spec-json-generator.ts +0 -17
  197. package/libs/instance-factories/views/templates/react/types-generator.ts +0 -76
  198. package/libs/instance-factories/views/templates/react/views-metadata-generator.ts +0 -42
  199. package/libs/instance-factories/views/templates/react/vite-config-generator.ts +0 -38
  200. package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.d.ts.map +0 -1
  201. package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.js.map +0 -1
  202. package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.ts +0 -474
  203. package/libs/instance-factories/views/templates/shared/__tests__/composite-patterns.test.ts +0 -242
  204. package/libs/instance-factories/views/templates/shared/adapter-types.d.ts +0 -77
  205. package/libs/instance-factories/views/templates/shared/adapter-types.d.ts.map +0 -1
  206. package/libs/instance-factories/views/templates/shared/adapter-types.js +0 -47
  207. package/libs/instance-factories/views/templates/shared/adapter-types.js.map +0 -1
  208. package/libs/instance-factories/views/templates/shared/adapter-types.ts +0 -142
  209. package/libs/instance-factories/views/templates/shared/atomic-components-registry.d.ts +0 -63
  210. package/libs/instance-factories/views/templates/shared/atomic-components-registry.d.ts.map +0 -1
  211. package/libs/instance-factories/views/templates/shared/atomic-components-registry.js +0 -822
  212. package/libs/instance-factories/views/templates/shared/atomic-components-registry.js.map +0 -1
  213. package/libs/instance-factories/views/templates/shared/atomic-components-registry.ts +0 -908
  214. package/libs/instance-factories/views/templates/shared/base-generator.d.ts +0 -247
  215. package/libs/instance-factories/views/templates/shared/base-generator.d.ts.map +0 -1
  216. package/libs/instance-factories/views/templates/shared/base-generator.js +0 -363
  217. package/libs/instance-factories/views/templates/shared/base-generator.js.map +0 -1
  218. package/libs/instance-factories/views/templates/shared/base-generator.ts +0 -608
  219. package/libs/instance-factories/views/templates/shared/component-metadata.d.ts +0 -254
  220. package/libs/instance-factories/views/templates/shared/component-metadata.d.ts.map +0 -1
  221. package/libs/instance-factories/views/templates/shared/component-metadata.js +0 -602
  222. package/libs/instance-factories/views/templates/shared/component-metadata.js.map +0 -1
  223. package/libs/instance-factories/views/templates/shared/component-metadata.ts +0 -803
  224. package/libs/instance-factories/views/templates/shared/composite-pattern-types.ts +0 -250
  225. package/libs/instance-factories/views/templates/shared/composite-patterns.ts +0 -535
  226. package/libs/instance-factories/views/templates/shared/index.ts +0 -68
  227. package/libs/instance-factories/views/templates/shared/pattern-validator.ts +0 -279
  228. package/libs/instance-factories/views/templates/shared/property-mapper.d.ts +0 -149
  229. package/libs/instance-factories/views/templates/shared/property-mapper.d.ts.map +0 -1
  230. package/libs/instance-factories/views/templates/shared/property-mapper.js +0 -580
  231. package/libs/instance-factories/views/templates/shared/property-mapper.js.map +0 -1
  232. package/libs/instance-factories/views/templates/shared/property-mapper.ts +0 -700
  233. package/libs/instance-factories/views/templates/shared/syntax-mapper.d.ts +0 -143
  234. package/libs/instance-factories/views/templates/shared/syntax-mapper.d.ts.map +0 -1
  235. package/libs/instance-factories/views/templates/shared/syntax-mapper.js +0 -420
  236. package/libs/instance-factories/views/templates/shared/syntax-mapper.js.map +0 -1
  237. package/libs/instance-factories/views/templates/shared/syntax-mapper.ts +0 -539
@@ -0,0 +1,150 @@
1
+ name: ReactAppStarter
2
+ version: "1.0.0"
3
+ category: view
4
+ description: "Fully standalone React starter kit — pre-rendered view components the user can fork and extend. No @specverse/runtime dependency."
5
+
6
+ metadata:
7
+ author: "SpecVerse Team"
8
+ license: "MIT"
9
+ tags: [react, vite, spa, frontend, starter, editable]
10
+ mode: starter
11
+
12
+ compatibility:
13
+ specverse: ">=4.0.0"
14
+ node: ">=18.0.0"
15
+
16
+ capabilities:
17
+ provides:
18
+ - "app.frontend"
19
+ - "ui.scaffolding"
20
+ - "ui.router"
21
+ - "ui.static-views"
22
+ - "ui.static-forms"
23
+ - "ui.static-components"
24
+ requires: []
25
+
26
+ technology:
27
+ runtime: "browser"
28
+ language: "typescript"
29
+ framework: "react"
30
+ bundler: "vite"
31
+ version: "^18.2.0"
32
+
33
+ dependencies:
34
+ # Runtime deps baked into the generated package.json.
35
+ # Deliberately no @specverse/runtime — the starter kit is
36
+ # standalone by design.
37
+ runtime:
38
+ - name: "react"
39
+ version: "^18.2.0"
40
+ - name: "react-dom"
41
+ version: "^18.2.0"
42
+ - name: "@tanstack/react-query"
43
+ version: "^5.0.0"
44
+
45
+ dev:
46
+ - name: "@types/react"
47
+ version: "^18.2.0"
48
+ - name: "@types/react-dom"
49
+ version: "^18.2.0"
50
+ - name: "typescript"
51
+ version: "^5.2.0"
52
+ - name: "vite"
53
+ version: "^6.0.0"
54
+ - name: "@vitejs/plugin-react"
55
+ version: "^4.2.0"
56
+ - name: "tailwindcss"
57
+ version: "^3.4.13"
58
+ - name: "postcss"
59
+ version: "^8.4.47"
60
+ - name: "autoprefixer"
61
+ version: "^10.4.20"
62
+
63
+ codeTemplates:
64
+ # === Infrastructure reused from ReactAppRuntime ===
65
+ # Safe to share because these files don't embed any runtime-specific
66
+ # logic — just build config + entry points.
67
+ index-html:
68
+ engine: typescript
69
+ generator: "libs/instance-factories/applications/templates/react/index-html-generator.ts"
70
+ outputPattern: "{frontendDir}/index.html"
71
+
72
+ main-tsx:
73
+ engine: typescript
74
+ generator: "libs/instance-factories/applications/templates/react/main-tsx-generator.ts"
75
+ outputPattern: "{frontendDir}/src/main.tsx"
76
+
77
+ vite-config:
78
+ engine: typescript
79
+ generator: "libs/instance-factories/applications/templates/react/vite-config-generator.ts"
80
+ outputPattern: "{frontendDir}/vite.config.ts"
81
+
82
+ tsconfig:
83
+ engine: typescript
84
+ generator: "libs/instance-factories/applications/templates/react/tsconfig-generator.ts"
85
+ outputPattern: "{frontendDir}/tsconfig.json"
86
+
87
+ index-css:
88
+ engine: typescript
89
+ generator: "libs/instance-factories/applications/templates/react/index-css-generator.ts"
90
+ outputPattern: "{frontendDir}/src/index.css"
91
+
92
+ gitignore:
93
+ engine: typescript
94
+ generator: "libs/instance-factories/applications/templates/react/gitignore-generator.ts"
95
+ outputPattern: "{frontendDir}/.gitignore"
96
+
97
+ env-example:
98
+ engine: typescript
99
+ generator: "libs/instance-factories/applications/templates/react/env-example-generator.ts"
100
+ outputPattern: "{frontendDir}/.env.example"
101
+
102
+ # API wiring — generated per project (endpoint paths / fetch client
103
+ # are instance-factory specific).
104
+ api-client:
105
+ engine: typescript
106
+ generator: "libs/instance-factories/applications/templates/react/api-client-generator.ts"
107
+ outputPattern: "{frontendDir}/src/lib/apiClient.ts"
108
+
109
+ use-api-hooks:
110
+ engine: typescript
111
+ generator: "libs/instance-factories/applications/templates/react/use-api-hooks-generator.ts"
112
+ outputPattern: "{frontendDir}/src/hooks/useApi.ts"
113
+
114
+ api-types:
115
+ engine: typescript
116
+ generator: "libs/instance-factories/applications/templates/react/api-types-generator.ts"
117
+ outputPattern: "{frontendDir}/src/types/api.ts"
118
+
119
+ # === Starter-specific: emitted view components + helpers + App + package.json ===
120
+ # The orchestrator returns a map of {relativePath: content} covering:
121
+ # src/views/*.tsx one per (model, view-type) pair
122
+ # src/lib/entity-display.ts FK→name helper (standalone source)
123
+ # src/App.tsx simple internal-routing shell
124
+ # package.json no @specverse/runtime dep
125
+ # .specverse-gen/hashes.json content hashes for regeneration safety
126
+ #
127
+ # Regeneration safety is internal to the orchestrator: user-edited
128
+ # files are omitted from the returned map (so realize never sees
129
+ # them), and the hash manifest's per-file entries are preserved for
130
+ # those skipped files so a later un-edit restores regenerability.
131
+ starter-output:
132
+ engine: typescript
133
+ generator: "libs/instance-factories/applications/templates/react-starter/orchestrator.ts"
134
+ outputPattern: "{frontendDir}/"
135
+
136
+ configuration:
137
+ projectStructure: "monorepo"
138
+ frontendDir: "frontend"
139
+ backendDir: "backend"
140
+
141
+ vite:
142
+ port: 5173
143
+ host: "localhost"
144
+ proxy:
145
+ "/api": "http://localhost:3000"
146
+
147
+ notes:
148
+ - "Generated code is a starter — user edits are preserved across regeneration via content-hashing in .specverse-gen/hashes.json."
149
+ - "To accept an upstream regeneration for a file you've edited: delete the file first, then re-run `spv realize`."
150
+ - "No @specverse/runtime dep — the generated project is fully forkable; delete the .specverse-gen directory and rely only on the emitted source."
@@ -154,7 +154,7 @@ ${serviceMap}
154
154
  ${routeRegistrations}
155
155
 
156
156
  const preferredPort = process.env.PORT ? parseInt(process.env.PORT) : 3000;
157
- const host = process.env.HOST || '0.0.0.0';
157
+ const host = process.env.HOST || '::';
158
158
 
159
159
  // Try to start server with automatic port fallback
160
160
  let port = preferredPort;
@@ -238,7 +238,7 @@ ${routeRegistrations}
238
238
 
239
239
  async function start() {
240
240
  const preferredPort = process.env.PORT ? parseInt(process.env.PORT) : 3000;
241
- const host = process.env.HOST || '0.0.0.0';
241
+ const host = process.env.HOST || '::';
242
242
  const maxAttempts = 10;
243
243
 
244
244
  // Try to start server with automatic port fallback
@@ -292,7 +292,7 @@ async function bootstrap() {
292
292
  });
293
293
 
294
294
  const preferredPort = process.env.PORT ? parseInt(process.env.PORT) : 3000;
295
- const host = process.env.HOST || '0.0.0.0';
295
+ const host = process.env.HOST || '::';
296
296
  const maxAttempts = 10;
297
297
 
298
298
  // Try to start server with automatic port fallback
@@ -360,7 +360,14 @@ export async function executeServiceOperation(
360
360
  }
361
361
 
362
362
  /**
363
- * Transition entity lifecycle state
363
+ * Transition entity lifecycle state.
364
+ *
365
+ * Calls the realized backend's CURVED evolve endpoint
366
+ * (PATCH /api/{resource}/:id/evolve) — matches what
367
+ * engines/libs/instance-factories/controllers/templates/fastify/routes-generator.ts
368
+ * emits and what scripts/smoke-parity.sh exercises. The older
369
+ * /transition URL this function used to call never existed on the
370
+ * realized backend.
364
371
  */
365
372
  export async function transitionState(
366
373
  modelName: string,
@@ -375,8 +382,8 @@ export async function transitionState(
375
382
 
376
383
  const resource = modelName.toLowerCase() + 's';
377
384
  return apiRequest<ApiResponse>(
378
- 'POST',
379
- \`/\${resource}/\${entityId}/transition\`,
385
+ 'PATCH',
386
+ \`/\${resource}/\${entityId}/evolve\`,
380
387
  body
381
388
  );
382
389
  }
@@ -468,7 +475,9 @@ export async function executeServiceOperation(
468
475
  }
469
476
 
470
477
  /**
471
- * Transition entity lifecycle state
478
+ * Transition entity lifecycle state — realized-backend variant
479
+ * (used by templates that pre-date the unified CURVED evolve path).
480
+ * Matches PATCH /api/{resource}/:id/evolve.
472
481
  */
473
482
  export async function transitionState(
474
483
  modelName: string,
@@ -481,9 +490,10 @@ export async function transitionState(
481
490
  body.lifecycleName = lifecycleName;
482
491
  }
483
492
 
493
+ const resource = modelName.toLowerCase() + 's';
484
494
  return apiRequest<ApiResponse>(
485
- 'POST',
486
- \`/lifecycle/\${modelName}/\${entityId}/transition\`,
495
+ 'PATCH',
496
+ \`/\${resource}/\${entityId}/evolve\`,
487
497
  body
488
498
  );
489
499
  }
@@ -0,0 +1,211 @@
1
+ # ReactAppStarter — Emitter Architecture
2
+
3
+ **Audience**: anyone touching the view emitter for Factory B (the "starter-kit" React factory).
4
+
5
+ **Companion docs**:
6
+ - `specverse-self/docs/guides/VIEW-RENDERING-ARCHITECTURE.md` (the "one pattern library, three consumers" design)
7
+ - `specverse-self/docs/plans/2026-04-17-UNIFIED-VIEW-RENDERING.md` (the migration plan)
8
+
9
+ ---
10
+
11
+ ## The problem this solves
12
+
13
+ Factory B emits a standalone React codebase. For every view in the inferred spec (dev defaults + user-defined views), it writes a `.tsx` file the user can read, fork, and edit. The generated code:
14
+
15
+ - Has no `@specverse/runtime` dependency (fully standalone).
16
+ - Is idiomatic React with typed props, hooks, event handlers — not a big static HTML blob.
17
+ - Renders the **same** visual output as `@specverse/runtime` renders for the same spec (verified by Phase 3 parity test P3).
18
+
19
+ The tension: "idiomatic React structure" wants hand-crafted JSX. "Same visual output as runtime" wants a single rendering authority. Strategy 3 resolves this by splitting *structure* (hand-written skeleton templates) from *interior* (canonical Tailwind adapter rendering).
20
+
21
+ ## The three pieces
22
+
23
+ ```
24
+ ┌─────────────────────────────────────────────────────────────┐
25
+ │ 1. view-emitter.ts — orchestrator │
26
+ │ Takes (viewSpec, modelSpec, expandedSpec) → .tsx string │
27
+ └──────────────┬───────────────────────────────┬──────────────┘
28
+ │ │
29
+ ▼ ▼
30
+ ┌──────────────────────────────┐ ┌─────────────────────────────────┐
31
+ │ 2. skeletons/*.tsx.template │ │ 3. html-to-jsx.ts — transformer│
32
+ │ One per view type: │ │ │
33
+ │ list.tsx.template │ │ Converts Tailwind-HTML │
34
+ │ detail.tsx.template │ │ strings from the runtime │
35
+ │ form.tsx.template │ │ adapter into JSX-safe source │
36
+ │ dashboard.tsx.template │ │ (class→className, self-close,│
37
+ │ + specialist types │ │ brace escape, attribute map) │
38
+ │ │ │ │
39
+ │ Each has a {{BODY}} │ │ ~50-150 LoC, bounded scope │
40
+ │ placeholder the emitter │ │ │
41
+ │ fills with transformed │ │ │
42
+ │ JSX. │ │ │
43
+ └──────────────────────────────┘ └─────────────────────────────────┘
44
+
45
+
46
+ │ renders body as HTML
47
+
48
+ ┌─────────────────────┴──────────────────────┐
49
+ │ @specverse/runtime/views/tailwind │
50
+ │ createUniversalTailwindAdapter() │
51
+ │ (the canonical build-time renderer — │
52
+ │ same code app-demo and Factory A │
53
+ │ transitively rely on at runtime) │
54
+ └────────────────────────────────────────────┘
55
+ ```
56
+
57
+ ## Data flow for ONE view
58
+
59
+ Given an inferred `PostListView` in the expanded spec:
60
+
61
+ 1. **view-emitter.ts** receives `(view, model, expandedSpec)`.
62
+ 2. Picks the correct skeleton based on `view.type`: `skeletons/list.tsx.template`.
63
+ 3. Builds a render context for the pattern: `{ primaryModel: 'Post', modelSchemas: {...}, viewSpec: view, modelData: [] }` (empty data — structure only).
64
+ 4. Calls `createUniversalTailwindAdapter()` and renders the composite pattern's interior → HTML string.
65
+ Example output: `<table class="w-full ..."><thead><tr><th class="...">Title</th>...`.
66
+ 5. Passes that HTML through **html-to-jsx.ts** → JSX-safe source.
67
+ Example: `<table className="w-full ..."><thead><tr><th className="...">{/* data.map */}Title</th>...`.
68
+ 6. Substitutes `{{BODY}}` in the skeleton with the transformed JSX.
69
+ 7. Substitutes skeleton placeholders like `{{MODEL_NAME}}`, `{{PLURAL}}`, `{{PROPS_INTERFACE}}` with model-specific values.
70
+ 8. Returns the complete `.tsx` source string.
71
+
72
+ The factory's realize pass calls this once per view, writes each to `frontend/src/views/{ViewName}.tsx`, updates `.specverse-gen/hashes.json`.
73
+
74
+ ## Why this split works
75
+
76
+ - **Idiomatic outer structure** — skeletons are hand-written `.tsx.template` files, tweaked by the humans who maintain the factory. They define imports, props interfaces, hook placement, event wiring, loading states, empty states. Exactly what a human React author would write.
77
+ - **Pattern-driven interior** — the actual markup (table header cells, row templates, form fields, badge styling) comes from the canonical Tailwind adapter. When runtime improves the pattern rendering, Factory B inherits the fix automatically — because the adapter is the single source.
78
+ - **No full re-implementation** — we don't build a second renderer. The adapter already works for runtime; we reuse it at build time.
79
+
80
+ ## The html-to-jsx transformer — scope
81
+
82
+ Input: HTML string with Tailwind classes. Single root element (always — the adapter always produces a containing element).
83
+
84
+ Output: A JSX-safe source string that can be dropped between JSX tags.
85
+
86
+ What it does:
87
+ 1. `class="..."` → `className="..."`
88
+ 2. Self-closing tags: `<img src="...">` → `<img src="..." />`
89
+ 3. Escape `{` and `}` in text content by wrapping in `{'{'}` / `{'}'}` when they appear outside attribute values.
90
+ 4. Convert `style="color: red"` to `style={{ color: 'red' }}`.
91
+ 5. Convert `for="..."` to `htmlFor="..."`.
92
+ 6. Strip any attributes that aren't valid in JSX.
93
+
94
+ What it explicitly does NOT do:
95
+ - Parse JavaScript inside the HTML (the adapter produces static HTML — no JS expressions).
96
+ - Insert React hooks or event handlers — those live in the skeleton, not the interior.
97
+ - Handle SVG / MathML nuances — only HTML+Tailwind.
98
+
99
+ Test coverage target: feed the transformer 50 pre-captured HTML snippets from the canonical adapter output, assert the JSX output parses as valid TSX via `typescript.transpileModule`.
100
+
101
+ ## Skeleton template format
102
+
103
+ A skeleton is a `.tsx.template` file — syntactically valid TypeScript with substitution markers. Example (sketch for `list.tsx.template`):
104
+
105
+ ```tsx
106
+ /**
107
+ * {{MODEL_NAME}}ListView — generated by @specverse/realize (ReactAppStarter)
108
+ *
109
+ * Safe to edit. Edits are preserved across regeneration (content-hashed).
110
+ * Regenerate with `spv realize` or, if this file was edited, delete the
111
+ * file first to opt back into regeneration.
112
+ */
113
+ import { useState, useMemo } from 'react';
114
+ import { use{{PLURAL_MODEL}}Query, useDelete{{MODEL_NAME}}Mutation } from '../hooks/useApi';
115
+ import type { {{MODEL_NAME}} } from '../types/api';
116
+
117
+ interface {{MODEL_NAME}}ListViewProps {
118
+ onSelect?: (item: {{MODEL_NAME}}) => void;
119
+ onCreate?: () => void;
120
+ }
121
+
122
+ export function {{MODEL_NAME}}ListView({ onSelect, onCreate }: {{MODEL_NAME}}ListViewProps) {
123
+ const { data: items = [], isLoading, error } = use{{PLURAL_MODEL}}Query();
124
+ const deleteItem = useDelete{{MODEL_NAME}}Mutation();
125
+ const [searchTerm, setSearchTerm] = useState('');
126
+
127
+ const filtered = useMemo(
128
+ () => items.filter(item =>
129
+ Object.values(item).some(v => String(v).toLowerCase().includes(searchTerm.toLowerCase()))
130
+ ),
131
+ [items, searchTerm]
132
+ );
133
+
134
+ if (isLoading) return <div className="p-4 text-gray-500">Loading…</div>;
135
+ if (error) return <div className="p-4 text-red-600">Error loading {{PLURAL_LOWER}}: {String(error)}</div>;
136
+
137
+ return (
138
+ <div className="p-6 space-y-4">
139
+ <div className="flex justify-between items-center">
140
+ <input
141
+ type="search"
142
+ placeholder="Search {{PLURAL_LOWER}}…"
143
+ value={searchTerm}
144
+ onChange={e => setSearchTerm(e.target.value)}
145
+ className="rounded border px-3 py-2 w-64"
146
+ />
147
+ <button
148
+ onClick={onCreate}
149
+ className="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
150
+ >
151
+ + New {{MODEL_NAME}}
152
+ </button>
153
+ </div>
154
+
155
+ {/* {{BODY}} — pattern-rendered table interior, JSX-transformed */}
156
+ {{BODY}}
157
+
158
+ {filtered.length === 0 && (
159
+ <div className="p-8 text-center text-gray-400">No {{PLURAL_LOWER}} yet.</div>
160
+ )}
161
+ </div>
162
+ );
163
+ }
164
+ ```
165
+
166
+ Substitution contract:
167
+ - `{{MODEL_NAME}}` — PascalCase model name (e.g., `Post`).
168
+ - `{{PLURAL_MODEL}}` — PascalCase plural (e.g., `Posts`).
169
+ - `{{PLURAL_LOWER}}` — lowercase plural (e.g., `posts`).
170
+ - `{{BODY}}` — exactly one substitution per skeleton, where the pattern-rendered interior goes.
171
+
172
+ Substitution is plain string replacement — no template expression evaluation.
173
+
174
+ ## File placement
175
+
176
+ ```
177
+ engines/libs/instance-factories/applications/templates/react-starter/
178
+ ├── README.md # this file
179
+ ├── view-emitter.ts # orchestrator (step 1)
180
+ ├── html-to-jsx.ts # transformer (step 3)
181
+ ├── skeletons/
182
+ │ ├── list.tsx.template # list view
183
+ │ ├── detail.tsx.template # detail view
184
+ │ ├── form.tsx.template # form view (Phase 2e)
185
+ │ ├── dashboard.tsx.template # dashboard (Phase 2e)
186
+ │ └── specialist-*.tsx.template # board/timeline/calendar (Phase 2e)
187
+ └── __tests__/
188
+ ├── html-to-jsx.test.ts # transformer unit tests
189
+ └── view-emitter.test.ts # end-to-end: (spec, view) → valid TSX
190
+ ```
191
+
192
+ ## Build order (Phase 2a in the migration plan)
193
+
194
+ 1. Implement `html-to-jsx.ts` first (smallest, most testable, standalone).
195
+ 2. Write `list.tsx.template` (one view type; production-quality).
196
+ 3. Implement `view-emitter.ts` — orchestrator that wires html-to-jsx + canonical Tailwind adapter + skeleton.
197
+ 4. Integration test: take a reference spec with one model + its list view, emit, assert the `.tsx` source parses and its JSX matches the expected shape.
198
+ 5. Only after list works end-to-end: template out to detail + form + dashboard.
199
+
200
+ ## Open questions (inside Phase 2a)
201
+
202
+ 1. **Where do per-model hooks come from?** `use{{PLURAL_MODEL}}Query` and `useDelete{{MODEL_NAME}}Mutation` in the skeleton presume a generated hooks file (`src/hooks/useApi.ts`) that provides them. `ReactAppRuntime` already generates this file via `use-api-hooks-generator.ts` — Factory B should reuse that generator (shared between both factories as called out in the migration plan).
203
+ 2. **How does the skeleton address auto-generated fields (id, createdAt, etc.)?** The view should skip them in the table/form. `isAutoGeneratedField()` logic needs to be available in the generated output. Plan: emit a `src/lib/field-helpers.ts` alongside the views (inlined from the pattern library's logic, same way the runtime has it).
204
+ 3. **What happens when the view spec has `uiComponents` that the pattern library doesn't recognize?** Runtime gracefully falls back to a default table. Factory B should do the same — same fallback logic, same output.
205
+ 4. **Should the skeleton expose a slot for user-added React nodes (e.g. `<Children />` or a `prepend` prop)?** Useful for extensibility without forcing users to edit the generated file. Defer to v2 of the emitter — start with fixed skeletons.
206
+
207
+ ## Non-goals
208
+
209
+ - No JSX AST parsing or manipulation. `html-to-jsx.ts` is a string transformer, not a compiler.
210
+ - No server-side rendering of the React components. The emitter outputs source files; the user's Vite build bundles them.
211
+ - No runtime customization of skeletons. A project that wants different skeletons forks the factory or the generated code.
@@ -0,0 +1,153 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import * as ts from 'typescript';
3
+ import { composeDashboardBody } from '../dashboard-body-composer.js';
4
+ import { emitView, type EmitContext, type ModelSpec } from '../view-emitter.js';
5
+
6
+ function makeContext(overrides: Partial<EmitContext> = {}): EmitContext {
7
+ const post: ModelSpec = {
8
+ name: 'Post',
9
+ attributes: {
10
+ id: { type: 'UUID', required: true },
11
+ title: { type: 'String', required: true },
12
+ body: { type: 'Text', required: false },
13
+ status: { type: 'String', required: false, values: ['draft', 'live', 'archived'] },
14
+ createdAt: { type: 'DateTime', required: false },
15
+ },
16
+ };
17
+
18
+ return {
19
+ view: { type: 'dashboard', model: 'Post' },
20
+ viewName: 'PostDashboardView',
21
+ model: post,
22
+ modelSchemas: { Post: post },
23
+ renderBody: composeDashboardBody,
24
+ ...overrides,
25
+ };
26
+ }
27
+
28
+ function assertValidTsx(source: string, label: string): void {
29
+ const result = ts.transpileModule(source, {
30
+ compilerOptions: {
31
+ jsx: ts.JsxEmit.Preserve,
32
+ target: ts.ScriptTarget.ES2022,
33
+ module: ts.ModuleKind.ESNext,
34
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
35
+ strict: false,
36
+ },
37
+ reportDiagnostics: true,
38
+ });
39
+ const errors = result.diagnostics?.filter(d => d.category === ts.DiagnosticCategory.Error) ?? [];
40
+ if (errors.length > 0) {
41
+ const message = errors
42
+ .map(d => ts.flattenDiagnosticMessageText(d.messageText, '\n'))
43
+ .join('\n');
44
+ throw new Error(`${label} failed to parse as TSX:\n${message}\n\n--- source ---\n${source}`);
45
+ }
46
+ }
47
+
48
+ describe('composeDashboardBody — metrics row', () => {
49
+ it('emits a total-count card', () => {
50
+ const body = composeDashboardBody(makeContext());
51
+ expect(body).toContain('Total posts');
52
+ expect(body).toContain('{items.length}');
53
+ });
54
+
55
+ it('emits a per-value breakdown card for each enum value when a small enum exists', () => {
56
+ const body = composeDashboardBody(makeContext());
57
+ // The `status` attribute has 3 values. Each gets a card.
58
+ expect(body).toContain('Status: Draft');
59
+ expect(body).toContain('Status: Live');
60
+ expect(body).toContain('Status: Archived');
61
+ // Filter expressions for counts
62
+ expect(body).toContain(`i.status === "draft"`);
63
+ expect(body).toContain(`i.status === "live"`);
64
+ expect(body).toContain(`i.status === "archived"`);
65
+ });
66
+
67
+ it('falls back to a placeholder card when no enum fields exist', () => {
68
+ const plain: ModelSpec = {
69
+ name: 'Plain',
70
+ attributes: {
71
+ id: { type: 'UUID' },
72
+ name: { type: 'String' },
73
+ },
74
+ };
75
+ const body = composeDashboardBody(makeContext({
76
+ model: plain,
77
+ view: { type: 'dashboard', model: 'Plain' },
78
+ viewName: 'PlainDashboardView',
79
+ modelSchemas: { Plain: plain },
80
+ }));
81
+ expect(body).toContain('Add a metric here');
82
+ });
83
+
84
+ it('skips large enums (> 6 values) to avoid card sprawl', () => {
85
+ const huge: ModelSpec = {
86
+ name: 'Huge',
87
+ attributes: {
88
+ id: { type: 'UUID' },
89
+ tier: {
90
+ type: 'String',
91
+ values: ['s', 'a', 'b', 'c', 'd', 'e', 'f'], // 7 → should be skipped
92
+ },
93
+ },
94
+ };
95
+ const body = composeDashboardBody(makeContext({
96
+ model: huge,
97
+ view: { type: 'dashboard', model: 'Huge' },
98
+ viewName: 'HugeDashboardView',
99
+ modelSchemas: { Huge: huge },
100
+ }));
101
+ expect(body).not.toContain('Tier: S');
102
+ expect(body).toContain('Add a metric here'); // placeholder kicked in
103
+ });
104
+ });
105
+
106
+ describe('composeDashboardBody — recent preview', () => {
107
+ it('emits a table with business-field columns (metadata excluded)', () => {
108
+ const body = composeDashboardBody(makeContext());
109
+ expect(body).toContain('Recent Posts');
110
+ expect(body).toContain('>Title<');
111
+ expect(body).toContain('>Body<');
112
+ expect(body).toContain('>Status<');
113
+ // metadata excluded
114
+ expect(body).not.toContain('>Id<');
115
+ expect(body).not.toContain('>Created At<');
116
+ });
117
+
118
+ it('maps over `preview` (from the skeleton), not `items`', () => {
119
+ const body = composeDashboardBody(makeContext());
120
+ expect(body).toContain('{preview.map((item, idx) =>');
121
+ });
122
+
123
+ it('handles empty-list state with a spanning row', () => {
124
+ const body = composeDashboardBody(makeContext());
125
+ expect(body).toContain('{preview.length === 0 && (');
126
+ expect(body).toContain('No records yet.');
127
+ });
128
+ });
129
+
130
+ describe('composeDashboardBody — TODO comments', () => {
131
+ it('flags the extension point for aggregation metrics', () => {
132
+ const body = composeDashboardBody(makeContext());
133
+ expect(body).toContain('TODO: add aggregation metrics');
134
+ });
135
+ });
136
+
137
+ describe('emitView wired to composeDashboardBody — end-to-end', () => {
138
+ it('produces a complete PostDashboardView.tsx that parses', () => {
139
+ const source = emitView(makeContext());
140
+ assertValidTsx(source, 'PostDashboardView');
141
+ });
142
+
143
+ it('wires the skeleton: component, hooks, preview memo, body', () => {
144
+ const source = emitView(makeContext());
145
+ expect(source).toContain('export function PostDashboardView');
146
+ expect(source).toContain('usePostsQuery');
147
+ expect(source).toContain('Post dashboard');
148
+ expect(source).toContain('items.slice(0, previewLimit)');
149
+ expect(source).toContain('Total posts');
150
+ expect(source).toContain('Recent Posts');
151
+ expect(source).not.toMatch(/\{\{[A-Z_]+\}\}/);
152
+ });
153
+ });