cabloy 5.1.108 → 5.1.110
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/.cabloy-version +1 -1
- package/.gitignore +5 -2
- package/CHANGELOG.md +43 -0
- package/cabloy-docs/.vitepress/config.mjs +1 -0
- package/cabloy-docs/ai/playbook-module-removal.md +1 -1
- package/cabloy-docs/ai/virtual-decorator-guidance.md +9 -9
- package/cabloy-docs/backend/websocket-call-flow.md +1 -1
- package/cabloy-docs/backend/websocket-guide.md +1 -1
- package/cabloy-docs/frontend/fetch-interceptor-guide.md +8 -8
- package/cabloy-docs/frontend/form-guide.md +3 -1
- package/cabloy-docs/frontend/form-layout-guide.md +322 -0
- package/cabloy-docs/frontend/introduction.md +1 -0
- package/cabloy-docs/frontend/resource-entry-page-deep-dive.md +2 -0
- package/cabloy-docs/frontend/router-tabs-layout-integration.md +2 -1
- package/cabloy-docs/frontend/scripts.md +56 -1
- package/cabloy-docs/frontend/ssr-build-deploy-guide.md +1 -1
- package/cabloy-docs/frontend/ssr-overview.md +6 -0
- package/cabloy-docs/frontend/ssr-review-checklist.md +1 -1
- package/cabloy-docs/frontend/ssr-troubleshooting-guide.md +9 -9
- package/cabloy-docs/frontend/table-resource-crud-cookbook.md +33 -1
- package/cabloy-docs/frontend/zova-form-source-reading-map.md +36 -4
- package/cabloy-docs/frontend/zova-form-under-the-hood.md +13 -0
- package/cabloy-docs/fullstack/framework-performance.md +3 -3
- package/cabloy-docs/fullstack/quickstart.md +14 -0
- package/cabloy-docs/reference/repo-scripts.md +79 -0
- package/e2e/config/playwright.basic.config.ts +3 -0
- package/e2e/config/playwright.commerce.config.ts +3 -0
- package/e2e/config/playwright.shared.config.ts +38 -0
- package/e2e/scripts/e2e.ts +33 -0
- package/e2e/scripts/startE2eVona.ts +38 -0
- package/e2e/scripts/testE2eClean.ts +56 -0
- package/e2e/specs/a-basic/basic.spec.ts +271 -0
- package/e2e/specs/a-commerce/commerce.spec.ts +61 -0
- package/package.json +17 -1
- package/scripts/upgrade.ts +182 -7
- package/test-results/.last-run.json +4 -0
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/pnpm-lock.yaml +304 -45
- package/vona/src/suite/a-commerce/modules/commerce-catalog/package.json +52 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +29 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/this.ts +2 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/index.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/tsconfig.build.json +11 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/tsconfig.json +7 -0
- package/vona/src/suite/a-commerce/modules/commerce-member/package.json +52 -0
- package/vona/src/suite/a-commerce/modules/commerce-member/src/.metadata/index.ts +29 -0
- package/vona/src/suite/a-commerce/modules/commerce-member/src/.metadata/this.ts +2 -0
- package/vona/src/suite/a-commerce/modules/commerce-member/src/index.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-member/tsconfig.build.json +11 -0
- package/vona/src/suite/a-commerce/modules/commerce-member/tsconfig.json +7 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/package.json +52 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/.metadata/index.ts +29 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/.metadata/this.ts +2 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/index.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/tsconfig.build.json +11 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/tsconfig.json +7 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/package.json +52 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/index.ts +29 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/this.ts +2 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/index.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/tsconfig.build.json +11 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/tsconfig.json +7 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteadmin/package.json +53 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/index.ts +81 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/this.ts +2 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/bean/ssrMenu.home.ts +21 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/bean/ssrSite.commerceAdmin.ts +34 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/index.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteadmin/tsconfig.build.json +11 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteadmin/tsconfig.json +7 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteweb/package.json +53 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/.metadata/index.ts +81 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/.metadata/this.ts +2 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/bean/ssrMenu.home.ts +20 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/bean/ssrSite.commerce.ts +34 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/index.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteweb/tsconfig.build.json +11 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteweb/tsconfig.json +7 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/package.json +52 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +29 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/this.ts +2 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/index.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/tsconfig.build.json +11 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/tsconfig.json +7 -0
- package/vona/src/suite/a-commerce/package.json +18 -0
- package/vona/src/suite/a-commerce/tsconfig.base.json +4 -0
- package/vona/src/suite/a-commerce/tsconfig.json +28 -0
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +2 -0
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +2 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +50 -1
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectResItem.tsx +21 -1
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +47 -1
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +47 -1
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +61 -2
- package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/bean/bean.database.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/bean/schedule.softDeletionPrune.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/bean.model/bean.model_meta.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoAggregate.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoCreate.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoGroup.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoSelectAndCount.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoUpdate.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/modelCacheBase.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/relations.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/relationsDynamic.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/relationsMutate.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/relationsStatic.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/database.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/databaseDialectBase_.ts +10 -3
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/db_.ts +1 -1
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/env/.env.cabloyCommerce +24 -0
- package/zova/env/.env.cabloyCommerceAdmin +19 -0
- package/zova/package.original.json +10 -1
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +145 -31
- package/zova/src/front/config/config/config.cabloyCommerce.ts +16 -0
- package/zova/src/front/config/config/config.cabloyCommerceAdmin.ts +13 -0
- package/zova/src/suite/a-commerce/modules/commerce-catalog/package.json +52 -0
- package/zova/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +26 -0
- package/zova/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/this.ts +2 -0
- package/zova/src/suite/a-commerce/modules/commerce-catalog/src/index.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-catalog/tsconfig.build.json +13 -0
- package/zova/src/suite/a-commerce/modules/commerce-catalog/tsconfig.json +5 -0
- package/zova/src/suite/a-commerce/modules/commerce-member/package.json +52 -0
- package/zova/src/suite/a-commerce/modules/commerce-member/src/.metadata/index.ts +26 -0
- package/zova/src/suite/a-commerce/modules/commerce-member/src/.metadata/this.ts +2 -0
- package/zova/src/suite/a-commerce/modules/commerce-member/src/index.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-member/tsconfig.build.json +13 -0
- package/zova/src/suite/a-commerce/modules/commerce-member/tsconfig.json +5 -0
- package/zova/src/suite/a-commerce/modules/commerce-payment/package.json +52 -0
- package/zova/src/suite/a-commerce/modules/commerce-payment/src/.metadata/index.ts +26 -0
- package/zova/src/suite/a-commerce/modules/commerce-payment/src/.metadata/this.ts +2 -0
- package/zova/src/suite/a-commerce/modules/commerce-payment/src/index.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-payment/tsconfig.build.json +13 -0
- package/zova/src/suite/a-commerce/modules/commerce-payment/tsconfig.json +5 -0
- package/zova/src/suite/a-commerce/modules/commerce-promotion/package.json +52 -0
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/index.ts +26 -0
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/this.ts +2 -0
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/index.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-promotion/tsconfig.build.json +13 -0
- package/zova/src/suite/a-commerce/modules/commerce-promotion/tsconfig.json +5 -0
- package/zova/src/suite/a-commerce/modules/commerce-siteadmin/package.json +52 -0
- package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/index.ts +26 -0
- package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/this.ts +2 -0
- package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/index.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-siteadmin/tsconfig.build.json +13 -0
- package/zova/src/suite/a-commerce/modules/commerce-siteadmin/tsconfig.json +5 -0
- package/zova/src/suite/a-commerce/modules/commerce-siteweb/package.json +52 -0
- package/zova/src/suite/a-commerce/modules/commerce-siteweb/src/.metadata/index.ts +26 -0
- package/zova/src/suite/a-commerce/modules/commerce-siteweb/src/.metadata/this.ts +2 -0
- package/zova/src/suite/a-commerce/modules/commerce-siteweb/src/index.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-siteweb/tsconfig.build.json +13 -0
- package/zova/src/suite/a-commerce/modules/commerce-siteweb/tsconfig.json +5 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/package.json +52 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +26 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/this.ts +2 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/index.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/tsconfig.build.json +13 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/tsconfig.json +5 -0
- package/zova/src/suite/a-commerce/package.json +18 -0
- package/zova/src/suite/a-commerce/tsconfig.base.json +4 -0
- package/zova/src/suite/a-commerce/tsconfig.json +4 -0
- package/zova/src/suite/cabloy-basic/modules/basic-form/src/.metadata/component/blockFormLayout.ts +31 -0
- package/zova/src/suite/cabloy-basic/modules/basic-form/src/.metadata/index.ts +13 -0
- package/zova/src/suite/cabloy-basic/modules/basic-form/src/component/blockFormLayout/controller.tsx +188 -0
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/.metadata/component/blockFilterActions.ts +31 -0
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/.metadata/index.ts +13 -0
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockFilter/controller.tsx +61 -31
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockFilterActions/controller.tsx +51 -0
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/types/page.ts +10 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockForm/controller.tsx +9 -2
- package/zova/src/suite-vendor/a-zova/modules/a-app/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-app/src/component/app/controller.tsx +6 -0
- package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +5 -3
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/form/controller.tsx +44 -3
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/form/render.tsx +22 -4
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/formField/controller.tsx +3 -2
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts +203 -0
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/index.ts +1 -0
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/formField.ts +7 -4
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/formLayout.ts +63 -0
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/index.ts +1 -0
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/action.ts +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/resource/formLayout.ts +56 -0
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/resource/index.ts +1 -0
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssr.ts +19 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/monkey.ts +5 -0
- package/zova/src/suite-vendor/a-zova/package.json +5 -5
|
@@ -162,8 +162,8 @@ This block uses `ZForm` with:
|
|
|
162
162
|
|
|
163
163
|
- `schema={$$page.schemaFilter}`
|
|
164
164
|
- `schemaScene="filter"`
|
|
165
|
-
- inline layout
|
|
166
165
|
- page-owned filter data
|
|
166
|
+
- field-level `formFieldLayout.inline: true` by default
|
|
167
167
|
|
|
168
168
|
Its job is not to duplicate the list query logic.
|
|
169
169
|
|
|
@@ -178,6 +178,38 @@ A practical rule is:
|
|
|
178
178
|
- if you need to refine which filter fields exist, start from backend filter-side metadata and schema
|
|
179
179
|
- if you need to refine how filter submission affects the list, inspect `blockFilter` and `blockPage.onFilter(...)`
|
|
180
180
|
|
|
181
|
+
### Use blocks for a structural filter layout
|
|
182
|
+
|
|
183
|
+
A bare `basic-page:blockFilter` keeps the default schema field rendering and adds Search/Reset controls automatically. For a structured filter, compose the existing form layout block with the filter-specific action block:
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
ZovaRender.block('basic-page:blockFilter', {
|
|
187
|
+
formFieldLayout: { inline: true },
|
|
188
|
+
blocks: [
|
|
189
|
+
ZovaRender.block('basic-form:blockFormLayout', {
|
|
190
|
+
formLayout: {
|
|
191
|
+
children: [
|
|
192
|
+
{
|
|
193
|
+
type: 'section',
|
|
194
|
+
layout: 'flow',
|
|
195
|
+
children: [
|
|
196
|
+
{ type: 'field', name: 'name' },
|
|
197
|
+
{ type: 'field', name: 'level' },
|
|
198
|
+
{ type: 'field', name: 'createdAt' },
|
|
199
|
+
],
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
}),
|
|
204
|
+
ZovaRender.block('basic-page:blockFilterActions'),
|
|
205
|
+
],
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`basic-form:blockFormLayout` only places schema fields. `basic-page:blockFilterActions` owns Search/Reset placement and invokes the filter command surface supplied through the form scope, so it preserves filter normalization and page-query behavior. A nonempty `blocks` list replaces the automatic body and footer; include the action block explicitly to make the filter operable.
|
|
210
|
+
|
|
211
|
+
`ZForm.inline` is no longer a form API. Use `formFieldLayout.inline` for field-level compact layout, or use blocks plus `basic-form:blockFormLayout` for structural layout. The flow section above keeps compact filters left-packed and wrapping; use the default Grid section when fields need responsive columns and spans. Read [Form Layout Guide](/frontend/form-layout-guide) for the full node grammar, section layout rules, resolver behavior, tabs, and entry-form composition.
|
|
212
|
+
|
|
181
213
|
## Step 5: Let `blockToolbarBulk` own bulk-action display
|
|
182
214
|
|
|
183
215
|
The standard bulk toolbar block is:
|
|
@@ -218,7 +218,39 @@ Use this path when you are asking questions like:
|
|
|
218
218
|
- `home-login/render.tsx` shows a page-level `formProvider` override for layout behavior
|
|
219
219
|
- `formField/render.tsx` shows how the behavior-wrapped render path leads to the final vnode
|
|
220
220
|
|
|
221
|
-
## 7.
|
|
221
|
+
## 7. DTO-driven structural Form Layout
|
|
222
|
+
|
|
223
|
+
Use this path when you are asking questions like:
|
|
224
|
+
|
|
225
|
+
- where does `formLayout` come from in a resource DTO?
|
|
226
|
+
- how are fields, sections, groups, and tabs normalized before rendering?
|
|
227
|
+
- why are omitted visible fields appended or duplicate fields removed?
|
|
228
|
+
- where does Cabloy Basic render responsive grids and tab error badges?
|
|
229
|
+
|
|
230
|
+
### Read the docs first
|
|
231
|
+
|
|
232
|
+
- [Form Layout Guide](/frontend/form-layout-guide)
|
|
233
|
+
- [Table + Resource CRUD Cookbook](/frontend/table-resource-crud-cookbook)
|
|
234
|
+
- [Resource Entry Page Deep Dive](/frontend/resource-entry-page-deep-dive)
|
|
235
|
+
|
|
236
|
+
### Then read source in this order
|
|
237
|
+
|
|
238
|
+
1. `vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx`
|
|
239
|
+
2. `vona/src/suite/a-training/modules/training-student/src/dto/studentSelectResItem.tsx`
|
|
240
|
+
3. `zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/resource/formLayout.ts`
|
|
241
|
+
4. `zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts`
|
|
242
|
+
5. `zova/src/suite/cabloy-basic/modules/basic-form/src/component/blockFormLayout/controller.tsx`
|
|
243
|
+
6. `vona/src/suite/a-training/modules/training-student/test/student.test.ts`
|
|
244
|
+
|
|
245
|
+
### What each file clarifies
|
|
246
|
+
|
|
247
|
+
- the Student DTOs show the entry and filter block composition that supplies layout metadata
|
|
248
|
+
- the OpenAPI type contract defines the legal node grammar and responsive values
|
|
249
|
+
- the resolver reconciles metadata with visible schema fields, generated IDs, and diagnostics
|
|
250
|
+
- the Basic block controller renders sections, groups, tabs, and field spans while delegating widgets to `$$form.renderField(...)`
|
|
251
|
+
- the Student test verifies emitted metadata nesting, columns, spans, and optional IDs; it is not a browser rendering test
|
|
252
|
+
|
|
253
|
+
## 8. Resource-driven CRUD page integration
|
|
222
254
|
|
|
223
255
|
Use this path when you are asking questions like:
|
|
224
256
|
|
|
@@ -251,7 +283,7 @@ If your next question becomes how `formScene` becomes `formMeta`, then `pageMeta
|
|
|
251
283
|
|
|
252
284
|
If your next question becomes how backend/entity schema metadata attaches field-side effects through `ZovaRender.onEffect(...)` and command chains, continue with [Schema-Driven Field Effects Guide](/frontend/schema-driven-field-effects-guide).
|
|
253
285
|
|
|
254
|
-
##
|
|
286
|
+
## 9. Representative specimens to read before editing the framework
|
|
255
287
|
|
|
256
288
|
Use this section when you want one small example before reading the framework internals.
|
|
257
289
|
|
|
@@ -267,7 +299,7 @@ Use this section when you want one small example before reading the framework in
|
|
|
267
299
|
- `home-login` shows a real page using presets, blank rows, and provider-level layout override
|
|
268
300
|
- together they give you the public authoring shape before you descend into the form runtime
|
|
269
301
|
|
|
270
|
-
##
|
|
302
|
+
## 10. A compact reading strategy
|
|
271
303
|
|
|
272
304
|
When in doubt, use this order:
|
|
273
305
|
|
|
@@ -280,7 +312,7 @@ When in doubt, use this order:
|
|
|
280
312
|
|
|
281
313
|
That order usually gets you to the answer faster than starting from the deepest runtime files first.
|
|
282
314
|
|
|
283
|
-
##
|
|
315
|
+
## 11. Final takeaway
|
|
284
316
|
|
|
285
317
|
The fastest way to read Zova Form accurately is not to memorize every file in `a-form`.
|
|
286
318
|
|
|
@@ -410,6 +410,19 @@ zova/src/suite-vendor/a-zova/modules/a-form/src/component/formField/render.tsx
|
|
|
410
410
|
|
|
411
411
|
That means automatic schema-driven rendering is not happening magically in the wrapper component. It is happening in the render bean.
|
|
412
412
|
|
|
413
|
+
### Structural Form Layout boundary
|
|
414
|
+
|
|
415
|
+
When `ZForm` receives a nonempty block list, the render bean delegates body rendering to those blocks instead of iterating schema fields directly. For Cabloy Basic structural forms, `basic-form:blockFormLayout` resolves `formLayout` against the form's current schema properties and calls `$$form.renderField(...)` for each surviving layout field.
|
|
416
|
+
|
|
417
|
+
This keeps ownership separate:
|
|
418
|
+
|
|
419
|
+
- the form controller owns schema properties, field state, validation, and field rendering
|
|
420
|
+
- the shared form-layout resolver normalizes field placement metadata
|
|
421
|
+
- the Basic layout block renders sections, groups, grids, and tabs
|
|
422
|
+
- field-layout behaviors still own each field wrapper and its visible validation message
|
|
423
|
+
|
|
424
|
+
Read [Form Layout Guide](/frontend/form-layout-guide) for the DTO authoring grammar, resolver behavior, and Basic-specific responsive/tab behavior.
|
|
425
|
+
|
|
413
426
|
### Field render path
|
|
414
427
|
|
|
415
428
|
`RenderFormField` does two important jobs:
|
|
@@ -42,17 +42,17 @@ For the current public explanation of this backend capability, see [Cache Guide]
|
|
|
42
42
|
|
|
43
43
|
This is the kind of result Cabloy is designed to support: a framework that stays operationally calm even when the system keeps running for long periods.
|
|
44
44
|
|
|
45
|
-
One internally generated project was kept running continuously for **
|
|
45
|
+
One internally generated project was kept running continuously for **35 days**. At one representative PM2 snapshot, the process looked like this:
|
|
46
46
|
|
|
47
47
|
```text
|
|
48
48
|
┌────┬─────────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
|
|
49
49
|
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
|
|
50
50
|
├────┼─────────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
|
|
51
|
-
│ 0 │ cabloy_*** │ default │ N/A │ cluster │ 226947 │ 21D │ 17 │ online │ 0% │
|
|
51
|
+
│ 0 │ cabloy_*** │ default │ N/A │ cluster │ 226947 │ 21D │ 17 │ online │ 0% │ 427.8mb │ ubuntu │ disabled │
|
|
52
52
|
└────┴─────────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
For this
|
|
55
|
+
For this 35-day continuous run, the observed result was **0 memory leak**.
|
|
56
56
|
|
|
57
57
|
The value of this example is not that it is a synthetic micro-benchmark. The value is that it reflects a real long-running project process with a clear, inspectable runtime footprint.
|
|
58
58
|
|
|
@@ -102,10 +102,24 @@ These commands build the edition-specific frontend flavors from the repository y
|
|
|
102
102
|
|
|
103
103
|
## 6. Upgrade an existing project
|
|
104
104
|
|
|
105
|
+
Inspect the planned framework changes before applying them:
|
|
106
|
+
|
|
105
107
|
```bash
|
|
108
|
+
npm run upgrade:dry-run
|
|
106
109
|
npm run upgrade
|
|
107
110
|
```
|
|
108
111
|
|
|
112
|
+
In Cabloy Basic, upgrade synchronizes the framework-owned SSR browser E2E baseline, its suite/surface root `test:e2e:*` commands, and the `@playwright/test` development dependency. It does not add a root script for each individual E2E scenario. The framework reserves these paths:
|
|
113
|
+
|
|
114
|
+
```text
|
|
115
|
+
e2e/config/
|
|
116
|
+
e2e/scripts/
|
|
117
|
+
e2e/specs/a-basic/
|
|
118
|
+
e2e/specs/a-commerce/
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Keep project-owned browser tests outside those reserved paths, for example under `e2e/specs/my-project/`; upgrade overlays framework files without deleting project test paths. Projects whose previous upgrader predates this E2E synchronization may need to run `npm run upgrade` once more: the updated upgrader recognizes an incomplete Basic E2E baseline even when the version marker is already current.
|
|
122
|
+
|
|
109
123
|
## 7. Next steps for framework-aware development
|
|
110
124
|
|
|
111
125
|
If you are contributing to framework-aware workflows or using Cabloy CLI generation directly, prefer CLI-backed generation over manual scaffolding.
|
|
@@ -9,20 +9,99 @@ The root `package.json` is the first reference point for shared monorepo workflo
|
|
|
9
9
|
## Current shared entrypoints in Cabloy Basic
|
|
10
10
|
|
|
11
11
|
- `npm run init`
|
|
12
|
+
- `npm run upgrade`
|
|
13
|
+
- `npm run upgrade:dry-run`
|
|
12
14
|
- `npm run vona`
|
|
13
15
|
- `npm run zova`
|
|
14
16
|
- `npm run dev`
|
|
15
17
|
- `npm run dev:zova:admin`
|
|
16
18
|
- `npm run dev:zova:web`
|
|
19
|
+
- `npm run dev:zova:commerce:web`
|
|
20
|
+
- `npm run dev:zova:commerce:admin`
|
|
17
21
|
- `npm run build`
|
|
18
22
|
- `npm run build:zova`
|
|
23
|
+
- `npm run build:zova:commerce`
|
|
24
|
+
- `npm run build:zova:commerce:web`
|
|
25
|
+
- `npm run build:zova:commerce:admin`
|
|
19
26
|
- `npm run start`
|
|
20
27
|
- `npm run test`
|
|
28
|
+
- `npm run test:e2e:basic`
|
|
29
|
+
- `npm run test:e2e:basic:web`
|
|
30
|
+
- `npm run test:e2e:basic:admin`
|
|
31
|
+
- `npm run test:e2e:basic:clean`
|
|
32
|
+
- `npm run test:e2e:commerce`
|
|
33
|
+
- `npm run test:e2e:commerce:web`
|
|
34
|
+
- `npm run test:e2e:commerce:admin`
|
|
35
|
+
- `npm run test:e2e:commerce:clean`
|
|
21
36
|
- `npm run tsc`
|
|
22
37
|
- `npm run docs:dev`
|
|
23
38
|
- `npm run docs:build`
|
|
24
39
|
- `npm run docs:preview`
|
|
25
40
|
|
|
41
|
+
## Upgrade
|
|
42
|
+
|
|
43
|
+
Run `npm run upgrade:dry-run` before `npm run upgrade` to inspect the framework files and root manifest entries that an upgrade would synchronize. In Cabloy Basic, the upgrade owns `e2e/config/`, `e2e/scripts/`, `e2e/specs/a-basic/`, and `e2e/specs/a-commerce/`, together with the framework `test:e2e:*` scripts and `@playwright/test` development dependency. Keep project browser tests outside those reserved paths; see [Fullstack Quickstart](/fullstack/quickstart) for the upgrade bootstrap behavior.
|
|
44
|
+
|
|
45
|
+
## SSR browser checks
|
|
46
|
+
|
|
47
|
+
Both E2E families use the same command structure:
|
|
48
|
+
|
|
49
|
+
- `test:e2e:<suite>` runs every browser scenario in the suite.
|
|
50
|
+
- `test:e2e:<suite>:web` and `test:e2e:<suite>:admin` are durable surface shortcuts that select `@web` and `@admin` tests.
|
|
51
|
+
- `test:e2e:<suite>:clean` resets managed local test state, starts one development Vona worker, then runs the suite or a Playwright-filtered subset.
|
|
52
|
+
|
|
53
|
+
Use native Playwright tags for scenario categories rather than adding a root script for each feature. Every framework scenario uses one surface tag (`@web` or `@admin`) and one purpose tag when applicable (`@smoke` or `@flow`). ATP IDs remain in titles for exact evidence and failure reruns.
|
|
54
|
+
|
|
55
|
+
Pass Playwright options after npm's `--` delimiter:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Exact acceptance scenario
|
|
59
|
+
npm run test:e2e:basic -- --grep ATP-BASIC-FLOW-01
|
|
60
|
+
|
|
61
|
+
# Category or surface selection
|
|
62
|
+
npm run test:e2e:basic:clean -- --grep @flow
|
|
63
|
+
npm run test:e2e:basic:clean -- --grep @admin
|
|
64
|
+
|
|
65
|
+
# Compose tags with a Playwright regular expression
|
|
66
|
+
npm run test:e2e:basic:clean -- --grep '(?=.*@admin)(?=.*@flow)'
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The managed `:clean` runner owns its suite config and local lifecycle. It accepts normal Playwright selection and reporting options, but does not accept `--config` or positional spec paths. Use `--grep` or `--grep-invert` to narrow the run. It rejects an external base URL rather than resetting or starting an externally managed target.
|
|
70
|
+
|
|
71
|
+
The Basic suite exercises Web at `/` and Admin at `/admin` through Vona's SSR dispatcher. Prepare fresh Basic SSR artifacts explicitly when frontend SSR output has changed:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm run build:zova
|
|
75
|
+
npm run deps:vona
|
|
76
|
+
npm run test:e2e:basic:clean
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
For an externally managed Basic target, set `BASIC_E2E_BASE_URL` and use aggregate, surface, or forwarded-tag commands. These commands do not reset, start, stop, or rebuild the target:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
BASIC_E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:basic
|
|
83
|
+
BASIC_E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:basic:admin
|
|
84
|
+
BASIC_E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:basic -- --grep @flow
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Commerce browser acceptance exercises Customer Web at `/commerce` and Operator Admin routing at `/commerce-admin`. Prepare its paired artifacts explicitly:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npm run build:zova:commerce
|
|
91
|
+
npm run deps:vona
|
|
92
|
+
npm run test:e2e:commerce:clean
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
For an externally managed Commerce target, set `COMMERCE_E2E_BASE_URL` and use the matching aggregate, surface, or forwarded-tag command. The target owner is responsible for data, cache, and artifact freshness:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
COMMERCE_E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:commerce
|
|
99
|
+
COMMERCE_E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:commerce:web
|
|
100
|
+
COMMERCE_E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:commerce -- --grep @smoke
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Browser commands consume existing SSR and REST artifacts; they never rebuild them. Install Chromium once when needed with `npx playwright install chromium`.
|
|
104
|
+
|
|
26
105
|
## Edition-sensitive note
|
|
27
106
|
|
|
28
107
|
Cabloy Start keeps the same high-level pattern while using different frontend flavors such as `cabloyStartAdmin` and `cabloyStartWeb`, plus its own SSR site baselines and project assets in the licensed private repository.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { defineConfig } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
import type { E2eSuiteName } from '../scripts/e2e.ts';
|
|
4
|
+
|
|
5
|
+
import { E2E_LOCAL_BASE_URL, E2E_ROOT_DIR, getE2eSuite } from '../scripts/e2e.ts';
|
|
6
|
+
|
|
7
|
+
export function createE2eConfig(suiteName: E2eSuiteName) {
|
|
8
|
+
const suite = getE2eSuite(suiteName);
|
|
9
|
+
const externalBaseURL = process.env[suite.externalBaseUrlEnv];
|
|
10
|
+
const baseURL = externalBaseURL || E2E_LOCAL_BASE_URL;
|
|
11
|
+
|
|
12
|
+
return defineConfig({
|
|
13
|
+
testDir: suite.testDir,
|
|
14
|
+
fullyParallel: true,
|
|
15
|
+
forbidOnly: !!process.env.CI,
|
|
16
|
+
retries: process.env.CI ? 2 : 0,
|
|
17
|
+
reporter: process.env.CI ? [['html', { open: 'never' }], ['list']] : 'list',
|
|
18
|
+
use: {
|
|
19
|
+
baseURL,
|
|
20
|
+
trace: 'on-first-retry',
|
|
21
|
+
},
|
|
22
|
+
webServer: externalBaseURL
|
|
23
|
+
? undefined
|
|
24
|
+
: {
|
|
25
|
+
command: 'node e2e/scripts/startE2eVona.ts',
|
|
26
|
+
cwd: E2E_ROOT_DIR,
|
|
27
|
+
url: `${baseURL}${suite.readinessPath}`,
|
|
28
|
+
timeout: 180_000,
|
|
29
|
+
reuseExistingServer: false,
|
|
30
|
+
stdout: 'pipe',
|
|
31
|
+
stderr: 'pipe',
|
|
32
|
+
gracefulShutdown: {
|
|
33
|
+
signal: 'SIGINT',
|
|
34
|
+
timeout: 10_000,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { dirname, resolve } from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
|
|
4
|
+
export const E2E_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
5
|
+
export const E2E_PORT = 7102;
|
|
6
|
+
export const E2E_LOCAL_BASE_URL = `http://127.0.0.1:${E2E_PORT}`;
|
|
7
|
+
|
|
8
|
+
const E2E_CONFIG_DIR = resolve(E2E_ROOT_DIR, 'e2e', 'config');
|
|
9
|
+
const E2E_SPECS_DIR = resolve(E2E_ROOT_DIR, 'e2e', 'specs');
|
|
10
|
+
|
|
11
|
+
const e2eSuites = {
|
|
12
|
+
basic: {
|
|
13
|
+
externalBaseUrlEnv: 'BASIC_E2E_BASE_URL',
|
|
14
|
+
configFile: resolve(E2E_CONFIG_DIR, 'playwright.basic.config.ts'),
|
|
15
|
+
testDir: resolve(E2E_SPECS_DIR, 'a-basic'),
|
|
16
|
+
readinessPath: '/',
|
|
17
|
+
},
|
|
18
|
+
commerce: {
|
|
19
|
+
externalBaseUrlEnv: 'COMMERCE_E2E_BASE_URL',
|
|
20
|
+
configFile: resolve(E2E_CONFIG_DIR, 'playwright.commerce.config.ts'),
|
|
21
|
+
testDir: resolve(E2E_SPECS_DIR, 'a-commerce'),
|
|
22
|
+
readinessPath: '/commerce',
|
|
23
|
+
},
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
export type E2eSuiteName = keyof typeof e2eSuites;
|
|
27
|
+
|
|
28
|
+
export function getE2eSuite(name: string | undefined) {
|
|
29
|
+
if (name && name in e2eSuites) {
|
|
30
|
+
return e2eSuites[name as E2eSuiteName];
|
|
31
|
+
}
|
|
32
|
+
throw new Error(`Expected an E2E suite: ${Object.keys(e2eSuites).join(', ')}.`);
|
|
33
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
import { E2E_ROOT_DIR } from './e2e.ts';
|
|
4
|
+
|
|
5
|
+
const child = spawn('npm', ['run', 'dev:one'], {
|
|
6
|
+
cwd: E2E_ROOT_DIR,
|
|
7
|
+
detached: process.platform !== 'win32',
|
|
8
|
+
stdio: 'inherit',
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
let stopping = false;
|
|
12
|
+
|
|
13
|
+
function stop() {
|
|
14
|
+
if (stopping) return;
|
|
15
|
+
stopping = true;
|
|
16
|
+
if (process.platform === 'win32') {
|
|
17
|
+
child.kill('SIGINT');
|
|
18
|
+
} else if (child.pid) {
|
|
19
|
+
try {
|
|
20
|
+
process.kill(-child.pid, 'SIGINT');
|
|
21
|
+
} catch (error: any) {
|
|
22
|
+
if (error.code !== 'ESRCH') throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
process.on('SIGINT', stop);
|
|
28
|
+
process.on('SIGTERM', stop);
|
|
29
|
+
|
|
30
|
+
child.on('error', error => {
|
|
31
|
+
// eslint-disable-next-line
|
|
32
|
+
console.error(error);
|
|
33
|
+
process.exitCode = 1;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
child.on('exit', code => {
|
|
37
|
+
process.exitCode = code ?? 1;
|
|
38
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { createServer } from 'node:net';
|
|
3
|
+
|
|
4
|
+
import { E2E_PORT, E2E_ROOT_DIR, getE2eSuite } from './e2e.ts';
|
|
5
|
+
|
|
6
|
+
const suiteName = process.argv[2];
|
|
7
|
+
const suite = getE2eSuite(suiteName);
|
|
8
|
+
const playwrightArgs = process.argv.slice(3);
|
|
9
|
+
|
|
10
|
+
function run(command: string, args: string[], cwd = E2E_ROOT_DIR) {
|
|
11
|
+
// eslint-disable-next-line
|
|
12
|
+
console.log(`\n===> ${[command, ...args].join(' ')}`);
|
|
13
|
+
execFileSync(command, args, {
|
|
14
|
+
cwd,
|
|
15
|
+
stdio: 'inherit',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function assertPlaywrightArgs(args: string[]) {
|
|
20
|
+
for (const arg of args) {
|
|
21
|
+
if (arg === '--config' || arg.startsWith('--config=')) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`test:e2e:${suiteName}:clean manages its suite config. Use --grep or --grep-invert to select tests.`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
if (!arg.startsWith('-') && (arg.startsWith('e2e/') || /\.[cm]?[jt]sx?$/.test(arg))) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`test:e2e:${suiteName}:clean does not accept spec paths. Use --grep or --grep-invert to select tests.`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function assertPortAvailable() {
|
|
35
|
+
const server = createServer();
|
|
36
|
+
await new Promise<void>((resolvePromise, reject) => {
|
|
37
|
+
server.once('error', reject);
|
|
38
|
+
server.listen(E2E_PORT, '127.0.0.1', () => {
|
|
39
|
+
server.close(error => {
|
|
40
|
+
if (error) reject(error);
|
|
41
|
+
else resolvePromise();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (process.env[suite.externalBaseUrlEnv]) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`test:e2e:${suiteName}:clean manages only a local target. Use the aggregate or surface E2E commands for ${suite.externalBaseUrlEnv}.`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
assertPlaywrightArgs(playwrightArgs);
|
|
54
|
+
await assertPortAvailable();
|
|
55
|
+
run('npm', ['run', 'db:reset']);
|
|
56
|
+
run('npx', ['playwright', 'test', '--config', suite.configFile, ...playwrightArgs]);
|