@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.
- package/assets/examples/manifests/frontend-only.yaml +3 -6
- package/assets/examples/manifests/fullstack-app.yaml +5 -7
- package/assets/examples/manifests/fullstack-monorepo.yaml +3 -6
- package/dist/inference/comprehensive-engine.d.ts.map +1 -1
- package/dist/inference/comprehensive-engine.js +3 -19
- package/dist/inference/comprehensive-engine.js.map +1 -1
- package/dist/inference/core/rule-engine.d.ts +31 -0
- package/dist/inference/core/rule-engine.d.ts.map +1 -1
- package/dist/inference/core/rule-engine.js +117 -33
- package/dist/inference/core/rule-engine.js.map +1 -1
- package/dist/inference/core/rule-file-types.d.ts +0 -2
- package/dist/inference/core/rule-file-types.d.ts.map +1 -1
- package/dist/inference/core/rule-file-types.js +3 -6
- package/dist/inference/core/rule-file-types.js.map +1 -1
- package/dist/inference/core/rule-loader.d.ts +5 -15
- package/dist/inference/core/rule-loader.d.ts.map +1 -1
- package/dist/inference/core/rule-loader.js +43 -132
- package/dist/inference/core/rule-loader.js.map +1 -1
- package/dist/inference/core/types.d.ts +0 -6
- package/dist/inference/core/types.d.ts.map +1 -1
- package/dist/inference/core/types.js +0 -4
- package/dist/inference/core/types.js.map +1 -1
- package/dist/inference/logical/generators/component-type-resolver.d.ts +0 -26
- package/dist/inference/logical/generators/component-type-resolver.d.ts.map +1 -1
- package/dist/inference/logical/generators/component-type-resolver.js +0 -19
- package/dist/inference/logical/generators/component-type-resolver.js.map +1 -1
- package/dist/inference/logical/generators/specialist-view-expander.d.ts +1 -17
- package/dist/inference/logical/generators/specialist-view-expander.d.ts.map +1 -1
- package/dist/inference/logical/generators/specialist-view-expander.js +0 -15
- package/dist/inference/logical/generators/specialist-view-expander.js.map +1 -1
- package/dist/inference/logical/generators/view-generator.d.ts +4 -14
- package/dist/inference/logical/generators/view-generator.d.ts.map +1 -1
- package/dist/inference/logical/generators/view-generator.js +6 -26
- package/dist/inference/logical/generators/view-generator.js.map +1 -1
- package/dist/inference/logical/index.d.ts +2 -2
- package/dist/inference/logical/index.d.ts.map +1 -1
- package/dist/inference/logical/logical-engine.d.ts.map +1 -1
- package/dist/inference/logical/logical-engine.js +17 -80
- package/dist/inference/logical/logical-engine.js.map +1 -1
- package/dist/inference/quint-transpiler.d.ts +5 -3
- package/dist/inference/quint-transpiler.d.ts.map +1 -1
- package/dist/inference/quint-transpiler.js +11 -6
- package/dist/inference/quint-transpiler.js.map +1 -1
- package/dist/libs/instance-factories/applications/templates/generic/main-generator.js +3 -3
- package/dist/libs/instance-factories/applications/templates/react/api-client-generator.js +16 -6
- package/dist/libs/instance-factories/applications/templates/react-starter/app-tsx-generator.js +110 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.js +121 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/detail-body-composer.js +78 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/form-body-composer.js +190 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/helpers-emitter.js +45 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/html-to-jsx.js +192 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/list-body-composer.js +46 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/orchestrator.js +30 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/package-json-generator.js +38 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/regen-safety.js +89 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/view-emitter.js +56 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/views-generator.js +66 -0
- package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +14 -11
- package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +11 -3
- package/dist/libs/instance-factories/services/templates/prisma/controller-generator.js +27 -17
- package/dist/libs/instance-factories/shared/path-resolver.js +1 -1
- package/dist/realize/index.d.ts.map +1 -1
- package/dist/realize/index.js +15 -22
- package/dist/realize/index.js.map +1 -1
- package/dist/registry/utils/manifest-adapter.d.ts +8 -1
- package/dist/registry/utils/manifest-adapter.d.ts.map +1 -1
- package/dist/registry/utils/manifest-adapter.js +8 -1
- package/dist/registry/utils/manifest-adapter.js.map +1 -1
- package/libs/instance-factories/applications/react-app-starter.yaml +150 -0
- package/libs/instance-factories/applications/templates/generic/main-generator.ts +3 -3
- package/libs/instance-factories/applications/templates/react/api-client-generator.ts +16 -6
- package/libs/instance-factories/applications/templates/react-starter/README.md +211 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/dashboard-body-composer.test.ts +153 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/detail-body-composer.test.ts +145 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/form-body-composer.test.ts +175 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/helpers-emitter.test.ts +55 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/html-to-jsx.test.ts +140 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/list-body-composer.test.ts +146 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/orchestrator.test.ts +163 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/parity-p2-factory-imports.test.ts +116 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/parity-p3-rendered-output.test.ts +183 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/regen-safety.test.ts +144 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/starter-generators.test.ts +114 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/view-emitter.test.ts +107 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/views-generator.test.ts +139 -0
- package/libs/instance-factories/applications/templates/react-starter/app-tsx-generator.ts +141 -0
- package/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.ts +174 -0
- package/libs/instance-factories/applications/templates/react-starter/detail-body-composer.ts +135 -0
- package/libs/instance-factories/applications/templates/react-starter/form-body-composer.ts +306 -0
- package/libs/instance-factories/applications/templates/react-starter/helpers-emitter.ts +60 -0
- package/libs/instance-factories/applications/templates/react-starter/html-to-jsx.ts +334 -0
- package/libs/instance-factories/applications/templates/react-starter/list-body-composer.ts +120 -0
- package/libs/instance-factories/applications/templates/react-starter/orchestrator.ts +80 -0
- package/libs/instance-factories/applications/templates/react-starter/package-json-generator.ts +57 -0
- package/libs/instance-factories/applications/templates/react-starter/regen-safety.ts +157 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/dashboard.tsx.template +47 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/detail.tsx.template +94 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/form.tsx.template +114 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/list.tsx.template +72 -0
- package/libs/instance-factories/applications/templates/react-starter/view-emitter.ts +151 -0
- package/libs/instance-factories/applications/templates/react-starter/views-generator.ts +137 -0
- package/libs/instance-factories/cli/templates/commander/command-generator.ts +14 -11
- package/libs/instance-factories/controllers/templates/fastify/server-generator.ts +11 -3
- package/libs/instance-factories/services/templates/prisma/controller-generator.ts +27 -17
- package/libs/instance-factories/shared/path-resolver.ts +8 -2
- package/package.json +3 -3
- package/dist/libs/instance-factories/applications/templates/react/_view-components-source.js +0 -530
- package/dist/libs/instance-factories/applications/templates/react/app-tsx-generator.js +0 -73
- package/dist/libs/instance-factories/applications/templates/react/field-helpers-generator.js +0 -99
- package/dist/libs/instance-factories/applications/templates/react/package-json-generator.js +0 -49
- package/dist/libs/instance-factories/applications/templates/react/pattern-adapter-generator.js +0 -156
- package/dist/libs/instance-factories/applications/templates/react/react-pattern-adapter.js +0 -935
- package/dist/libs/instance-factories/applications/templates/react/relationship-field-generator.js +0 -143
- package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-generator.js +0 -646
- package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-wrapper-generator.js +0 -65
- package/dist/libs/instance-factories/applications/templates/react/view-dashboard-generator.js +0 -143
- package/dist/libs/instance-factories/applications/templates/react/view-detail-generator.js +0 -143
- package/dist/libs/instance-factories/applications/templates/react/view-form-generator.js +0 -355
- package/dist/libs/instance-factories/applications/templates/react/view-list-generator.js +0 -91
- package/dist/libs/instance-factories/applications/templates/react/view-router-generator.js +0 -79
- package/dist/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.js.bak +0 -244
- package/dist/libs/instance-factories/views/index.js +0 -48
- package/dist/libs/instance-factories/views/templates/react/adapters/antd-adapter.js +0 -742
- package/dist/libs/instance-factories/views/templates/react/adapters/mui-adapter.js +0 -824
- package/dist/libs/instance-factories/views/templates/react/adapters/shadcn-adapter.js +0 -719
- package/dist/libs/instance-factories/views/templates/react/app-generator.js +0 -45
- package/dist/libs/instance-factories/views/templates/react/components-generator.js +0 -820
- package/dist/libs/instance-factories/views/templates/react/forms-generator.js +0 -275
- package/dist/libs/instance-factories/views/templates/react/frontend-package-json-generator.js +0 -46
- package/dist/libs/instance-factories/views/templates/react/hooks-generator.js +0 -81
- package/dist/libs/instance-factories/views/templates/react/index-css-generator.js +0 -9
- package/dist/libs/instance-factories/views/templates/react/index-html-generator.js +0 -23
- package/dist/libs/instance-factories/views/templates/react/main-tsx-generator.js +0 -21
- package/dist/libs/instance-factories/views/templates/react/react-component-generator.js +0 -299
- package/dist/libs/instance-factories/views/templates/react/router-generator.js +0 -136
- package/dist/libs/instance-factories/views/templates/react/router-generic-generator.js +0 -107
- package/dist/libs/instance-factories/views/templates/react/shared-utils-generator.js +0 -187
- package/dist/libs/instance-factories/views/templates/react/spec-json-generator.js +0 -7
- package/dist/libs/instance-factories/views/templates/react/types-generator.js +0 -56
- package/dist/libs/instance-factories/views/templates/react/views-metadata-generator.js +0 -27
- package/dist/libs/instance-factories/views/templates/react/vite-config-generator.js +0 -29
- package/dist/libs/instance-factories/views/templates/runtime/runtime-view-renderer.js +0 -261
- package/dist/libs/instance-factories/views/templates/shared/adapter-types.js +0 -34
- package/dist/libs/instance-factories/views/templates/shared/atomic-components-registry.js +0 -800
- package/dist/libs/instance-factories/views/templates/shared/base-generator.js +0 -305
- package/dist/libs/instance-factories/views/templates/shared/component-metadata.js +0 -517
- package/dist/libs/instance-factories/views/templates/shared/composite-pattern-types.js +0 -0
- package/dist/libs/instance-factories/views/templates/shared/composite-patterns.js +0 -445
- package/dist/libs/instance-factories/views/templates/shared/index.js +0 -80
- package/dist/libs/instance-factories/views/templates/shared/pattern-validator.js +0 -210
- package/dist/libs/instance-factories/views/templates/shared/property-mapper.js +0 -492
- package/dist/libs/instance-factories/views/templates/shared/syntax-mapper.js +0 -321
- package/dist/realize/index.js.bak +0 -758
- package/libs/instance-factories/applications/react-app.yaml +0 -186
- package/libs/instance-factories/applications/templates/react/_view-components-source.ts +0 -555
- package/libs/instance-factories/applications/templates/react/app-tsx-generator.ts +0 -94
- package/libs/instance-factories/applications/templates/react/field-helpers-generator.ts +0 -106
- package/libs/instance-factories/applications/templates/react/package-json-generator.ts +0 -57
- package/libs/instance-factories/applications/templates/react/pattern-adapter-generator.ts +0 -179
- package/libs/instance-factories/applications/templates/react/react-pattern-adapter.tsx +0 -1347
- package/libs/instance-factories/applications/templates/react/relationship-field-generator.ts +0 -150
- package/libs/instance-factories/applications/templates/react/tailwind-adapter-generator.ts +0 -704
- package/libs/instance-factories/applications/templates/react/tailwind-adapter-wrapper-generator.ts +0 -84
- package/libs/instance-factories/applications/templates/react/view-dashboard-generator.ts +0 -150
- package/libs/instance-factories/applications/templates/react/view-detail-generator.ts +0 -150
- package/libs/instance-factories/applications/templates/react/view-form-generator.ts +0 -362
- package/libs/instance-factories/applications/templates/react/view-list-generator.ts +0 -98
- package/libs/instance-factories/applications/templates/react/view-router-generator.ts +0 -89
- package/libs/instance-factories/views/README.md +0 -62
- package/libs/instance-factories/views/index.d.ts +0 -13
- package/libs/instance-factories/views/index.d.ts.map +0 -1
- package/libs/instance-factories/views/index.js +0 -18
- package/libs/instance-factories/views/index.js.map +0 -1
- package/libs/instance-factories/views/index.ts +0 -45
- package/libs/instance-factories/views/react-components.yaml +0 -129
- package/libs/instance-factories/views/templates/ARCHITECTURE.md +0 -198
- package/libs/instance-factories/views/templates/react/adapters/antd-adapter.ts +0 -869
- package/libs/instance-factories/views/templates/react/adapters/mui-adapter.ts +0 -953
- package/libs/instance-factories/views/templates/react/adapters/shadcn-adapter.ts +0 -806
- package/libs/instance-factories/views/templates/react/app-generator.ts +0 -55
- package/libs/instance-factories/views/templates/react/components-generator.ts +0 -938
- package/libs/instance-factories/views/templates/react/forms-generator.ts +0 -325
- package/libs/instance-factories/views/templates/react/frontend-package-json-generator.ts +0 -57
- package/libs/instance-factories/views/templates/react/hooks-generator.ts +0 -106
- package/libs/instance-factories/views/templates/react/index-css-generator.ts +0 -14
- package/libs/instance-factories/views/templates/react/index-html-generator.ts +0 -34
- package/libs/instance-factories/views/templates/react/main-tsx-generator.ts +0 -29
- package/libs/instance-factories/views/templates/react/react-component-generator.d.ts +0 -152
- package/libs/instance-factories/views/templates/react/react-component-generator.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/react/react-component-generator.js +0 -398
- package/libs/instance-factories/views/templates/react/react-component-generator.js.map +0 -1
- package/libs/instance-factories/views/templates/react/react-component-generator.ts +0 -533
- package/libs/instance-factories/views/templates/react/router-generator.ts +0 -197
- package/libs/instance-factories/views/templates/react/router-generic-generator.ts +0 -132
- package/libs/instance-factories/views/templates/react/shared-utils-generator.ts +0 -196
- package/libs/instance-factories/views/templates/react/spec-json-generator.ts +0 -17
- package/libs/instance-factories/views/templates/react/types-generator.ts +0 -76
- package/libs/instance-factories/views/templates/react/views-metadata-generator.ts +0 -42
- package/libs/instance-factories/views/templates/react/vite-config-generator.ts +0 -38
- package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.js.map +0 -1
- package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.ts +0 -474
- package/libs/instance-factories/views/templates/shared/__tests__/composite-patterns.test.ts +0 -242
- package/libs/instance-factories/views/templates/shared/adapter-types.d.ts +0 -77
- package/libs/instance-factories/views/templates/shared/adapter-types.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/shared/adapter-types.js +0 -47
- package/libs/instance-factories/views/templates/shared/adapter-types.js.map +0 -1
- package/libs/instance-factories/views/templates/shared/adapter-types.ts +0 -142
- package/libs/instance-factories/views/templates/shared/atomic-components-registry.d.ts +0 -63
- package/libs/instance-factories/views/templates/shared/atomic-components-registry.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/shared/atomic-components-registry.js +0 -822
- package/libs/instance-factories/views/templates/shared/atomic-components-registry.js.map +0 -1
- package/libs/instance-factories/views/templates/shared/atomic-components-registry.ts +0 -908
- package/libs/instance-factories/views/templates/shared/base-generator.d.ts +0 -247
- package/libs/instance-factories/views/templates/shared/base-generator.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/shared/base-generator.js +0 -363
- package/libs/instance-factories/views/templates/shared/base-generator.js.map +0 -1
- package/libs/instance-factories/views/templates/shared/base-generator.ts +0 -608
- package/libs/instance-factories/views/templates/shared/component-metadata.d.ts +0 -254
- package/libs/instance-factories/views/templates/shared/component-metadata.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/shared/component-metadata.js +0 -602
- package/libs/instance-factories/views/templates/shared/component-metadata.js.map +0 -1
- package/libs/instance-factories/views/templates/shared/component-metadata.ts +0 -803
- package/libs/instance-factories/views/templates/shared/composite-pattern-types.ts +0 -250
- package/libs/instance-factories/views/templates/shared/composite-patterns.ts +0 -535
- package/libs/instance-factories/views/templates/shared/index.ts +0 -68
- package/libs/instance-factories/views/templates/shared/pattern-validator.ts +0 -279
- package/libs/instance-factories/views/templates/shared/property-mapper.d.ts +0 -149
- package/libs/instance-factories/views/templates/shared/property-mapper.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/shared/property-mapper.js +0 -580
- package/libs/instance-factories/views/templates/shared/property-mapper.js.map +0 -1
- package/libs/instance-factories/views/templates/shared/property-mapper.ts +0 -700
- package/libs/instance-factories/views/templates/shared/syntax-mapper.d.ts +0 -143
- package/libs/instance-factories/views/templates/shared/syntax-mapper.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/shared/syntax-mapper.js +0 -420
- package/libs/instance-factories/views/templates/shared/syntax-mapper.js.map +0 -1
- 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 || '
|
|
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 || '
|
|
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 || '
|
|
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
|
-
'
|
|
379
|
-
\`/\${resource}/\${entityId}/
|
|
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
|
-
'
|
|
486
|
-
|
|
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
|
+
});
|