@specverse/engines 4.1.30 → 4.2.1
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/assets/templates/default/specs/main.specly +65 -0
- 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/CURVED-INTERFACE.md +278 -0
- package/dist/libs/instance-factories/README.md +73 -0
- package/dist/libs/instance-factories/applications/README.md +51 -0
- package/dist/libs/instance-factories/applications/generic-app.yaml +52 -0
- package/{libs/instance-factories/applications/react-app.yaml → dist/libs/instance-factories/applications/react-app-runtime.yaml} +30 -77
- package/dist/libs/instance-factories/applications/react-app-starter.yaml +143 -0
- package/dist/libs/instance-factories/applications/templates/react/env-example-generator.js +24 -2
- package/dist/libs/instance-factories/applications/templates/react/vite-config-generator.js +54 -33
- package/dist/libs/instance-factories/applications/templates/react-starter/README.md +211 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/api-types-starter-generator.js +69 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/app-tsx-generator.js +110 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/belongs-to.js +40 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.js +129 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/detail-body-composer.js +80 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/form-body-composer.js +217 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/helpers-emitter.js +51 -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 +56 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/orchestrator.js +41 -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/skeletons/dashboard.tsx.template +49 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/detail.tsx.template +96 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/form.tsx.template +116 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/list.tsx.template +74 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/use-api-hooks-starter-generator.js +95 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/view-emitter.js +81 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/views-generator.js +66 -0
- package/dist/libs/instance-factories/archived/fastify-prisma.yaml +104 -0
- package/dist/libs/instance-factories/cli/README.md +43 -0
- package/dist/libs/instance-factories/cli/commander-js.yaml +55 -0
- package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +63 -12
- package/dist/libs/instance-factories/communication/README.md +47 -0
- package/dist/libs/instance-factories/communication/event-emitter.yaml +60 -0
- package/dist/libs/instance-factories/communication/rabbitmq-events.yaml +87 -0
- package/dist/libs/instance-factories/controllers/README.md +42 -0
- package/dist/libs/instance-factories/controllers/fastify.yaml +139 -0
- package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +29 -2
- package/dist/libs/instance-factories/infrastructure/README.md +29 -0
- package/dist/libs/instance-factories/infrastructure/docker-k8s.yaml +61 -0
- package/dist/libs/instance-factories/orms/README.md +54 -0
- package/dist/libs/instance-factories/orms/prisma.yaml +89 -0
- package/dist/libs/instance-factories/orms/templates/prisma/schema-generator.js +2 -2
- package/dist/libs/instance-factories/scaffolding/README.md +49 -0
- package/dist/libs/instance-factories/scaffolding/generic-scaffold.yaml +65 -0
- package/dist/libs/instance-factories/sdks/README.md +28 -0
- package/dist/libs/instance-factories/sdks/python-sdk.yaml +66 -0
- package/dist/libs/instance-factories/sdks/typescript-sdk.yaml +59 -0
- package/dist/libs/instance-factories/services/README.md +55 -0
- package/dist/libs/instance-factories/services/prisma-services.yaml +71 -0
- package/dist/libs/instance-factories/storage/README.md +34 -0
- package/dist/libs/instance-factories/storage/mongodb.yaml +79 -0
- package/dist/libs/instance-factories/storage/postgresql.yaml +75 -0
- package/dist/libs/instance-factories/storage/redis.yaml +79 -0
- package/dist/libs/instance-factories/testing/README.md +40 -0
- package/dist/libs/instance-factories/testing/vitest-tests.yaml +63 -0
- package/dist/libs/instance-factories/tools/README.md +70 -0
- package/dist/libs/instance-factories/tools/mcp.yaml +36 -0
- package/dist/libs/instance-factories/tools/vscode.yaml +35 -0
- package/dist/libs/instance-factories/validation/README.md +38 -0
- package/dist/libs/instance-factories/validation/zod.yaml +56 -0
- package/dist/realize/engines/code-generator.d.ts.map +1 -1
- package/dist/realize/engines/code-generator.js +3 -0
- package/dist/realize/engines/code-generator.js.map +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 +143 -0
- package/libs/instance-factories/applications/templates/react/env-example-generator.ts +24 -2
- package/libs/instance-factories/applications/templates/react/vite-config-generator.ts +54 -33
- 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 +146 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/form-body-composer.test.ts +188 -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 +184 -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/api-types-starter-generator.ts +98 -0
- package/libs/instance-factories/applications/templates/react-starter/app-tsx-generator.ts +141 -0
- package/libs/instance-factories/applications/templates/react-starter/belongs-to.ts +82 -0
- package/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.ts +189 -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 +383 -0
- package/libs/instance-factories/applications/templates/react-starter/helpers-emitter.ts +66 -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 +146 -0
- package/libs/instance-factories/applications/templates/react-starter/orchestrator.ts +95 -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 +49 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/detail.tsx.template +96 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/form.tsx.template +116 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/list.tsx.template +74 -0
- package/libs/instance-factories/applications/templates/react-starter/use-api-hooks-starter-generator.ts +124 -0
- package/libs/instance-factories/applications/templates/react-starter/view-emitter.ts +209 -0
- package/libs/instance-factories/applications/templates/react-starter/views-generator.ts +137 -0
- package/libs/instance-factories/cli/templates/commander/command-generator.ts +63 -12
- package/libs/instance-factories/controllers/fastify.yaml +7 -0
- package/libs/instance-factories/controllers/templates/fastify/server-generator.ts +36 -2
- package/libs/instance-factories/orms/templates/prisma/schema-generator.ts +11 -4
- 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/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/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,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
|
+
};
|
package/dist/libs/instance-factories/applications/templates/react-starter/list-body-composer.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
import { buildFKMap } from "./belongs-to.js";
|
|
5
|
+
const TBODY_SENTINEL = "__SPECVERSE_TBODY_ROWS__";
|
|
6
|
+
function composeListBody(context) {
|
|
7
|
+
const fkMap = buildFKMap(context.model);
|
|
8
|
+
const inferred = inferColumns(context);
|
|
9
|
+
const inferredSet = new Set(inferred);
|
|
10
|
+
const extraFKs = [...fkMap.keys()].filter((k) => !inferredSet.has(k));
|
|
11
|
+
const columns = [...inferred, ...extraFKs];
|
|
12
|
+
const headers = columns.map((col) => {
|
|
13
|
+
const fk = fkMap.get(col);
|
|
14
|
+
return humanize(fk ? fk.name : col);
|
|
15
|
+
});
|
|
16
|
+
const adapter = createUniversalTailwindAdapter({ darkMode: true });
|
|
17
|
+
const shellHtml = adapter.components.table.render({
|
|
18
|
+
properties: { columns: headers },
|
|
19
|
+
children: TBODY_SENTINEL
|
|
20
|
+
});
|
|
21
|
+
const shellJsx = htmlToJsx(shellHtml);
|
|
22
|
+
const rowMap = buildRowMap(columns, fkMap);
|
|
23
|
+
if (!shellJsx.includes(TBODY_SENTINEL)) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
"composeListBody: tbody sentinel not present in adapter output. The canonical Tailwind adapter may have changed its table rendering."
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return shellJsx.replace(TBODY_SENTINEL, rowMap);
|
|
29
|
+
}
|
|
30
|
+
function inferColumns(context) {
|
|
31
|
+
return inferFieldsFromSchema(context.modelSchemas, context.model.name);
|
|
32
|
+
}
|
|
33
|
+
function humanize(name) {
|
|
34
|
+
return name.replace(/([A-Z])/g, " $1").replace(/^./, (c) => c.toUpperCase()).trim();
|
|
35
|
+
}
|
|
36
|
+
function buildRowMap(columns, fkMap) {
|
|
37
|
+
const cells = columns.map((col) => {
|
|
38
|
+
const fk = fkMap.get(col);
|
|
39
|
+
const expr = fk ? `{resolveEntityDisplayName((item as any).${col}, ${fk.name}Options)}` : `{String((item as any).${col} ?? '')}`;
|
|
40
|
+
return ` <td className="px-6 py-4 text-sm text-gray-700 dark:text-gray-300">${expr}</td>`;
|
|
41
|
+
}).join("\n");
|
|
42
|
+
return [
|
|
43
|
+
"{filtered.map((item, idx) => (",
|
|
44
|
+
" <tr",
|
|
45
|
+
" key={idx}",
|
|
46
|
+
" onClick={() => onSelect?.(item)}",
|
|
47
|
+
' className="hover:bg-gray-50 dark:hover:bg-gray-800 cursor-pointer"',
|
|
48
|
+
" >",
|
|
49
|
+
cells,
|
|
50
|
+
" </tr>",
|
|
51
|
+
"))}"
|
|
52
|
+
].join("\n");
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
composeListBody
|
|
56
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { generate as generateViews } from "./views-generator.js";
|
|
4
|
+
import { generate as generateAppTsx } from "./app-tsx-generator.js";
|
|
5
|
+
import { generate as generatePackageJson } from "./package-json-generator.js";
|
|
6
|
+
import {
|
|
7
|
+
loadHashManifest,
|
|
8
|
+
reconcileWrites,
|
|
9
|
+
summarize,
|
|
10
|
+
HASHES_DIR,
|
|
11
|
+
HASHES_FILE
|
|
12
|
+
} from "./regen-safety.js";
|
|
13
|
+
async function generate(context) {
|
|
14
|
+
const { spec, outputDir } = context;
|
|
15
|
+
const frontendDir = context.frontendDir || "frontend";
|
|
16
|
+
const projectRoot = frontendDir === "." ? outputDir : join(outputDir, frontendDir);
|
|
17
|
+
const proposed = {};
|
|
18
|
+
const viewFiles = await generateViews({ spec });
|
|
19
|
+
Object.assign(proposed, viewFiles);
|
|
20
|
+
proposed["src/App.tsx"] = await generateAppTsx({ spec });
|
|
21
|
+
proposed["package.json"] = await generatePackageJson({ spec });
|
|
22
|
+
const prevManifest = loadHashManifest(projectRoot);
|
|
23
|
+
const result = reconcileWrites(projectRoot, proposed, prevManifest);
|
|
24
|
+
console.log(summarize(result, projectRoot));
|
|
25
|
+
const toWrite = {
|
|
26
|
+
...result.approvedWrites,
|
|
27
|
+
[`${HASHES_DIR}/${HASHES_FILE}`]: JSON.stringify(result.manifest, null, 2) + "\n"
|
|
28
|
+
};
|
|
29
|
+
for (const [rel, content] of Object.entries(toWrite)) {
|
|
30
|
+
const abs = join(projectRoot, rel);
|
|
31
|
+
const dir = dirname(abs);
|
|
32
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
33
|
+
writeFileSync(abs, content, "utf-8");
|
|
34
|
+
}
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
var stdin_default = generate;
|
|
38
|
+
export {
|
|
39
|
+
stdin_default as default,
|
|
40
|
+
generate
|
|
41
|
+
};
|
package/dist/libs/instance-factories/applications/templates/react-starter/package-json-generator.js
ADDED
|
@@ -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,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{MODEL_NAME}}DashboardView — generated by @specverse/realize (ReactAppStarter)
|
|
3
|
+
*
|
|
4
|
+
* Safe to edit. Edits are preserved across regeneration via content
|
|
5
|
+
* hashing (see .specverse-gen/hashes.json). To accept an upstream
|
|
6
|
+
* regeneration of this file, delete it first, then run `spv realize`.
|
|
7
|
+
*
|
|
8
|
+
* A minimal dashboard: summary counts derived from the list query,
|
|
9
|
+
* plus a compact preview of recent records. Charts and aggregation
|
|
10
|
+
* metrics are deferred — add them as the backend grows suitable
|
|
11
|
+
* endpoints.
|
|
12
|
+
*/
|
|
13
|
+
import { useMemo } from 'react';
|
|
14
|
+
import { use{{PLURAL_MODEL}}Query } from '../hooks/useApi';
|
|
15
|
+
{{RELATED_IMPORTS}}
|
|
16
|
+
import type { {{MODEL_NAME}} } from '../types/api';
|
|
17
|
+
|
|
18
|
+
interface {{MODEL_NAME}}DashboardViewProps {
|
|
19
|
+
/** Number of recent records to preview. Default 5. */
|
|
20
|
+
previewLimit?: number;
|
|
21
|
+
onSelect?: (item: {{MODEL_NAME}}) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function {{MODEL_NAME}}DashboardView({
|
|
25
|
+
previewLimit = 5,
|
|
26
|
+
onSelect,
|
|
27
|
+
}: {{MODEL_NAME}}DashboardViewProps) {
|
|
28
|
+
const { data: items = [], isLoading, error } = use{{PLURAL_MODEL}}Query();
|
|
29
|
+
{{RELATED_HOOKS}}
|
|
30
|
+
|
|
31
|
+
const preview = useMemo(
|
|
32
|
+
() => items.slice(0, previewLimit),
|
|
33
|
+
[items, previewLimit]
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
if (isLoading) return <div className="p-4 text-gray-500">Loading dashboard…</div>;
|
|
37
|
+
if (error) return <div className="p-4 text-red-600">Error loading {{PLURAL_LOWER}}: {String(error)}</div>;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className="p-6 space-y-6">
|
|
41
|
+
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
|
42
|
+
{{MODEL_NAME}} dashboard
|
|
43
|
+
</h2>
|
|
44
|
+
|
|
45
|
+
{/* Pattern-rendered dashboard body (metrics + preview). Edit freely. */}
|
|
46
|
+
{{BODY}}
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{MODEL_NAME}}DetailView — generated by @specverse/realize (ReactAppStarter)
|
|
3
|
+
*
|
|
4
|
+
* Safe to edit. Edits are preserved across regeneration via content
|
|
5
|
+
* hashing (see .specverse-gen/hashes.json). To accept an upstream
|
|
6
|
+
* regeneration of this file, delete it first, then run `spv realize`.
|
|
7
|
+
*/
|
|
8
|
+
import { use{{PLURAL_MODEL}}Query, useDelete{{MODEL_NAME}}Mutation } from '../hooks/useApi';
|
|
9
|
+
{{RELATED_IMPORTS}}
|
|
10
|
+
import type { {{MODEL_NAME}} } from '../types/api';
|
|
11
|
+
|
|
12
|
+
interface {{MODEL_NAME}}DetailViewProps {
|
|
13
|
+
entityId: string | number;
|
|
14
|
+
onEdit?: (item: {{MODEL_NAME}}) => void;
|
|
15
|
+
onBack?: () => void;
|
|
16
|
+
/** Called after the delete mutation succeeds. */
|
|
17
|
+
onDeleted?: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function {{MODEL_NAME}}DetailView({
|
|
21
|
+
entityId,
|
|
22
|
+
onEdit,
|
|
23
|
+
onBack,
|
|
24
|
+
onDeleted,
|
|
25
|
+
}: {{MODEL_NAME}}DetailViewProps) {
|
|
26
|
+
const { data: items = [], isLoading, error } = use{{PLURAL_MODEL}}Query();
|
|
27
|
+
const deleteItem = useDelete{{MODEL_NAME}}Mutation();
|
|
28
|
+
{{RELATED_HOOKS}}
|
|
29
|
+
|
|
30
|
+
const item = items.find(
|
|
31
|
+
(x: {{MODEL_NAME}}) => (x as any).id === entityId
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if (isLoading) return <div className="p-4 text-gray-500">Loading…</div>;
|
|
35
|
+
if (error) return <div className="p-4 text-red-600">Error loading {{PLURAL_LOWER}}: {String(error)}</div>;
|
|
36
|
+
if (!item) return <div className="p-4 text-gray-400">No {{SINGULAR_LOWER}} matching id {String(entityId)}.</div>;
|
|
37
|
+
|
|
38
|
+
const handleDelete = async () => {
|
|
39
|
+
if (!confirm('Delete this {{SINGULAR_LOWER}}?')) return;
|
|
40
|
+
try {
|
|
41
|
+
await deleteItem.mutateAsync((item as any).id);
|
|
42
|
+
onDeleted?.();
|
|
43
|
+
} catch {
|
|
44
|
+
// deleteItem.error is surfaced below
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className="p-6 space-y-4">
|
|
50
|
+
<div className="flex items-center justify-between">
|
|
51
|
+
<div className="flex items-center gap-3">
|
|
52
|
+
{onBack && (
|
|
53
|
+
<button
|
|
54
|
+
type="button"
|
|
55
|
+
onClick={onBack}
|
|
56
|
+
className="text-sm text-gray-500 hover:text-gray-700"
|
|
57
|
+
>
|
|
58
|
+
← Back
|
|
59
|
+
</button>
|
|
60
|
+
)}
|
|
61
|
+
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
|
62
|
+
{{MODEL_NAME}} detail
|
|
63
|
+
</h2>
|
|
64
|
+
</div>
|
|
65
|
+
<div className="flex gap-2">
|
|
66
|
+
{onEdit && (
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
onClick={() => onEdit(item)}
|
|
70
|
+
className="rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
|
|
71
|
+
>
|
|
72
|
+
Edit
|
|
73
|
+
</button>
|
|
74
|
+
)}
|
|
75
|
+
<button
|
|
76
|
+
type="button"
|
|
77
|
+
onClick={handleDelete}
|
|
78
|
+
disabled={deleteItem.isPending}
|
|
79
|
+
className="rounded bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-700 disabled:opacity-60"
|
|
80
|
+
>
|
|
81
|
+
{deleteItem.isPending ? 'Deleting…' : 'Delete'}
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
{/* Pattern-rendered detail body (fields). Edit freely. */}
|
|
87
|
+
{{BODY}}
|
|
88
|
+
|
|
89
|
+
{deleteItem.isError && (
|
|
90
|
+
<div className="p-2 text-sm text-red-600">
|
|
91
|
+
Delete failed: {String(deleteItem.error)}
|
|
92
|
+
</div>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{MODEL_NAME}}FormView — generated by @specverse/realize (ReactAppStarter)
|
|
3
|
+
*
|
|
4
|
+
* Safe to edit. Edits are preserved across regeneration via content
|
|
5
|
+
* hashing (see .specverse-gen/hashes.json). To accept an upstream
|
|
6
|
+
* regeneration of this file, delete it first, then run `spv realize`.
|
|
7
|
+
*
|
|
8
|
+
* Controlled form. One field per non-auto-generated attribute.
|
|
9
|
+
* Mode: 'create' (default) or 'update' — passed as a prop.
|
|
10
|
+
*/
|
|
11
|
+
import { useEffect, useState } from 'react';
|
|
12
|
+
import {
|
|
13
|
+
use{{PLURAL_MODEL}}Query,
|
|
14
|
+
useCreate{{MODEL_NAME}}Mutation,
|
|
15
|
+
useUpdate{{MODEL_NAME}}Mutation,
|
|
16
|
+
} from '../hooks/useApi';
|
|
17
|
+
{{RELATED_IMPORTS}}
|
|
18
|
+
import type { {{MODEL_NAME}} } from '../types/api';
|
|
19
|
+
|
|
20
|
+
type FormMode = 'create' | 'update';
|
|
21
|
+
|
|
22
|
+
interface {{MODEL_NAME}}FormViewProps {
|
|
23
|
+
mode?: FormMode;
|
|
24
|
+
/** Required in update mode. */
|
|
25
|
+
entityId?: string | number;
|
|
26
|
+
onSuccess?: (item: {{MODEL_NAME}}) => void;
|
|
27
|
+
onCancel?: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function {{MODEL_NAME}}FormView({
|
|
31
|
+
mode = 'create',
|
|
32
|
+
entityId,
|
|
33
|
+
onSuccess,
|
|
34
|
+
onCancel,
|
|
35
|
+
}: {{MODEL_NAME}}FormViewProps) {
|
|
36
|
+
const { data: items = [] } = use{{PLURAL_MODEL}}Query();
|
|
37
|
+
const createItem = useCreate{{MODEL_NAME}}Mutation();
|
|
38
|
+
const updateItem = useUpdate{{MODEL_NAME}}Mutation();
|
|
39
|
+
{{RELATED_HOOKS}}
|
|
40
|
+
|
|
41
|
+
const existing =
|
|
42
|
+
mode === 'update'
|
|
43
|
+
? items.find((x: {{MODEL_NAME}}) => (x as any).id === entityId)
|
|
44
|
+
: undefined;
|
|
45
|
+
|
|
46
|
+
const [formData, setFormData] = useState<Partial<{{MODEL_NAME}}>>(existing ?? {});
|
|
47
|
+
|
|
48
|
+
// When the fetched list lands after initial render (update mode),
|
|
49
|
+
// hydrate the form with the loaded entity's data.
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (existing) setFormData(existing);
|
|
52
|
+
}, [existing]);
|
|
53
|
+
|
|
54
|
+
const handleChange = (field: string, value: unknown) => {
|
|
55
|
+
setFormData(prev => ({ ...prev, [field]: value }) as Partial<{{MODEL_NAME}}>);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handleSubmit = async (event: React.FormEvent) => {
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
try {
|
|
61
|
+
const result =
|
|
62
|
+
mode === 'create'
|
|
63
|
+
? await createItem.mutateAsync(formData as {{MODEL_NAME}})
|
|
64
|
+
: await updateItem.mutateAsync({
|
|
65
|
+
id: entityId as any,
|
|
66
|
+
data: formData as Partial<{{MODEL_NAME}}>,
|
|
67
|
+
});
|
|
68
|
+
onSuccess?.(result as {{MODEL_NAME}});
|
|
69
|
+
} catch {
|
|
70
|
+
// Mutation errors are surfaced below via createItem / updateItem.
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const mutation = mode === 'create' ? createItem : updateItem;
|
|
75
|
+
const submitLabel =
|
|
76
|
+
mode === 'create'
|
|
77
|
+
? mutation.isPending ? 'Creating…' : 'Create {{MODEL_NAME}}'
|
|
78
|
+
: mutation.isPending ? 'Updating…' : 'Update {{MODEL_NAME}}';
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<form onSubmit={handleSubmit} className="p-6 space-y-6">
|
|
82
|
+
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">
|
|
83
|
+
{mode === 'create' ? 'New {{MODEL_NAME}}' : 'Edit {{MODEL_NAME}}'}
|
|
84
|
+
</h2>
|
|
85
|
+
|
|
86
|
+
{/* Pattern-rendered form fields. Edit freely. */}
|
|
87
|
+
{{BODY}}
|
|
88
|
+
|
|
89
|
+
<div className="flex gap-2 border-t border-gray-200 dark:border-gray-700 pt-4">
|
|
90
|
+
<button
|
|
91
|
+
type="submit"
|
|
92
|
+
disabled={mutation.isPending}
|
|
93
|
+
className="rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-60"
|
|
94
|
+
>
|
|
95
|
+
{submitLabel}
|
|
96
|
+
</button>
|
|
97
|
+
{onCancel && (
|
|
98
|
+
<button
|
|
99
|
+
type="button"
|
|
100
|
+
onClick={onCancel}
|
|
101
|
+
className="rounded border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-200 dark:hover:bg-gray-800"
|
|
102
|
+
>
|
|
103
|
+
Cancel
|
|
104
|
+
</button>
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
{mutation.isError && (
|
|
109
|
+
<div className="rounded border border-red-200 bg-red-50 p-3 text-sm text-red-700 dark:border-red-800 dark:bg-red-950 dark:text-red-300">
|
|
110
|
+
{mode === 'create' ? 'Create failed: ' : 'Update failed: '}
|
|
111
|
+
{String(mutation.error)}
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
</form>
|
|
115
|
+
);
|
|
116
|
+
}
|