@specverse/engines 4.1.30 → 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 (226) 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/react-starter/app-tsx-generator.js +110 -0
  45. package/dist/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.js +121 -0
  46. package/dist/libs/instance-factories/applications/templates/react-starter/detail-body-composer.js +78 -0
  47. package/dist/libs/instance-factories/applications/templates/react-starter/form-body-composer.js +190 -0
  48. package/dist/libs/instance-factories/applications/templates/react-starter/helpers-emitter.js +45 -0
  49. package/dist/libs/instance-factories/applications/templates/react-starter/html-to-jsx.js +192 -0
  50. package/dist/libs/instance-factories/applications/templates/react-starter/list-body-composer.js +46 -0
  51. package/dist/libs/instance-factories/applications/templates/react-starter/orchestrator.js +30 -0
  52. package/dist/libs/instance-factories/applications/templates/react-starter/package-json-generator.js +38 -0
  53. package/dist/libs/instance-factories/applications/templates/react-starter/regen-safety.js +89 -0
  54. package/dist/libs/instance-factories/applications/templates/react-starter/view-emitter.js +56 -0
  55. package/dist/libs/instance-factories/applications/templates/react-starter/views-generator.js +66 -0
  56. package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +14 -11
  57. package/dist/realize/index.d.ts.map +1 -1
  58. package/dist/realize/index.js +15 -22
  59. package/dist/realize/index.js.map +1 -1
  60. package/dist/registry/utils/manifest-adapter.d.ts +8 -1
  61. package/dist/registry/utils/manifest-adapter.d.ts.map +1 -1
  62. package/dist/registry/utils/manifest-adapter.js +8 -1
  63. package/dist/registry/utils/manifest-adapter.js.map +1 -1
  64. package/libs/instance-factories/applications/react-app-starter.yaml +150 -0
  65. package/libs/instance-factories/applications/templates/react-starter/README.md +211 -0
  66. package/libs/instance-factories/applications/templates/react-starter/__tests__/dashboard-body-composer.test.ts +153 -0
  67. package/libs/instance-factories/applications/templates/react-starter/__tests__/detail-body-composer.test.ts +145 -0
  68. package/libs/instance-factories/applications/templates/react-starter/__tests__/form-body-composer.test.ts +175 -0
  69. package/libs/instance-factories/applications/templates/react-starter/__tests__/helpers-emitter.test.ts +55 -0
  70. package/libs/instance-factories/applications/templates/react-starter/__tests__/html-to-jsx.test.ts +140 -0
  71. package/libs/instance-factories/applications/templates/react-starter/__tests__/list-body-composer.test.ts +146 -0
  72. package/libs/instance-factories/applications/templates/react-starter/__tests__/orchestrator.test.ts +163 -0
  73. package/libs/instance-factories/applications/templates/react-starter/__tests__/parity-p2-factory-imports.test.ts +116 -0
  74. package/libs/instance-factories/applications/templates/react-starter/__tests__/parity-p3-rendered-output.test.ts +183 -0
  75. package/libs/instance-factories/applications/templates/react-starter/__tests__/regen-safety.test.ts +144 -0
  76. package/libs/instance-factories/applications/templates/react-starter/__tests__/starter-generators.test.ts +114 -0
  77. package/libs/instance-factories/applications/templates/react-starter/__tests__/view-emitter.test.ts +107 -0
  78. package/libs/instance-factories/applications/templates/react-starter/__tests__/views-generator.test.ts +139 -0
  79. package/libs/instance-factories/applications/templates/react-starter/app-tsx-generator.ts +141 -0
  80. package/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.ts +174 -0
  81. package/libs/instance-factories/applications/templates/react-starter/detail-body-composer.ts +135 -0
  82. package/libs/instance-factories/applications/templates/react-starter/form-body-composer.ts +306 -0
  83. package/libs/instance-factories/applications/templates/react-starter/helpers-emitter.ts +60 -0
  84. package/libs/instance-factories/applications/templates/react-starter/html-to-jsx.ts +334 -0
  85. package/libs/instance-factories/applications/templates/react-starter/list-body-composer.ts +120 -0
  86. package/libs/instance-factories/applications/templates/react-starter/orchestrator.ts +80 -0
  87. package/libs/instance-factories/applications/templates/react-starter/package-json-generator.ts +57 -0
  88. package/libs/instance-factories/applications/templates/react-starter/regen-safety.ts +157 -0
  89. package/libs/instance-factories/applications/templates/react-starter/skeletons/dashboard.tsx.template +47 -0
  90. package/libs/instance-factories/applications/templates/react-starter/skeletons/detail.tsx.template +94 -0
  91. package/libs/instance-factories/applications/templates/react-starter/skeletons/form.tsx.template +114 -0
  92. package/libs/instance-factories/applications/templates/react-starter/skeletons/list.tsx.template +72 -0
  93. package/libs/instance-factories/applications/templates/react-starter/view-emitter.ts +151 -0
  94. package/libs/instance-factories/applications/templates/react-starter/views-generator.ts +137 -0
  95. package/libs/instance-factories/cli/templates/commander/command-generator.ts +14 -11
  96. package/package.json +3 -3
  97. package/dist/libs/instance-factories/applications/templates/react/_view-components-source.js +0 -530
  98. package/dist/libs/instance-factories/applications/templates/react/app-tsx-generator.js +0 -73
  99. package/dist/libs/instance-factories/applications/templates/react/field-helpers-generator.js +0 -99
  100. package/dist/libs/instance-factories/applications/templates/react/package-json-generator.js +0 -49
  101. package/dist/libs/instance-factories/applications/templates/react/pattern-adapter-generator.js +0 -156
  102. package/dist/libs/instance-factories/applications/templates/react/react-pattern-adapter.js +0 -935
  103. package/dist/libs/instance-factories/applications/templates/react/relationship-field-generator.js +0 -143
  104. package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-generator.js +0 -646
  105. package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-wrapper-generator.js +0 -65
  106. package/dist/libs/instance-factories/applications/templates/react/view-dashboard-generator.js +0 -143
  107. package/dist/libs/instance-factories/applications/templates/react/view-detail-generator.js +0 -143
  108. package/dist/libs/instance-factories/applications/templates/react/view-form-generator.js +0 -355
  109. package/dist/libs/instance-factories/applications/templates/react/view-list-generator.js +0 -91
  110. package/dist/libs/instance-factories/applications/templates/react/view-router-generator.js +0 -79
  111. package/dist/libs/instance-factories/views/index.js +0 -48
  112. package/dist/libs/instance-factories/views/templates/react/adapters/antd-adapter.js +0 -742
  113. package/dist/libs/instance-factories/views/templates/react/adapters/mui-adapter.js +0 -824
  114. package/dist/libs/instance-factories/views/templates/react/adapters/shadcn-adapter.js +0 -719
  115. package/dist/libs/instance-factories/views/templates/react/app-generator.js +0 -45
  116. package/dist/libs/instance-factories/views/templates/react/components-generator.js +0 -820
  117. package/dist/libs/instance-factories/views/templates/react/forms-generator.js +0 -275
  118. package/dist/libs/instance-factories/views/templates/react/frontend-package-json-generator.js +0 -46
  119. package/dist/libs/instance-factories/views/templates/react/hooks-generator.js +0 -81
  120. package/dist/libs/instance-factories/views/templates/react/index-css-generator.js +0 -9
  121. package/dist/libs/instance-factories/views/templates/react/index-html-generator.js +0 -23
  122. package/dist/libs/instance-factories/views/templates/react/main-tsx-generator.js +0 -21
  123. package/dist/libs/instance-factories/views/templates/react/react-component-generator.js +0 -299
  124. package/dist/libs/instance-factories/views/templates/react/router-generator.js +0 -136
  125. package/dist/libs/instance-factories/views/templates/react/router-generic-generator.js +0 -107
  126. package/dist/libs/instance-factories/views/templates/react/shared-utils-generator.js +0 -187
  127. package/dist/libs/instance-factories/views/templates/react/spec-json-generator.js +0 -7
  128. package/dist/libs/instance-factories/views/templates/react/types-generator.js +0 -56
  129. package/dist/libs/instance-factories/views/templates/react/views-metadata-generator.js +0 -27
  130. package/dist/libs/instance-factories/views/templates/react/vite-config-generator.js +0 -29
  131. package/dist/libs/instance-factories/views/templates/runtime/runtime-view-renderer.js +0 -261
  132. package/dist/libs/instance-factories/views/templates/shared/adapter-types.js +0 -34
  133. package/dist/libs/instance-factories/views/templates/shared/atomic-components-registry.js +0 -800
  134. package/dist/libs/instance-factories/views/templates/shared/base-generator.js +0 -305
  135. package/dist/libs/instance-factories/views/templates/shared/component-metadata.js +0 -517
  136. package/dist/libs/instance-factories/views/templates/shared/composite-pattern-types.js +0 -0
  137. package/dist/libs/instance-factories/views/templates/shared/composite-patterns.js +0 -445
  138. package/dist/libs/instance-factories/views/templates/shared/index.js +0 -80
  139. package/dist/libs/instance-factories/views/templates/shared/pattern-validator.js +0 -210
  140. package/dist/libs/instance-factories/views/templates/shared/property-mapper.js +0 -492
  141. package/dist/libs/instance-factories/views/templates/shared/syntax-mapper.js +0 -321
  142. package/dist/realize/index.js.bak +0 -758
  143. package/libs/instance-factories/applications/react-app.yaml +0 -186
  144. package/libs/instance-factories/applications/templates/react/_view-components-source.ts +0 -555
  145. package/libs/instance-factories/applications/templates/react/app-tsx-generator.ts +0 -94
  146. package/libs/instance-factories/applications/templates/react/field-helpers-generator.ts +0 -106
  147. package/libs/instance-factories/applications/templates/react/package-json-generator.ts +0 -57
  148. package/libs/instance-factories/applications/templates/react/pattern-adapter-generator.ts +0 -179
  149. package/libs/instance-factories/applications/templates/react/react-pattern-adapter.tsx +0 -1347
  150. package/libs/instance-factories/applications/templates/react/relationship-field-generator.ts +0 -150
  151. package/libs/instance-factories/applications/templates/react/tailwind-adapter-generator.ts +0 -704
  152. package/libs/instance-factories/applications/templates/react/tailwind-adapter-wrapper-generator.ts +0 -84
  153. package/libs/instance-factories/applications/templates/react/view-dashboard-generator.ts +0 -150
  154. package/libs/instance-factories/applications/templates/react/view-detail-generator.ts +0 -150
  155. package/libs/instance-factories/applications/templates/react/view-form-generator.ts +0 -362
  156. package/libs/instance-factories/applications/templates/react/view-list-generator.ts +0 -98
  157. package/libs/instance-factories/applications/templates/react/view-router-generator.ts +0 -89
  158. package/libs/instance-factories/views/README.md +0 -62
  159. package/libs/instance-factories/views/index.d.ts +0 -13
  160. package/libs/instance-factories/views/index.d.ts.map +0 -1
  161. package/libs/instance-factories/views/index.js +0 -18
  162. package/libs/instance-factories/views/index.js.map +0 -1
  163. package/libs/instance-factories/views/index.ts +0 -45
  164. package/libs/instance-factories/views/react-components.yaml +0 -129
  165. package/libs/instance-factories/views/templates/ARCHITECTURE.md +0 -198
  166. package/libs/instance-factories/views/templates/react/adapters/antd-adapter.ts +0 -869
  167. package/libs/instance-factories/views/templates/react/adapters/mui-adapter.ts +0 -953
  168. package/libs/instance-factories/views/templates/react/adapters/shadcn-adapter.ts +0 -806
  169. package/libs/instance-factories/views/templates/react/app-generator.ts +0 -55
  170. package/libs/instance-factories/views/templates/react/components-generator.ts +0 -938
  171. package/libs/instance-factories/views/templates/react/forms-generator.ts +0 -325
  172. package/libs/instance-factories/views/templates/react/frontend-package-json-generator.ts +0 -57
  173. package/libs/instance-factories/views/templates/react/hooks-generator.ts +0 -106
  174. package/libs/instance-factories/views/templates/react/index-css-generator.ts +0 -14
  175. package/libs/instance-factories/views/templates/react/index-html-generator.ts +0 -34
  176. package/libs/instance-factories/views/templates/react/main-tsx-generator.ts +0 -29
  177. package/libs/instance-factories/views/templates/react/react-component-generator.d.ts +0 -152
  178. package/libs/instance-factories/views/templates/react/react-component-generator.d.ts.map +0 -1
  179. package/libs/instance-factories/views/templates/react/react-component-generator.js +0 -398
  180. package/libs/instance-factories/views/templates/react/react-component-generator.js.map +0 -1
  181. package/libs/instance-factories/views/templates/react/react-component-generator.ts +0 -533
  182. package/libs/instance-factories/views/templates/react/router-generator.ts +0 -197
  183. package/libs/instance-factories/views/templates/react/router-generic-generator.ts +0 -132
  184. package/libs/instance-factories/views/templates/react/shared-utils-generator.ts +0 -196
  185. package/libs/instance-factories/views/templates/react/spec-json-generator.ts +0 -17
  186. package/libs/instance-factories/views/templates/react/types-generator.ts +0 -76
  187. package/libs/instance-factories/views/templates/react/views-metadata-generator.ts +0 -42
  188. package/libs/instance-factories/views/templates/react/vite-config-generator.ts +0 -38
  189. package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.d.ts.map +0 -1
  190. package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.js.map +0 -1
  191. package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.ts +0 -474
  192. package/libs/instance-factories/views/templates/shared/__tests__/composite-patterns.test.ts +0 -242
  193. package/libs/instance-factories/views/templates/shared/adapter-types.d.ts +0 -77
  194. package/libs/instance-factories/views/templates/shared/adapter-types.d.ts.map +0 -1
  195. package/libs/instance-factories/views/templates/shared/adapter-types.js +0 -47
  196. package/libs/instance-factories/views/templates/shared/adapter-types.js.map +0 -1
  197. package/libs/instance-factories/views/templates/shared/adapter-types.ts +0 -142
  198. package/libs/instance-factories/views/templates/shared/atomic-components-registry.d.ts +0 -63
  199. package/libs/instance-factories/views/templates/shared/atomic-components-registry.d.ts.map +0 -1
  200. package/libs/instance-factories/views/templates/shared/atomic-components-registry.js +0 -822
  201. package/libs/instance-factories/views/templates/shared/atomic-components-registry.js.map +0 -1
  202. package/libs/instance-factories/views/templates/shared/atomic-components-registry.ts +0 -908
  203. package/libs/instance-factories/views/templates/shared/base-generator.d.ts +0 -247
  204. package/libs/instance-factories/views/templates/shared/base-generator.d.ts.map +0 -1
  205. package/libs/instance-factories/views/templates/shared/base-generator.js +0 -363
  206. package/libs/instance-factories/views/templates/shared/base-generator.js.map +0 -1
  207. package/libs/instance-factories/views/templates/shared/base-generator.ts +0 -608
  208. package/libs/instance-factories/views/templates/shared/component-metadata.d.ts +0 -254
  209. package/libs/instance-factories/views/templates/shared/component-metadata.d.ts.map +0 -1
  210. package/libs/instance-factories/views/templates/shared/component-metadata.js +0 -602
  211. package/libs/instance-factories/views/templates/shared/component-metadata.js.map +0 -1
  212. package/libs/instance-factories/views/templates/shared/component-metadata.ts +0 -803
  213. package/libs/instance-factories/views/templates/shared/composite-pattern-types.ts +0 -250
  214. package/libs/instance-factories/views/templates/shared/composite-patterns.ts +0 -535
  215. package/libs/instance-factories/views/templates/shared/index.ts +0 -68
  216. package/libs/instance-factories/views/templates/shared/pattern-validator.ts +0 -279
  217. package/libs/instance-factories/views/templates/shared/property-mapper.d.ts +0 -149
  218. package/libs/instance-factories/views/templates/shared/property-mapper.d.ts.map +0 -1
  219. package/libs/instance-factories/views/templates/shared/property-mapper.js +0 -580
  220. package/libs/instance-factories/views/templates/shared/property-mapper.js.map +0 -1
  221. package/libs/instance-factories/views/templates/shared/property-mapper.ts +0 -700
  222. package/libs/instance-factories/views/templates/shared/syntax-mapper.d.ts +0 -143
  223. package/libs/instance-factories/views/templates/shared/syntax-mapper.d.ts.map +0 -1
  224. package/libs/instance-factories/views/templates/shared/syntax-mapper.js +0 -420
  225. package/libs/instance-factories/views/templates/shared/syntax-mapper.js.map +0 -1
  226. package/libs/instance-factories/views/templates/shared/syntax-mapper.ts +0 -539
