@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,334 @@
1
+ /**
2
+ * HTML → JSX transformer for ReactAppStarter
3
+ *
4
+ * Input: HTML strings produced by the canonical Tailwind adapter
5
+ * (`@specverse/runtime/views/tailwind`). Output: JSX-safe source
6
+ * that can be dropped between JSX tags in a .tsx template.
7
+ *
8
+ * Scope is deliberately narrow:
9
+ * - Controlled input — only the adapter's output shape matters.
10
+ * - String transformer with a small tokenizer, not a full HTML parser.
11
+ * - No SVG / MathML / script / style tags.
12
+ *
13
+ * If the adapter ever produces markup outside this scope, extend the
14
+ * ATTRIBUTE_RENAMES map / VOID_ELEMENTS set + add a focused test,
15
+ * don't broaden the tokenizer into a general HTML parser.
16
+ *
17
+ * See: README.md ("The html-to-jsx transformer — scope").
18
+ */
19
+
20
+ /**
21
+ * HTML void elements — must be self-closing in JSX.
22
+ */
23
+ const VOID_ELEMENTS = new Set([
24
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img',
25
+ 'input', 'link', 'meta', 'source', 'track', 'wbr',
26
+ ]);
27
+
28
+ /**
29
+ * HTML attribute → JSX attribute renames. JSX is case-sensitive and
30
+ * camelCases several HTML attribute names. Only lists the ones the
31
+ * Tailwind adapter actually emits.
32
+ */
33
+ const ATTRIBUTE_RENAMES: Record<string, string> = {
34
+ class: 'className',
35
+ for: 'htmlFor',
36
+ tabindex: 'tabIndex',
37
+ readonly: 'readOnly',
38
+ maxlength: 'maxLength',
39
+ minlength: 'minLength',
40
+ colspan: 'colSpan',
41
+ rowspan: 'rowSpan',
42
+ autofocus: 'autoFocus',
43
+ autocomplete: 'autoComplete',
44
+ cellpadding: 'cellPadding',
45
+ cellspacing: 'cellSpacing',
46
+ enctype: 'encType',
47
+ usemap: 'useMap',
48
+ accesskey: 'accessKey',
49
+ contenteditable: 'contentEditable',
50
+ spellcheck: 'spellCheck',
51
+ crossorigin: 'crossOrigin',
52
+ srcset: 'srcSet',
53
+ srcdoc: 'srcDoc',
54
+ };
55
+
56
+ /**
57
+ * Convert an HTML string to JSX-safe source.
58
+ *
59
+ * Idempotent on already-JSX-shaped input.
60
+ */
61
+ export function htmlToJsx(html: string): string {
62
+ if (!html) return '';
63
+
64
+ const tokens = tokenize(html);
65
+ return tokens.map(transformToken).join('');
66
+ }
67
+
68
+ // ──────────────────────────────────────────────────────────────────────
69
+ // Tokenizer
70
+ // ──────────────────────────────────────────────────────────────────────
71
+
72
+ type Token =
73
+ | { kind: 'text'; value: string }
74
+ | { kind: 'tag'; raw: string; tagName: string; isClosing: boolean; isSelfClosed: boolean };
75
+
76
+ /**
77
+ * Split an HTML string into text and tag tokens. Quoted attribute
78
+ * values never close a tag, even if they contain '>'. The adapter
79
+ * does not emit unbalanced quotes, so a simple state machine is
80
+ * sufficient.
81
+ */
82
+ function tokenize(input: string): Token[] {
83
+ const tokens: Token[] = [];
84
+ let i = 0;
85
+ const n = input.length;
86
+
87
+ while (i < n) {
88
+ const ch = input[i];
89
+ if (ch === '<') {
90
+ // Start of tag. Consume until the matching '>', respecting quotes.
91
+ const tagStart = i;
92
+ i++; // consume '<'
93
+
94
+ let quote: '"' | "'" | null = null;
95
+ while (i < n) {
96
+ const c = input[i];
97
+ if (quote) {
98
+ if (c === quote) quote = null;
99
+ i++;
100
+ continue;
101
+ }
102
+ if (c === '"' || c === "'") {
103
+ quote = c;
104
+ i++;
105
+ continue;
106
+ }
107
+ if (c === '>') {
108
+ i++; // include the '>'
109
+ break;
110
+ }
111
+ i++;
112
+ }
113
+
114
+ const raw = input.slice(tagStart, i);
115
+ const isClosing = raw.startsWith('</');
116
+ const isSelfClosed = raw.endsWith('/>');
117
+ const tagName = parseTagName(raw);
118
+ tokens.push({ kind: 'tag', raw, tagName, isClosing, isSelfClosed });
119
+ } else {
120
+ // Text until the next '<'.
121
+ const textStart = i;
122
+ while (i < n && input[i] !== '<') i++;
123
+ tokens.push({ kind: 'text', value: input.slice(textStart, i) });
124
+ }
125
+ }
126
+
127
+ return tokens;
128
+ }
129
+
130
+ function parseTagName(tagRaw: string): string {
131
+ // Handles '<foo', '</foo', '<foo ', '<foo/>', '<foo>' etc.
132
+ const m = tagRaw.match(/^<\/?([a-zA-Z][a-zA-Z0-9-]*)/);
133
+ return m ? m[1].toLowerCase() : '';
134
+ }
135
+
136
+ // ──────────────────────────────────────────────────────────────────────
137
+ // Transformation
138
+ // ──────────────────────────────────────────────────────────────────────
139
+
140
+ function transformToken(token: Token): string {
141
+ if (token.kind === 'text') {
142
+ // Adapter output never contains raw '{' or '}' in text nodes in
143
+ // the current implementation. If that changes, escape them here
144
+ // via `{'{'}` / `{'}'}`. Flagged for future: a regression test
145
+ // in view-emitter.test.ts that feeds a spec containing literal
146
+ // braces and expects them round-tripped.
147
+ return token.value;
148
+ }
149
+
150
+ return transformTag(token.raw, token.tagName, token.isClosing, token.isSelfClosed);
151
+ }
152
+
153
+ /**
154
+ * Transform a single tag: rename attributes, self-close if void.
155
+ *
156
+ * Works inside-out: split into `<`, open-portion, attributes, close-portion, `>`.
157
+ * We only rewrite attribute NAMES (identifier tokens at attribute start),
158
+ * never attribute values or text. The tokenizer guarantees we see the
159
+ * full tag as one unit.
160
+ */
161
+ function transformTag(
162
+ raw: string,
163
+ tagName: string,
164
+ isClosing: boolean,
165
+ isSelfClosed: boolean
166
+ ): string {
167
+ if (isClosing) {
168
+ // '</div>' — nothing to rewrite
169
+ return raw;
170
+ }
171
+
172
+ // Split the tag into the tag-name section and the attributes section.
173
+ // Strip the leading '<' and trailing '>' or '/>'.
174
+ const inner = isSelfClosed
175
+ ? raw.slice(1, -2).trimEnd()
176
+ : raw.slice(1, -1);
177
+
178
+ // Separate tag name from attributes. The tag name is the first word.
179
+ const nameMatch = inner.match(/^([a-zA-Z][a-zA-Z0-9-]*)(.*)$/s);
180
+ if (!nameMatch) return raw;
181
+
182
+ const elementName = nameMatch[1];
183
+ const attrsSection = nameMatch[2];
184
+
185
+ // Parse attributes into structured form, rename names, re-serialise.
186
+ // This guarantees we never rewrite anything inside an attribute value.
187
+ const attrs = parseAttributes(attrsSection);
188
+ const renamedAttrs = attrs.map(renameAttribute);
189
+ const rebuiltAttrs = serializeAttributes(renamedAttrs);
190
+
191
+ const shouldSelfClose = VOID_ELEMENTS.has(tagName) && !isSelfClosed;
192
+
193
+ if (shouldSelfClose || isSelfClosed) {
194
+ return `<${elementName}${rebuiltAttrs} />`;
195
+ }
196
+ return `<${elementName}${rebuiltAttrs}>`;
197
+ }
198
+
199
+ // ──────────────────────────────────────────────────────────────────────
200
+ // Attribute tokenizer
201
+ // ──────────────────────────────────────────────────────────────────────
202
+
203
+ interface Attribute {
204
+ name: string;
205
+ /** undefined = boolean attribute, no value. */
206
+ value?: string;
207
+ /** How the original source quoted the value — preserve on output. */
208
+ quote?: '"' | "'";
209
+ }
210
+
211
+ /**
212
+ * Parse the attributes section of a tag (the substring after the tag
213
+ * name, before the closing `>` or `/>`) into a list of attributes.
214
+ * Whitespace is consumed between attributes; values inside quotes are
215
+ * opaque.
216
+ */
217
+ function parseAttributes(section: string): Attribute[] {
218
+ const attrs: Attribute[] = [];
219
+ let i = 0;
220
+ const n = section.length;
221
+
222
+ while (i < n) {
223
+ // Skip whitespace
224
+ while (i < n && /\s/.test(section[i])) i++;
225
+ if (i >= n) break;
226
+
227
+ // Read name (letters, digits, hyphens, colons for namespaced attrs)
228
+ const nameStart = i;
229
+ while (i < n && /[a-zA-Z0-9:_-]/.test(section[i])) i++;
230
+ if (i === nameStart) {
231
+ // Nothing we recognise — advance to avoid infinite loop
232
+ i++;
233
+ continue;
234
+ }
235
+ const name = section.slice(nameStart, i);
236
+
237
+ // Look for '=value' or end-of-attr
238
+ // Skip whitespace before '='
239
+ let j = i;
240
+ while (j < n && /\s/.test(section[j])) j++;
241
+ if (j >= n || section[j] !== '=') {
242
+ // Boolean attribute (e.g. `readonly`, `disabled`)
243
+ attrs.push({ name });
244
+ continue;
245
+ }
246
+ // Consume '=' and optional whitespace
247
+ i = j + 1;
248
+ while (i < n && /\s/.test(section[i])) i++;
249
+ if (i >= n) {
250
+ // Malformed: attribute with '=' and no value. Treat as boolean.
251
+ attrs.push({ name });
252
+ break;
253
+ }
254
+
255
+ // Read value — quoted or unquoted
256
+ const vc = section[i];
257
+ if (vc === '"' || vc === "'") {
258
+ const quote = vc as '"' | "'";
259
+ i++; // skip opening quote
260
+ const valStart = i;
261
+ while (i < n && section[i] !== quote) i++;
262
+ const value = section.slice(valStart, i);
263
+ if (i < n) i++; // skip closing quote
264
+ attrs.push({ name, value, quote });
265
+ } else {
266
+ // Unquoted value — run until whitespace or '/' or '>'
267
+ const valStart = i;
268
+ while (i < n && !/[\s/>]/.test(section[i])) i++;
269
+ const value = section.slice(valStart, i);
270
+ attrs.push({ name, value });
271
+ }
272
+ }
273
+
274
+ return attrs;
275
+ }
276
+
277
+ /** Apply HTML→JSX name rewrite and, for `style`, value conversion. */
278
+ function renameAttribute(attr: Attribute): Attribute {
279
+ const lower = attr.name.toLowerCase();
280
+ const newName = ATTRIBUTE_RENAMES[lower] ?? attr.name;
281
+
282
+ if (lower === 'style' && attr.value !== undefined) {
283
+ return { name: newName, value: cssStringToJsxObject(attr.value), quote: undefined };
284
+ }
285
+
286
+ return { ...attr, name: newName };
287
+ }
288
+
289
+ /**
290
+ * Serialise a list of attributes back into source form. Preserves the
291
+ * original quote style. A value of `{{ ... }}` (already a JSX object
292
+ * expression) is emitted unquoted (no surrounding `"`); it's already
293
+ * valid JSX syntax.
294
+ */
295
+ function serializeAttributes(attrs: Attribute[]): string {
296
+ if (attrs.length === 0) return '';
297
+ const parts = attrs.map(attr => {
298
+ if (attr.value === undefined) return ` ${attr.name}`;
299
+ if (attr.value.startsWith('{{') && attr.value.endsWith('}}')) {
300
+ // JSX expression like {{ color: 'red' }} — no quotes.
301
+ return ` ${attr.name}=${attr.value}`;
302
+ }
303
+ const q = attr.quote ?? '"';
304
+ return ` ${attr.name}=${q}${attr.value}${q}`;
305
+ });
306
+ return parts.join('');
307
+ }
308
+
309
+ // ──────────────────────────────────────────────────────────────────────
310
+ // CSS → JSX style object
311
+ // ──────────────────────────────────────────────────────────────────────
312
+
313
+ function cssStringToJsxObject(css: string): string {
314
+ const declarations = css
315
+ .split(';')
316
+ .map(d => d.trim())
317
+ .filter(d => d.length > 0);
318
+
319
+ if (declarations.length === 0) return '{{}}';
320
+
321
+ const entries = declarations
322
+ .map(decl => {
323
+ const colonIdx = decl.indexOf(':');
324
+ if (colonIdx === -1) return null;
325
+ const prop = decl.slice(0, colonIdx).trim();
326
+ const value = decl.slice(colonIdx + 1).trim();
327
+ const camelProp = prop.replace(/-([a-z])/g, (_m, c) => c.toUpperCase());
328
+ const escapedValue = value.replace(/'/g, "\\'");
329
+ return `${camelProp}: '${escapedValue}'`;
330
+ })
331
+ .filter((e): e is string => e !== null);
332
+
333
+ return `{{ ${entries.join(', ')} }}`;
334
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * List-view body composer for ReactAppStarter
3
+ *
4
+ * Implements the `renderBody` contract of view-emitter.ts for list
5
+ * views. Uses the canonical Tailwind adapter from
6
+ * `@specverse/runtime/views/tailwind` to render the TABLE SHELL, then
7
+ * injects a JSX `.map()` expression into the tbody so the generated
8
+ * component renders rows from the `filtered` array defined by the
9
+ * skeleton.
10
+ *
11
+ * Why split it this way: the adapter produces static HTML (the same
12
+ * HTML app-demo uses). For Factory B we want React that maps over
13
+ * runtime data — so we take the adapter's shell and replace a sentinel
14
+ * inside the tbody with a `{filtered.map(...)}` expression. The shell
15
+ * stays canonical; only the row-iteration JSX is synthesized here.
16
+ *
17
+ * See README.md for the full architecture.
18
+ */
19
+
20
+ import { createUniversalTailwindAdapter } from '@specverse/runtime/views/tailwind';
21
+ import { inferFieldsFromSchema } from '@specverse/runtime/views/core';
22
+ import { htmlToJsx } from './html-to-jsx.js';
23
+ import type { EmitContext, ModelSpec } from './view-emitter.js';
24
+
25
+ /**
26
+ * Sentinel token. Must be ASCII-safe (no curly braces) so it survives
27
+ * the html-to-jsx transform unchanged, and must be unique enough not
28
+ * to appear in real HTML output.
29
+ */
30
+ const TBODY_SENTINEL = '__SPECVERSE_TBODY_ROWS__';
31
+
32
+ /**
33
+ * Compose the list view's body as JSX-safe source. Returned string is
34
+ * meant to be dropped at `{{BODY}}` inside `skeletons/list.tsx.template`.
35
+ */
36
+ export function composeListBody(context: EmitContext): string {
37
+ const columns = inferColumns(context);
38
+ const headers = columns.map(humanize);
39
+
40
+ const adapter = createUniversalTailwindAdapter({ darkMode: true });
41
+ const shellHtml = adapter.components.table.render({
42
+ properties: { columns: headers },
43
+ children: TBODY_SENTINEL,
44
+ });
45
+
46
+ // Transform the static HTML shell to JSX-safe source. The sentinel
47
+ // lands in a text position inside the <tbody> and survives the
48
+ // transform untouched.
49
+ const shellJsx = htmlToJsx(shellHtml);
50
+
51
+ // Build the row-iteration JSX that replaces the sentinel. The
52
+ // skeleton's useMemo produces `filtered: Model[]`, so the map
53
+ // expression binds over that. onSelect is a prop the skeleton
54
+ // declares. Delete action wires to the deleteItem mutation.
55
+ const rowMap = buildRowMap(columns);
56
+
57
+ if (!shellJsx.includes(TBODY_SENTINEL)) {
58
+ // Defensive: catches adapter output changes that no longer flow
59
+ // `children` into the tbody. Caught at composer time, not runtime.
60
+ throw new Error(
61
+ 'composeListBody: tbody sentinel not present in adapter output. ' +
62
+ 'The canonical Tailwind adapter may have changed its table rendering.'
63
+ );
64
+ }
65
+
66
+ return shellJsx.replace(TBODY_SENTINEL, rowMap);
67
+ }
68
+
69
+ /**
70
+ * Select the non-metadata attributes of a model. Delegates to the
71
+ * canonical pattern library so the column set is identical to what
72
+ * the runtime React adapter picks for the same model — parity test P3
73
+ * relies on this being one-and-the-same function.
74
+ */
75
+ function inferColumns(context: EmitContext): string[] {
76
+ return inferFieldsFromSchema(context.modelSchemas, context.model.name);
77
+ }
78
+
79
+ /**
80
+ * Convert a camelCase attribute name to Title-Case words for display
81
+ * as a column header. Matches the convention used by runtime's
82
+ * pattern adapter (see `humanize` in runtime react-pattern-adapter).
83
+ */
84
+ function humanize(name: string): string {
85
+ return name
86
+ .replace(/([A-Z])/g, ' $1')
87
+ .replace(/^./, c => c.toUpperCase())
88
+ .trim();
89
+ }
90
+
91
+ /**
92
+ * Emit the `{filtered.map(...)}` JSX expression that renders each
93
+ * row. Keep the output readable so users inspecting the generated
94
+ * file can follow what's happening.
95
+ */
96
+ function buildRowMap(columns: string[]): string {
97
+ // Type-cast to `any` to avoid TS generic syntax (`<string, unknown>`)
98
+ // inside JSX expressions, which the TSX parser can't always
99
+ // disambiguate from a JSX opening tag. `any` is the right choice for
100
+ // starter-kit output anyway — the user will often reshape the row
101
+ // type after editing.
102
+ const cells = columns
103
+ .map(col =>
104
+ ` <td className="px-6 py-4 text-sm text-gray-700 dark:text-gray-300">` +
105
+ `{String((item as any).${col} ?? '')}</td>`
106
+ )
107
+ .join('\n');
108
+
109
+ return [
110
+ '{filtered.map((item, idx) => (',
111
+ ' <tr',
112
+ ' key={idx}',
113
+ ' onClick={() => onSelect?.(item)}',
114
+ ' className="hover:bg-gray-50 dark:hover:bg-gray-800 cursor-pointer"',
115
+ ' >',
116
+ cells,
117
+ ' </tr>',
118
+ '))}',
119
+ ].join('\n');
120
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Factory B top-level orchestrator
3
+ *
4
+ * Single entry point the realize engine calls. Returns a map of
5
+ * `relativePath → content` for every file the factory decides to
6
+ * write, after applying regeneration-safety triage. Realize handles
7
+ * the actual filesystem writes via its normal multi-file generator
8
+ * pipeline.
9
+ *
10
+ * Skipped (user-edited) files are NOT in the returned map — realize
11
+ * never sees them, so user edits are safe. The hash manifest is
12
+ * emitted as one of the returned files (`.specverse-gen/hashes.json`)
13
+ * so it rolls through the same write pipeline.
14
+ */
15
+
16
+ import { generate as generateViews } from './views-generator.js';
17
+ import { generate as generateAppTsx } from './app-tsx-generator.js';
18
+ import { generate as generatePackageJson } from './package-json-generator.js';
19
+ import {
20
+ loadHashManifest,
21
+ reconcileWrites,
22
+ summarize,
23
+ HASHES_DIR,
24
+ HASHES_FILE,
25
+ } from './regen-safety.js';
26
+ import type { ExpandedSpec } from './views-generator.js';
27
+
28
+ export interface OrchestratorContext {
29
+ /** Expanded spec (post-inference). */
30
+ spec: ExpandedSpec & { metadata?: { name?: string }; name?: string };
31
+ /** Resolved manifest configuration. */
32
+ manifest?: unknown;
33
+ /** Absolute path to the project root (the frontend directory for this factory). */
34
+ projectRoot: string;
35
+ }
36
+
37
+ /**
38
+ * The shape realize expects from a multi-file generator: a map of
39
+ * relative paths to their contents. Realize creates parent dirs and
40
+ * writes the files verbatim.
41
+ *
42
+ * Skipped (user-edited) files are intentionally absent — realize
43
+ * never sees them.
44
+ */
45
+ export type OrchestratorOutput = Record<string, string>;
46
+
47
+ /**
48
+ * Produce the complete file set for a Factory B run. Realize writes
49
+ * whatever comes back; the orchestrator is the sole source of truth
50
+ * for "what ends up on disk."
51
+ */
52
+ export async function generate(context: OrchestratorContext): Promise<OrchestratorOutput> {
53
+ const { spec, projectRoot } = context;
54
+
55
+ // 1. Produce all file contents (pure composition — no I/O yet).
56
+ const proposed: Record<string, string> = {};
57
+
58
+ const viewFiles = await generateViews({ spec });
59
+ Object.assign(proposed, viewFiles);
60
+
61
+ proposed['src/App.tsx'] = await generateAppTsx({ spec });
62
+ proposed['package.json'] = await generatePackageJson({ spec });
63
+
64
+ // 2. Load prior hash manifest + triage. Read-only.
65
+ const prevManifest = loadHashManifest(projectRoot);
66
+ const result = reconcileWrites(projectRoot, proposed, prevManifest);
67
+
68
+ // 3. Log the summary for operator visibility.
69
+ console.log(summarize(result, projectRoot));
70
+
71
+ // 4. Emit the hash manifest as one of the written files so it flows
72
+ // through realize's normal write pipeline.
73
+ const manifestPath = `${HASHES_DIR}/${HASHES_FILE}`;
74
+ const out: OrchestratorOutput = {
75
+ ...result.approvedWrites,
76
+ [manifestPath]: JSON.stringify(result.manifest, null, 2) + '\n',
77
+ };
78
+
79
+ return out;
80
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * package.json generator for ReactAppStarter
3
+ *
4
+ * Deliberately does NOT include @specverse/runtime. The promise of
5
+ * Factory B is that the generated project is standalone — everything
6
+ * needed to render is emitted as local source.
7
+ */
8
+
9
+ export interface PackageJsonGeneratorContext {
10
+ spec: {
11
+ metadata?: { name?: string };
12
+ name?: string;
13
+ };
14
+ manifest?: unknown;
15
+ }
16
+
17
+ export async function generate(context: PackageJsonGeneratorContext): Promise<string> {
18
+ const appName = context.spec?.metadata?.name ?? context.spec?.name ?? 'specverse-starter-app';
19
+ const packageName = slugify(appName);
20
+
21
+ const pkg = {
22
+ name: packageName,
23
+ private: true,
24
+ version: '0.1.0',
25
+ type: 'module',
26
+ scripts: {
27
+ dev: 'vite',
28
+ build: 'tsc && vite build',
29
+ preview: 'vite preview',
30
+ typecheck: 'tsc --noEmit',
31
+ },
32
+ dependencies: {
33
+ react: '^18.2.0',
34
+ 'react-dom': '^18.2.0',
35
+ '@tanstack/react-query': '^5.0.0',
36
+ },
37
+ devDependencies: {
38
+ '@types/react': '^18.2.0',
39
+ '@types/react-dom': '^18.2.0',
40
+ '@vitejs/plugin-react': '^4.2.0',
41
+ autoprefixer: '^10.4.20',
42
+ postcss: '^8.4.47',
43
+ tailwindcss: '^3.4.13',
44
+ typescript: '^5.2.0',
45
+ vite: '^6.0.0',
46
+ },
47
+ };
48
+
49
+ return JSON.stringify(pkg, null, 2) + '\n';
50
+ }
51
+
52
+ function slugify(s: string): string {
53
+ return s
54
+ .toLowerCase()
55
+ .replace(/[^a-z0-9]+/g, '-')
56
+ .replace(/^-+|-+$/g, '') || 'specverse-starter-app';
57
+ }