@@ -0,0 +1,190 @@
1
+ import { METADATA_FIELDS } from "@specverse/runtime/views/core";
2
+ const AUTO_GENERATED_FIELD_NAMES = new Set(METADATA_FIELDS);
3
+ function composeFormBody(context) {
4
+ const belongsTo = extractBelongsTo(context.model);
5
+ const shadowedFKs = new Set(belongsTo.map((rel) => `${rel}Id`));
6
+ const fields = selectFormFields(context.model, shadowedFKs);
7
+ const lines = [];
8
+ lines.push('<div className="space-y-4">');
9
+ if (fields.length === 0 && belongsTo.length === 0) {
10
+ lines.push(' <p className="text-sm text-gray-400">No editable fields for this model.</p>');
11
+ lines.push("</div>");
12
+ return lines.join("\n");
13
+ }
14
+ for (const field of fields) {
15
+ lines.push(...renderInput(field));
16
+ }
17
+ if (belongsTo.length > 0) {
18
+ lines.push("");
19
+ lines.push(" {/* TODO: swap these text inputs for <select>s that load the related");
20
+ lines.push(" entities. See useEntitiesQuery(TargetModel) in the runtime hooks. */}");
21
+ for (const rel of belongsTo) {
22
+ lines.push(...renderInput({
23
+ name: `${rel}Id`,
24
+ label: humanize(rel),
25
+ type: "String",
26
+ required: true
27
+ }));
28
+ }
29
+ }
30
+ lines.push("</div>");
31
+ return lines.join("\n");
32
+ }
33
+ function selectFormFields(model, skip = /* @__PURE__ */ new Set()) {
34
+ const out = [];
35
+ const attrs = model.attributes ?? {};
36
+ for (const [name, rawDef] of Object.entries(attrs)) {
37
+ if (AUTO_GENERATED_FIELD_NAMES.has(name)) continue;
38
+ if (skip.has(name)) continue;
39
+ const def = rawDef;
40
+ if (def?.auto) continue;
41
+ const type = def?.type ?? parseTypeFromConvention(def) ?? "String";
42
+ const required = def?.required === true || hasConventionFlag(def, "required");
43
+ const values = def?.values;
44
+ const maxLength = def?.maxLength ?? def?.max;
45
+ const isLongString = typeof maxLength === "number" && maxLength > 100;
46
+ out.push({
47
+ name,
48
+ label: humanize(name),
49
+ type: normalizeType(type),
50
+ required,
51
+ values,
52
+ isLongString
53
+ });
54
+ }
55
+ return out;
56
+ }
57
+ function parseTypeFromConvention(def) {
58
+ if (typeof def === "string") {
59
+ const first = def.trim().split(/\s+/)[0];
60
+ return first;
61
+ }
62
+ return void 0;
63
+ }
64
+ function hasConventionFlag(def, flag) {
65
+ if (typeof def === "string") {
66
+ return def.split(/\s+/).includes(flag);
67
+ }
68
+ return false;
69
+ }
70
+ function normalizeType(type) {
71
+ return type.replace(/[^A-Za-z].*$/, "");
72
+ }
73
+ function extractBelongsTo(model) {
74
+ const rels = model.relationships ?? {};
75
+ const out = [];
76
+ for (const [name, def] of Object.entries(rels)) {
77
+ if (def?.type === "belongsTo") out.push(name);
78
+ }
79
+ return out;
80
+ }
81
+ const INPUT_CLS = "w-full rounded border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100";
82
+ const LABEL_CLS = "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1";
83
+ function renderInput(field) {
84
+ const requiredMark = field.required ? " *" : "";
85
+ const reqAttr = field.required ? " required" : "";
86
+ if (field.values && field.values.length > 0) {
87
+ const options = field.values.map((v) => ` <option value="${escapeAttr(v)}">${escapeText(v)}</option>`).join("\n");
88
+ return [
89
+ " <div>",
90
+ ` <label className="${LABEL_CLS}" htmlFor="${field.name}">${field.label}${requiredMark}</label>`,
91
+ ` <select`,
92
+ ` id="${field.name}"`,
93
+ ` className="${INPUT_CLS}"`,
94
+ ` value={String((formData as any).${field.name} ?? '')}`,
95
+ ` onChange={e => handleChange('${field.name}', e.target.value)}${reqAttr}`,
96
+ ` >`,
97
+ ` <option value="">\u2014 choose \u2014</option>`,
98
+ options,
99
+ ` </select>`,
100
+ " </div>"
101
+ ];
102
+ }
103
+ if (field.type === "Text" || field.isLongString) {
104
+ return [
105
+ " <div>",
106
+ ` <label className="${LABEL_CLS}" htmlFor="${field.name}">${field.label}${requiredMark}</label>`,
107
+ ` <textarea`,
108
+ ` id="${field.name}"`,
109
+ ` className="${INPUT_CLS}"`,
110
+ ` rows={4}`,
111
+ ` value={String((formData as any).${field.name} ?? '')}`,
112
+ ` onChange={e => handleChange('${field.name}', e.target.value)}${reqAttr}`,
113
+ ` />`,
114
+ " </div>"
115
+ ];
116
+ }
117
+ if (field.type === "Boolean") {
118
+ return [
119
+ " <div>",
120
+ ` <label className="inline-flex items-center gap-2">`,
121
+ ` <input`,
122
+ ` id="${field.name}"`,
123
+ ` type="checkbox"`,
124
+ ` checked={Boolean((formData as any).${field.name})}`,
125
+ ` onChange={e => handleChange('${field.name}', e.target.checked)}`,
126
+ ` />`,
127
+ ` <span className="text-sm text-gray-700 dark:text-gray-300">${field.label}${requiredMark}</span>`,
128
+ ` </label>`,
129
+ " </div>"
130
+ ];
131
+ }
132
+ const inputType = mapInputType(field.type);
133
+ return [
134
+ " <div>",
135
+ ` <label className="${LABEL_CLS}" htmlFor="${field.name}">${field.label}${requiredMark}</label>`,
136
+ ` <input`,
137
+ ` id="${field.name}"`,
138
+ ` type="${inputType}"`,
139
+ ` className="${INPUT_CLS}"`,
140
+ ` value={String((formData as any).${field.name} ?? '')}`,
141
+ ` onChange={e => handleChange('${field.name}', ${coerceOnChange(field.type)})}${reqAttr}`,
142
+ ` />`,
143
+ " </div>"
144
+ ];
145
+ }
146
+ function mapInputType(type) {
147
+ switch (type) {
148
+ case "Integer":
149
+ case "Float":
150
+ case "Number":
151
+ case "Money":
152
+ return "number";
153
+ case "DateTime":
154
+ return "datetime-local";
155
+ case "Date":
156
+ return "date";
157
+ case "Email":
158
+ return "email";
159
+ case "URL":
160
+ return "url";
161
+ case "String":
162
+ case "UUID":
163
+ default:
164
+ return "text";
165
+ }
166
+ }
167
+ function coerceOnChange(type) {
168
+ switch (type) {
169
+ case "Integer":
170
+ case "Number":
171
+ return "e.target.value === '' ? undefined : Number(e.target.value)";
172
+ case "Float":
173
+ case "Money":
174
+ return "e.target.value === '' ? undefined : parseFloat(e.target.value)";
175
+ default:
176
+ return "e.target.value";
177
+ }
178
+ }
179
+ function humanize(name) {
180
+ return name.replace(/([A-Z])/g, " $1").replace(/^./, (c) => c.toUpperCase()).trim();
181
+ }
182
+ function escapeAttr(s) {
183
+ return s.replace(/"/g, "&quot;");
184
+ }
185
+ function escapeText(s) {
186
+ return s.replace(/</g, "&lt;").replace(/>/g, "&gt;");
187
+ }
188
+ export {
189
+ composeFormBody
190
+ };
@@ -0,0 +1,45 @@
1
+ function emitEntityDisplay() {
2
+ return `/**
3
+ * Pick the best human-readable label for an entity.
4
+ *
5
+ * Walks a prioritized list of candidate fields. Falls back to a
6
+ * truncated id if none match. Inlined into this project by
7
+ * @specverse/realize (ReactAppStarter); edit freely \u2014 nothing in
8
+ * the factory's regeneration will clobber your edits.
9
+ */
10
+ export function getEntityDisplayName(entity: Record<string, unknown> | null | undefined): string {
11
+ if (!entity) return '';
12
+
13
+ const candidates = ['name', 'title', 'displayName', 'label', 'username', 'email'];
14
+ for (const key of candidates) {
15
+ const value = entity[key];
16
+ if (typeof value === 'string' && value.trim().length > 0) {
17
+ return value;
18
+ }
19
+ }
20
+
21
+ const id = entity.id;
22
+ if (typeof id === 'string') {
23
+ return id.length > 8 ? id.slice(0, 8) + '\u2026' : id;
24
+ }
25
+ return id != null ? String(id) : '';
26
+ }
27
+
28
+ /**
29
+ * Map an entity id to the display name of the record with that id in
30
+ * a provided list. Handy for resolving belongsTo FK columns in
31
+ * list / detail views.
32
+ */
33
+ export function resolveEntityDisplayName(
34
+ id: unknown,
35
+ records: ReadonlyArray<Record<string, unknown>>
36
+ ): string {
37
+ if (id == null) return '';
38
+ const match = records.find(r => r.id === id);
39
+ return match ? getEntityDisplayName(match) : String(id);
40
+ }
41
+ `;
42
+ }
43
+ export {
44
+ emitEntityDisplay
45
+ };
@@ -0,0 +1,192 @@
1
+ const VOID_ELEMENTS = /* @__PURE__ */ new Set([
2
+ "area",
3
+ "base",
4
+ "br",
5
+ "col",
6
+ "embed",
7
+ "hr",
8
+ "img",
9
+ "input",
10
+ "link",
11
+ "meta",
12
+ "source",
13
+ "track",
14
+ "wbr"
15
+ ]);
16
+ const ATTRIBUTE_RENAMES = {
17
+ class: "className",
18
+ for: "htmlFor",
19
+ tabindex: "tabIndex",
20
+ readonly: "readOnly",
21
+ maxlength: "maxLength",
22
+ minlength: "minLength",
23
+ colspan: "colSpan",
24
+ rowspan: "rowSpan",
25
+ autofocus: "autoFocus",
26
+ autocomplete: "autoComplete",
27
+ cellpadding: "cellPadding",
28
+ cellspacing: "cellSpacing",
29
+ enctype: "encType",
30
+ usemap: "useMap",
31
+ accesskey: "accessKey",
32
+ contenteditable: "contentEditable",
33
+ spellcheck: "spellCheck",
34
+ crossorigin: "crossOrigin",
35
+ srcset: "srcSet",
36
+ srcdoc: "srcDoc"
37
+ };
38
+ function htmlToJsx(html) {
39
+ if (!html) return "";
40
+ const tokens = tokenize(html);
41
+ return tokens.map(transformToken).join("");
42
+ }
43
+ function tokenize(input) {
44
+ const tokens = [];
45
+ let i = 0;
46
+ const n = input.length;
47
+ while (i < n) {
48
+ const ch = input[i];
49
+ if (ch === "<") {
50
+ const tagStart = i;
51
+ i++;
52
+ let quote = null;
53
+ while (i < n) {
54
+ const c = input[i];
55
+ if (quote) {
56
+ if (c === quote) quote = null;
57
+ i++;
58
+ continue;
59
+ }
60
+ if (c === '"' || c === "'") {
61
+ quote = c;
62
+ i++;
63
+ continue;
64
+ }
65
+ if (c === ">") {
66
+ i++;
67
+ break;
68
+ }
69
+ i++;
70
+ }
71
+ const raw = input.slice(tagStart, i);
72
+ const isClosing = raw.startsWith("</");
73
+ const isSelfClosed = raw.endsWith("/>");
74
+ const tagName = parseTagName(raw);
75
+ tokens.push({ kind: "tag", raw, tagName, isClosing, isSelfClosed });
76
+ } else {
77
+ const textStart = i;
78
+ while (i < n && input[i] !== "<") i++;
79
+ tokens.push({ kind: "text", value: input.slice(textStart, i) });
80
+ }
81
+ }
82
+ return tokens;
83
+ }
84
+ function parseTagName(tagRaw) {
85
+ const m = tagRaw.match(/^<\/?([a-zA-Z][a-zA-Z0-9-]*)/);
86
+ return m ? m[1].toLowerCase() : "";
87
+ }
88
+ function transformToken(token) {
89
+ if (token.kind === "text") {
90
+ return token.value;
91
+ }
92
+ return transformTag(token.raw, token.tagName, token.isClosing, token.isSelfClosed);
93
+ }
94
+ function transformTag(raw, tagName, isClosing, isSelfClosed) {
95
+ if (isClosing) {
96
+ return raw;
97
+ }
98
+ const inner = isSelfClosed ? raw.slice(1, -2).trimEnd() : raw.slice(1, -1);
99
+ const nameMatch = inner.match(/^([a-zA-Z][a-zA-Z0-9-]*)(.*)$/s);
100
+ if (!nameMatch) return raw;
101
+ const elementName = nameMatch[1];
102
+ const attrsSection = nameMatch[2];
103
+ const attrs = parseAttributes(attrsSection);
104
+ const renamedAttrs = attrs.map(renameAttribute);
105
+ const rebuiltAttrs = serializeAttributes(renamedAttrs);
106
+ const shouldSelfClose = VOID_ELEMENTS.has(tagName) && !isSelfClosed;
107
+ if (shouldSelfClose || isSelfClosed) {
108
+ return `<${elementName}${rebuiltAttrs} />`;
109
+ }
110
+ return `<${elementName}${rebuiltAttrs}>`;
111
+ }
112
+ function parseAttributes(section) {
113
+ const attrs = [];
114
+ let i = 0;
115
+ const n = section.length;
116
+ while (i < n) {
117
+ while (i < n && /\s/.test(section[i])) i++;
118
+ if (i >= n) break;
119
+ const nameStart = i;
120
+ while (i < n && /[a-zA-Z0-9:_-]/.test(section[i])) i++;
121
+ if (i === nameStart) {
122
+ i++;
123
+ continue;
124
+ }
125
+ const name = section.slice(nameStart, i);
126
+ let j = i;
127
+ while (j < n && /\s/.test(section[j])) j++;
128
+ if (j >= n || section[j] !== "=") {
129
+ attrs.push({ name });
130
+ continue;
131
+ }
132
+ i = j + 1;
133
+ while (i < n && /\s/.test(section[i])) i++;
134
+ if (i >= n) {
135
+ attrs.push({ name });
136
+ break;
137
+ }
138
+ const vc = section[i];
139
+ if (vc === '"' || vc === "'") {
140
+ const quote = vc;
141
+ i++;
142
+ const valStart = i;
143
+ while (i < n && section[i] !== quote) i++;
144
+ const value = section.slice(valStart, i);
145
+ if (i < n) i++;
146
+ attrs.push({ name, value, quote });
147
+ } else {
148
+ const valStart = i;
149
+ while (i < n && !/[\s/>]/.test(section[i])) i++;
150
+ const value = section.slice(valStart, i);
151
+ attrs.push({ name, value });
152
+ }
153
+ }
154
+ return attrs;
155
+ }
156
+ function renameAttribute(attr) {
157
+ const lower = attr.name.toLowerCase();
158
+ const newName = ATTRIBUTE_RENAMES[lower] ?? attr.name;
159
+ if (lower === "style" && attr.value !== void 0) {
160
+ return { name: newName, value: cssStringToJsxObject(attr.value), quote: void 0 };
161
+ }
162
+ return { ...attr, name: newName };
163
+ }
164
+ function serializeAttributes(attrs) {
165
+ if (attrs.length === 0) return "";
166
+ const parts = attrs.map((attr) => {
167
+ if (attr.value === void 0) return ` ${attr.name}`;
168
+ if (attr.value.startsWith("{{") && attr.value.endsWith("}}")) {
169
+ return ` ${attr.name}=${attr.value}`;
170
+ }
171
+ const q = attr.quote ?? '"';
172
+ return ` ${attr.name}=${q}${attr.value}${q}`;
173
+ });
174
+ return parts.join("");
175
+ }
176
+ function cssStringToJsxObject(css) {
177
+ const declarations = css.split(";").map((d) => d.trim()).filter((d) => d.length > 0);
178
+ if (declarations.length === 0) return "{{}}";
179
+ const entries = declarations.map((decl) => {
180
+ const colonIdx = decl.indexOf(":");
181
+ if (colonIdx === -1) return null;
182
+ const prop = decl.slice(0, colonIdx).trim();
183
+ const value = decl.slice(colonIdx + 1).trim();
184
+ const camelProp = prop.replace(/-([a-z])/g, (_m, c) => c.toUpperCase());
185
+ const escapedValue = value.replace(/'/g, "\\'");
186
+ return `${camelProp}: '${escapedValue}'`;
187
+ }).filter((e) => e !== null);
188
+ return `{{ ${entries.join(", ")} }}`;
189
+ }
190
+ export {
191
+ htmlToJsx
192
+ };
@@ -0,0 +1,46 @@
1
+ import { createUniversalTailwindAdapter } from "@specverse/runtime/views/tailwind";
2
+ import { inferFieldsFromSchema } from "@specverse/runtime/views/core";
3
+ import { htmlToJsx } from "./html-to-jsx.js";
4
+ const TBODY_SENTINEL = "__SPECVERSE_TBODY_ROWS__";
5
+ function composeListBody(context) {
6
+ const columns = inferColumns(context);
7
+ const headers = columns.map(humanize);
8
+ const adapter = createUniversalTailwindAdapter({ darkMode: true });
9
+ const shellHtml = adapter.components.table.render({
10
+ properties: { columns: headers },
11
+ children: TBODY_SENTINEL
12
+ });
13
+ const shellJsx = htmlToJsx(shellHtml);
14
+ const rowMap = buildRowMap(columns);
15
+ if (!shellJsx.includes(TBODY_SENTINEL)) {
16
+ throw new Error(
17
+ "composeListBody: tbody sentinel not present in adapter output. The canonical Tailwind adapter may have changed its table rendering."
18
+ );
19
+ }
20
+ return shellJsx.replace(TBODY_SENTINEL, rowMap);
21
+ }
22
+ function inferColumns(context) {
23
+ return inferFieldsFromSchema(context.modelSchemas, context.model.name);
24
+ }
25
+ function humanize(name) {
26
+ return name.replace(/([A-Z])/g, " $1").replace(/^./, (c) => c.toUpperCase()).trim();
27
+ }
28
+ function buildRowMap(columns) {
29
+ const cells = columns.map(
30
+ (col) => ` <td className="px-6 py-4 text-sm text-gray-700 dark:text-gray-300">{String((item as any).${col} ?? '')}</td>`
31
+ ).join("\n");
32
+ return [
33
+ "{filtered.map((item, idx) => (",
34
+ " <tr",
35
+ " key={idx}",
36
+ " onClick={() => onSelect?.(item)}",
37
+ ' className="hover:bg-gray-50 dark:hover:bg-gray-800 cursor-pointer"',
38
+ " >",
39
+ cells,
40
+ " </tr>",
41
+ "))}"
42
+ ].join("\n");
43
+ }
44
+ export {
45
+ composeListBody
46
+ };
@@ -0,0 +1,30 @@
1
+ import { generate as generateViews } from "./views-generator.js";
2
+ import { generate as generateAppTsx } from "./app-tsx-generator.js";
3
+ import { generate as generatePackageJson } from "./package-json-generator.js";
4
+ import {
5
+ loadHashManifest,
6
+ reconcileWrites,
7
+ summarize,
8
+ HASHES_DIR,
9
+ HASHES_FILE
10
+ } from "./regen-safety.js";
11
+ async function generate(context) {
12
+ const { spec, projectRoot } = context;
13
+ const proposed = {};
14
+ const viewFiles = await generateViews({ spec });
15
+ Object.assign(proposed, viewFiles);
16
+ proposed["src/App.tsx"] = await generateAppTsx({ spec });
17
+ proposed["package.json"] = await generatePackageJson({ spec });
18
+ const prevManifest = loadHashManifest(projectRoot);
19
+ const result = reconcileWrites(projectRoot, proposed, prevManifest);
20
+ console.log(summarize(result, projectRoot));
21
+ const manifestPath = `${HASHES_DIR}/${HASHES_FILE}`;
22
+ const out = {
23
+ ...result.approvedWrites,
24
+ [manifestPath]: JSON.stringify(result.manifest, null, 2) + "\n"
25
+ };
26
+ return out;
27
+ }
28
+ export {
29
+ generate
30
+ };
@@ -0,0 +1,38 @@
1
+ async function generate(context) {
2
+ const appName = context.spec?.metadata?.name ?? context.spec?.name ?? "specverse-starter-app";
3
+ const packageName = slugify(appName);
4
+ const pkg = {
5
+ name: packageName,
6
+ private: true,
7
+ version: "0.1.0",
8
+ type: "module",
9
+ scripts: {
10
+ dev: "vite",
11
+ build: "tsc && vite build",
12
+ preview: "vite preview",
13
+ typecheck: "tsc --noEmit"
14
+ },
15
+ dependencies: {
16
+ react: "^18.2.0",
17
+ "react-dom": "^18.2.0",
18
+ "@tanstack/react-query": "^5.0.0"
19
+ },
20
+ devDependencies: {
21
+ "@types/react": "^18.2.0",
22
+ "@types/react-dom": "^18.2.0",
23
+ "@vitejs/plugin-react": "^4.2.0",
24
+ autoprefixer: "^10.4.20",
25
+ postcss: "^8.4.47",
26
+ tailwindcss: "^3.4.13",
27
+ typescript: "^5.2.0",
28
+ vite: "^6.0.0"
29
+ }
30
+ };
31
+ return JSON.stringify(pkg, null, 2) + "\n";
32
+ }
33
+ function slugify(s) {
34
+ return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "specverse-starter-app";
35
+ }
36
+ export {
37
+ generate
38
+ };
@@ -0,0 +1,89 @@
1
+ import { createHash } from "crypto";
2
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
3
+ import { join, relative } from "path";
4
+ const HASHES_DIR = ".specverse-gen";
5
+ const HASHES_FILE = "hashes.json";
6
+ function sha256(s) {
7
+ return createHash("sha256").update(s, "utf8").digest("hex");
8
+ }
9
+ function loadHashManifest(projectRoot) {
10
+ const path = join(projectRoot, HASHES_DIR, HASHES_FILE);
11
+ if (!existsSync(path)) return {};
12
+ try {
13
+ const raw = readFileSync(path, "utf8");
14
+ const parsed = JSON.parse(raw);
15
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
16
+ const out = {};
17
+ for (const [k, v] of Object.entries(parsed)) {
18
+ if (typeof v === "string") out[k] = v;
19
+ }
20
+ return out;
21
+ }
22
+ } catch {
23
+ }
24
+ return {};
25
+ }
26
+ function saveHashManifest(projectRoot, manifest) {
27
+ const dir = join(projectRoot, HASHES_DIR);
28
+ mkdirSync(dir, { recursive: true });
29
+ const path = join(dir, HASHES_FILE);
30
+ writeFileSync(path, JSON.stringify(manifest, null, 2) + "\n", "utf8");
31
+ }
32
+ function reconcileWrites(projectRoot, proposed, prevManifest) {
33
+ const manifest = { ...prevManifest };
34
+ const approvedWrites = {};
35
+ const skipped = [];
36
+ for (const [relPath, content] of Object.entries(proposed)) {
37
+ const abs = join(projectRoot, relPath);
38
+ const newHash = sha256(content);
39
+ if (!existsSync(abs)) {
40
+ approvedWrites[relPath] = content;
41
+ manifest[relPath] = newHash;
42
+ continue;
43
+ }
44
+ const currentContent = readFileSync(abs, "utf8");
45
+ const currentHash = sha256(currentContent);
46
+ const recordedHash = prevManifest[relPath];
47
+ if (recordedHash == null) {
48
+ skipped.push({
49
+ path: relPath,
50
+ reason: "no prior hash recorded \u2014 cannot confirm this file was generated by us"
51
+ });
52
+ continue;
53
+ }
54
+ if (currentHash === recordedHash) {
55
+ approvedWrites[relPath] = content;
56
+ manifest[relPath] = newHash;
57
+ continue;
58
+ }
59
+ skipped.push({
60
+ path: relPath,
61
+ reason: "file has been edited since last generation"
62
+ });
63
+ }
64
+ return { approvedWrites, skipped, manifest };
65
+ }
66
+ function summarize(result, projectRoot) {
67
+ const lines = [];
68
+ const writeCount = Object.keys(result.approvedWrites).length;
69
+ lines.push(`[ReactAppStarter] Approved ${writeCount} file(s) for writing.`);
70
+ if (result.skipped.length > 0) {
71
+ lines.push(`[ReactAppStarter] Skipped ${result.skipped.length} user-edited file(s):`);
72
+ for (const { path, reason } of result.skipped) {
73
+ lines.push(` - ${path} (${reason})`);
74
+ }
75
+ lines.push(
76
+ `To accept upstream regeneration for a skipped file, delete it (\`rm ${relative(process.cwd(), projectRoot)}/PATH\`) and re-run \`spv realize\`.`
77
+ );
78
+ }
79
+ return lines.join("\n");
80
+ }
81
+ export {
82
+ HASHES_DIR,
83
+ HASHES_FILE,
84
+ loadHashManifest,
85
+ reconcileWrites,
86
+ saveHashManifest,
87
+ sha256,
88
+ summarize
89
+ };
@@ -0,0 +1,56 @@
1
+ import { readFileSync } from "fs";
2
+ import { join, dirname } from "path";
3
+ import { fileURLToPath } from "url";
4
+ function emitView(context) {
5
+ const skeleton = loadSkeleton(context.view.type);
6
+ const bodyJsx = context.renderBody(context);
7
+ const substitutions = buildSubstitutions(context, bodyJsx);
8
+ return applySubstitutions(skeleton, substitutions);
9
+ }
10
+ const SKELETON_BY_VIEW_TYPE = {
11
+ list: "list.tsx.template",
12
+ detail: "detail.tsx.template",
13
+ form: "form.tsx.template",
14
+ dashboard: "dashboard.tsx.template"
15
+ // Specialist types (board, timeline, calendar, analytics, workflow,
16
+ // wizard, comparison, settings, map, feed, profile) come online in
17
+ // Phase 2e.
18
+ };
19
+ function loadSkeleton(viewType) {
20
+ const filename = SKELETON_BY_VIEW_TYPE[viewType.toLowerCase()];
21
+ if (!filename) {
22
+ throw new Error(
23
+ `No skeleton registered for view type "${viewType}". Known types: ${Object.keys(SKELETON_BY_VIEW_TYPE).join(", ")}.`
24
+ );
25
+ }
26
+ const here = dirname(fileURLToPath(import.meta.url));
27
+ const path = join(here, "skeletons", filename);
28
+ return readFileSync(path, "utf8");
29
+ }
30
+ function buildSubstitutions(context, body) {
31
+ const modelName = context.model.name;
32
+ const pluralModel = pluralize(modelName);
33
+ return {
34
+ MODEL_NAME: modelName,
35
+ PLURAL_MODEL: pluralModel,
36
+ PLURAL_LOWER: pluralModel.toLowerCase(),
37
+ SINGULAR_LOWER: modelName.toLowerCase(),
38
+ BODY: body
39
+ };
40
+ }
41
+ function applySubstitutions(template, subs) {
42
+ let out = template;
43
+ for (const [key, value] of Object.entries(subs)) {
44
+ const pattern = new RegExp(`\\{\\{${key}\\}\\}`, "g");
45
+ out = out.replace(pattern, value);
46
+ }
47
+ return out;
48
+ }
49
+ function pluralize(s) {
50
+ if (/[^aeiou]y$/i.test(s)) return s.slice(0, -1) + "ies";
51
+ if (/(s|x|z|ch|sh)$/i.test(s)) return s + "es";
52
+ return s + "s";
53
+ }
54
+ export {
55
+ emitView
56
+ };