cabloy 5.1.149 → 5.1.151
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/.claude/commands/release.md +12 -1
- package/.claude/hooks/contract-loop-gate.ts +35 -17
- package/.claude/skills/cabloy-backend-scaffold/SKILL.md +4 -3
- package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +3 -2
- package/.claude/skills/cabloy-contract-loop/SKILL.md +4 -3
- package/.claude/skills/cabloy-domain-planning/SKILL.md +4 -3
- package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +5 -3
- package/.claude/skills/cabloy-master-detail/SKILL.md +4 -3
- package/.claude/skills/cabloy-module-removal/SKILL.md +5 -4
- package/.claude/skills/cabloy-resource-field-update/SKILL.md +8 -5
- package/.claude/skills/cabloy-spec-execution/SKILL.md +169 -0
- package/.claude/skills/cabloy-spec-execution/evals/evals.json +71 -0
- package/.claude/skills/cabloy-spec-execution/references/execution-protocol.md +137 -0
- package/.claude/skills/cabloy-spec-execution/references/status-and-evidence.md +117 -0
- package/.claude/skills/cabloy-spec-generation/SKILL.md +227 -0
- package/.claude/skills/cabloy-spec-generation/evals/evals.json +83 -0
- package/.claude/skills/cabloy-spec-generation/references/repo-aware-discovery.md +101 -0
- package/.claude/skills/cabloy-spec-generation/references/repo-specs-document-set.md +267 -0
- package/.claude/skills/cabloy-spec-generation/references/traceability-and-status-rules.md +135 -0
- package/.claude/skills/cabloy-spec-generation/scripts/generate-implementation-charts.mjs +350 -0
- package/.claude/skills/cabloy-spec-generation/scripts/generate-implementation-charts.test.mjs +202 -0
- package/.claude/skills/cabloy-workflow/SKILL.md +21 -10
- package/.claude/skills/cabloy-workflow/evals/evals.json +8 -2
- package/.claude/skills/cabloy-workflow/references/edition-detection.md +5 -3
- package/.claude/skills/cabloy-zova-source-reading/SKILL.md +4 -3
- package/CHANGELOG.md +41 -0
- package/CLAUDE.md +12 -11
- package/package.json +4 -2
- package/repo-docs/.vitepress/config.mjs +4 -0
- package/repo-docs/ai/docs-skills-rules-mapping.md +6 -6
- package/repo-docs/ai/future-skill-roadmap.md +2 -2
- package/repo-docs/ai/introduction.md +5 -5
- package/repo-docs/ai/playbook-contract-regeneration.md +3 -9
- package/repo-docs/ai/repo-guidance.md +5 -4
- package/repo-docs/ai/rules-and-config.md +2 -2
- package/repo-docs/ai/virtual-decorator-guidance.md +1 -3
- package/repo-docs/backend/cache-guide.md +36 -0
- package/repo-docs/backend/dto-guide.md +7 -6
- package/repo-docs/backend/dto-infer-generation.md +25 -5
- package/repo-docs/backend/introduction.md +1 -1
- package/repo-docs/backend/migration-and-changes.md +27 -2
- package/repo-docs/backend/model-guide.md +1 -1
- package/repo-docs/backend/resource-field-update.md +156 -0
- package/repo-docs/frontend/a-model-under-the-hood.md +38 -0
- package/repo-docs/frontend/a-openapi-under-the-hood.md +13 -2
- package/repo-docs/frontend/form-layout-guide.md +26 -9
- package/repo-docs/frontend/model-resource-best-practices.md +2 -0
- package/repo-docs/frontend/model-resource-cookbook.md +7 -1
- package/repo-docs/frontend/router-tabs-introduction.md +2 -4
- package/repo-docs/frontend/use-state-data-best-practices.md +44 -0
- package/repo-docs/frontend/zova-form-source-reading-map.md +26 -9
- package/repo-docs/frontend/zova-form-under-the-hood.md +11 -0
- package/repo-docs/fullstack/a-pay-payment-suite.md +1 -1
- package/repo-docs/fullstack/contract-loop-playbook.md +4 -0
- package/repo-docs/fullstack/one-to-one-companion-resource-guide.md +2 -2
- package/repo-docs/reference/package-map.md +1 -1
- package/repo-e2e/specs/a-commerce.spec.ts +40 -0
- package/scripts/release.ts +48 -1
- package/scripts/upgrade.ts +18 -7
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/controller/{{resourceName}}.ts_ +2 -2
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/pnpm-lock.yaml +83 -75
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +108 -108
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/skuSelectResItem.tsx +12 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/sku.ts +19 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/skuPresentation.test.ts +53 -4
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +148 -148
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockAdjust.tsx +37 -3
- package/vona/src/suite/a-training/modules/training-student/src/.metadata/index.ts +2 -2
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +7 -6
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentSummary.tsx +1 -1
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +7 -6
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +10 -7
- package/vona/src/suite/a-training/modules/training-student/src/model/student.ts +4 -4
- package/vona/src/suite/a-training/modules/training-student/src/service/student.ts +39 -67
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +56 -37
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +5 -5
- package/zova/src/suite/a-commerce/modules/commerce-trade/cli/openapi.config.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/package.json +6 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/component/tableCellActionAdjustStock.ts +34 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +81 -6
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradeStockBalance.ts +32 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/schemas.ts +44 -44
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +356 -338
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradeStockBalance.ts +17 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/bean/tableCell.actionAdjustStock.tsx +35 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/component/tableCellActionAdjustStock/controller.tsx +138 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/en-us.ts +3 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +3 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/stockBalance.ts +36 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +4 -4
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +8 -8
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +11 -11
- package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.actionSummary.tsx +2 -2
- package/zova/src/suite/cabloy-basic/modules/basic-app/src/bean/behavior.appModal.tsx +2 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +2 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/test/model/resource.types.ts +11 -0
- package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts +58 -9
- package/zova/src/suite-vendor/a-zova/modules/a-form/test/lib/formLayout.test.ts +157 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.persister.ts +3 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useQuery.ts +11 -13
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useState.ts +9 -7
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useStateGeneral.ts +5 -12
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/queryPersister.ts +17 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/queryRefetch.ts +34 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/types/query.ts +27 -3
- package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/queryPersister.test.ts +115 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/queryRefetch.test.ts +84 -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/lib/schema.ts +27 -5
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/model/sdk.ts +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/rest.ts +4 -0
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/schema.ts +2 -5
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/test/lib/schema.test.ts +179 -0
- package/zova/src/suite-vendor/a-zova/package.json +4 -4
|
@@ -48,6 +48,31 @@ The key rule is:
|
|
|
48
48
|
|
|
49
49
|
In the scaffolded CRUD workflow, this is not an isolated maintenance step. The generator-driven thread treats `fileVersion` as part of the same backend evolution path that also touches entity/model/controller/test resources.
|
|
50
50
|
|
|
51
|
+
## Decide whether to increment fileVersion
|
|
52
|
+
|
|
53
|
+
For a new persisted field or another schema-shape change on an existing resource, decide whether `vonaModule.fileVersion` should change **before** editing `meta.version.ts`, a versioned schema path, or the module `package.json`.
|
|
54
|
+
|
|
55
|
+
### Increment fileVersion
|
|
56
|
+
|
|
57
|
+
Choose a new migration version when the change must be applied sequentially to an existing released module:
|
|
58
|
+
|
|
59
|
+
1. increment `fileVersion`;
|
|
60
|
+
2. add a new migration branch;
|
|
61
|
+
3. preserve older branches as historical snapshots;
|
|
62
|
+
4. introduce the schema change in the new branch.
|
|
63
|
+
|
|
64
|
+
Do not introduce the same column in an older create path and again in a later migration branch. A fresh installation can execute version branches sequentially and fail on a duplicate column.
|
|
65
|
+
|
|
66
|
+
### Keep the current fileVersion
|
|
67
|
+
|
|
68
|
+
When the change belongs in the current version path rather than new migration history:
|
|
69
|
+
|
|
70
|
+
1. keep the existing `fileVersion`;
|
|
71
|
+
2. fold the schema change into the current version path;
|
|
72
|
+
3. do not create a new migration branch.
|
|
73
|
+
|
|
74
|
+
Ask before choosing this strategy; do not infer migration history from the field change alone. Any change to `meta.version.ts` requires `npm run test` so the test database is recreated and schema/data consistency is exercised.
|
|
75
|
+
|
|
51
76
|
## `meta.version`
|
|
52
77
|
|
|
53
78
|
Vona uses a bean named `meta.version` to organize migration code for a module.
|
|
@@ -156,9 +181,9 @@ When the generated CRUD thread evolves, migration should evolve with it.
|
|
|
156
181
|
|
|
157
182
|
A practical sequence is:
|
|
158
183
|
|
|
159
|
-
1. increment `fileVersion`
|
|
184
|
+
1. decide whether to increment `fileVersion`
|
|
160
185
|
2. add or adjust entity/model structure
|
|
161
|
-
3. update `meta.version`
|
|
186
|
+
3. update the current or new `meta.version` path to match that decision
|
|
162
187
|
4. rerun migration locally
|
|
163
188
|
5. verify the contract through tests and controller actions
|
|
164
189
|
|
|
@@ -254,7 +254,7 @@ A practical distinction is:
|
|
|
254
254
|
|
|
255
255
|
That means model behavior affects performance and consistency semantics, not only data retrieval.
|
|
256
256
|
|
|
257
|
-
For the broader cache story, also see [Cache Guide](/backend/cache-guide).
|
|
257
|
+
For the broader cache story, also see [Cache Guide](/backend/cache-guide). For dependencies between Models, see [Cross-model query-cache dependencies](/backend/cache-guide#cross-model-query-cache-dependencies).
|
|
258
258
|
|
|
259
259
|
## Relationship to the backend contract loop
|
|
260
260
|
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Existing Resource Field Update
|
|
2
|
+
|
|
3
|
+
<Badge type="tip" text="Common" />
|
|
4
|
+
|
|
5
|
+
Use this workflow when changing a field on an existing Vona resource. It covers a new persisted field, validation or metadata refinement, enum-like constraints, and the optional renderer follow-up that connects backend `ZovaRender.*(...)` metadata to Zova resources.
|
|
6
|
+
|
|
7
|
+
This is a field-update workflow, not a substitute for initial module or CRUD scaffolding. For a new backend thread, start from [CRUD Workflow](/backend/crud-workflow). For an already stale generated consumer, start from the [Contract Loop Playbook](/fullstack/contract-loop-playbook).
|
|
8
|
+
|
|
9
|
+
## When to use this workflow
|
|
10
|
+
|
|
11
|
+
Use it when you need to:
|
|
12
|
+
|
|
13
|
+
- add a stored field to an existing resource;
|
|
14
|
+
- refine validation, titles, OpenAPI metadata, or `ZovaRender.field(...)` / `ZovaRender.cell(...)` metadata for an existing field;
|
|
15
|
+
- add enum-like field constraints;
|
|
16
|
+
- decide whether a persisted field change increments `vonaModule.fileVersion`;
|
|
17
|
+
- add a custom form-field or table-cell renderer because shared renderer options are insufficient.
|
|
18
|
+
|
|
19
|
+
Before changing anything, detect the active edition from the root marker. Explain the common backend workflow once, then resolve renderer keys, frontend flavors, build commands, and generated-output paths from the active edition.
|
|
20
|
+
|
|
21
|
+
## Persisted field or metadata-only change
|
|
22
|
+
|
|
23
|
+
Classify the work before editing migration code.
|
|
24
|
+
|
|
25
|
+
### New persisted field
|
|
26
|
+
|
|
27
|
+
Examples include a new `level: number`, `status: string`, or stored relation key. This changes storage shape and requires an explicit `fileVersion` decision.
|
|
28
|
+
|
|
29
|
+
### Metadata-only or validation/rendering refinement
|
|
30
|
+
|
|
31
|
+
Examples include:
|
|
32
|
+
|
|
33
|
+
- enum validation for a field that already exists in storage;
|
|
34
|
+
- a new field or table rendering hint;
|
|
35
|
+
- locale-label changes;
|
|
36
|
+
- stricter validation that does not change storage shape.
|
|
37
|
+
|
|
38
|
+
This usually does not require a `fileVersion` increment. Confirm the current schema first instead of assuming the field already exists.
|
|
39
|
+
|
|
40
|
+
## Decide whether to increment fileVersion
|
|
41
|
+
|
|
42
|
+
For a new persisted field, decide with the user **before** editing `meta.version.ts`, a versioned schema path, or the module `package.json`.
|
|
43
|
+
|
|
44
|
+
### Increment fileVersion
|
|
45
|
+
|
|
46
|
+
When the change needs a new sequential migration:
|
|
47
|
+
|
|
48
|
+
1. increment `vonaModule.fileVersion`;
|
|
49
|
+
2. add a new `meta.version.ts` migration branch;
|
|
50
|
+
3. preserve older branches as historical snapshots;
|
|
51
|
+
4. introduce the field only in the new version branch.
|
|
52
|
+
|
|
53
|
+
Do not add the same column to an older create path and again to a later migration branch. A fresh installation can apply the branches sequentially and fail on a duplicate column.
|
|
54
|
+
|
|
55
|
+
### Keep the current fileVersion
|
|
56
|
+
|
|
57
|
+
When the user decides not to create new migration history:
|
|
58
|
+
|
|
59
|
+
1. keep the current `fileVersion`;
|
|
60
|
+
2. fold the schema change into the current version path;
|
|
61
|
+
3. do not create a new migration branch.
|
|
62
|
+
|
|
63
|
+
For the complete migration lifecycle and test requirement, see [Migration and Changes](/backend/migration-and-changes#decide-whether-to-increment-fileversion).
|
|
64
|
+
|
|
65
|
+
## Inspect the existing resource thread
|
|
66
|
+
|
|
67
|
+
Read the current implementation before proposing changes:
|
|
68
|
+
|
|
69
|
+
- entity and model;
|
|
70
|
+
- DTOs, controller, and service;
|
|
71
|
+
- `meta.version.ts` and module `package.json`;
|
|
72
|
+
- locale files;
|
|
73
|
+
- resource tests;
|
|
74
|
+
- root `package.json`, `npm run vona`, and `npm run zova`.
|
|
75
|
+
|
|
76
|
+
Use source patterns to determine whether DTOs are inferred. Do not hand-edit generated consumers or duplicate existing schema metadata.
|
|
77
|
+
|
|
78
|
+
## Update entity truth first
|
|
79
|
+
|
|
80
|
+
Treat the entity as the primary field-definition surface. Typical changes begin with `@Api.field(...)`, validation helpers, titles, and `ZovaRender.*(...)` metadata.
|
|
81
|
+
|
|
82
|
+
For enum-like values, prefer a constrained schema that expresses the allowed values explicitly, for example `z.union([z.literal(1), z.literal(2), z.literal(3)])`.
|
|
83
|
+
|
|
84
|
+
Structure-shaping schema helpers remain order-sensitive. When a field uses `v.object(...)`, `v.array(...)`, `v.optional()`, `v.nullable()`, `v.default(...)`, preprocess/transform wrappers, or an explicit zod schema, keep the final structure-defining schema-like argument last. Then verify emitted schema/OpenAPI output rather than assuming argument reordering is safe.
|
|
85
|
+
|
|
86
|
+
For the underlying contract surface, see [Entity Guide](/backend/entity-guide#api-field-as-the-shared-contract-surface).
|
|
87
|
+
|
|
88
|
+
## Preserve the inferred DTO flow
|
|
89
|
+
|
|
90
|
+
When DTOs use `$Dto.create(...)`, `$Dto.update(...)`, `$Dto.get(...)`, or another inferred projection, let the entity change propagate. Add hand-authored field lists only when the current source demonstrates an intentional business projection or a separate contract-only field.
|
|
91
|
+
|
|
92
|
+
For projected fields, use `$makeMetadata(...)` for metadata-only refinement and `$makeSchema(...)` for schema or validation refinement. Use a class-body `@Api.field(...)` member only for a genuinely new declared field.
|
|
93
|
+
|
|
94
|
+
Read [DTO Infer and Generation](/backend/dto-infer-generation) when inference cannot express the intended contract. If serialization metadata controls the returned value, also confirm that the target controller action opts into `@Core.serializer()`.
|
|
95
|
+
|
|
96
|
+
## Choose the renderer branch
|
|
97
|
+
|
|
98
|
+
Prefer the smallest rendering change that expresses the requirement:
|
|
99
|
+
|
|
100
|
+
1. reuse an existing shared renderer;
|
|
101
|
+
2. configure it with field-level options;
|
|
102
|
+
3. create a custom module-local renderer only when the shared surface cannot express the needed behavior.
|
|
103
|
+
|
|
104
|
+
For a field-rendering select, provide a visible placeholder unless the UX genuinely requires a preselected value.
|
|
105
|
+
|
|
106
|
+
### Shared renderer reuse
|
|
107
|
+
|
|
108
|
+
Resolve renderer keys and component behavior in the active edition. Cabloy Basic and Cabloy Start can differ in select wrappers, placeholder behavior, UI library, flavors, and generated outputs. Do not copy a Basic renderer key or empty-item convention into Start without inspecting the Start baseline.
|
|
109
|
+
|
|
110
|
+
### Custom renderer follow-up
|
|
111
|
+
|
|
112
|
+
A custom backend-rendered field normally needs both sides of the frontend pair:
|
|
113
|
+
|
|
114
|
+
- a module-local FormField component;
|
|
115
|
+
- a registered module-local `@TableCell(...)` bean when backend `ZovaRender.cell(...)` references a table cell.
|
|
116
|
+
|
|
117
|
+
A plain frontend component is not sufficient for backend table-cell metadata. Reuse the closest shared renderer’s option/data flow and keep the customization focused.
|
|
118
|
+
|
|
119
|
+
For public implementation patterns, see [Form Guide](/frontend/form-guide), [TableCell Cookbook](/frontend/table-cell-cookbook#pattern-7-backend-contract-to-frontend-cell-handoff), and the Basic-only [Custom Form/Table Renderers tutorial](/fullstack/tutorial-4-custom-level-renderers).
|
|
120
|
+
|
|
121
|
+
## Follow the contract loop
|
|
122
|
+
|
|
123
|
+
A field change commonly has two possible handoffs:
|
|
124
|
+
|
|
125
|
+
- a persisted entity/DTO/OpenAPI change begins with the **forward chain**: update backend truth, verify emitted contract output, regenerate frontend consumers, then make thin follow-up changes;
|
|
126
|
+
- a new frontend-owned FormField or TableCell resource begins the **reverse chain**: update frontend source, regenerate metadata when needed, run the active edition’s complete frontend build, then run `npm run deps:vona`.
|
|
127
|
+
|
|
128
|
+
Do not treat `build:rest:*` alone as sufficient for reverse-chain work. The SSR bundle and REST output must move together. If generated `.zova-rest` output already contains the expected keys but Vona still sees stale types after the normal build and dependency sync, treat the issue as local dependency drift and repair the local installation state before hand-patching generated links.
|
|
129
|
+
|
|
130
|
+
Use the [Contract Loop Playbook](/fullstack/contract-loop-playbook) for edition-aware commands and recovery details.
|
|
131
|
+
|
|
132
|
+
## Update locale and tests
|
|
133
|
+
|
|
134
|
+
Update locale files when users can see field titles, enum labels, placeholders, or renderer helper text.
|
|
135
|
+
|
|
136
|
+
Minimum backend coverage generally includes:
|
|
137
|
+
|
|
138
|
+
- create with the field;
|
|
139
|
+
- select/list behavior;
|
|
140
|
+
- update persistence;
|
|
141
|
+
- get-by-id or view response;
|
|
142
|
+
- delete behavior when it is relevant.
|
|
143
|
+
|
|
144
|
+
For constrained enum-like values, add a negative test that proves an invalid value is rejected. Test-local persisted resources must be deleted in `finally` using precise owned identities and reverse dependency order.
|
|
145
|
+
|
|
146
|
+
## Verification checklist
|
|
147
|
+
|
|
148
|
+
Choose checks that match the layers changed:
|
|
149
|
+
|
|
150
|
+
- inspect generated schema/OpenAPI after structure-shaping field changes;
|
|
151
|
+
- run the narrow resource test and relevant typecheck;
|
|
152
|
+
- run `npm run test` for any `meta.version.ts` change so the test database is recreated and migration consistency is exercised;
|
|
153
|
+
- run the relevant frontend metadata/build/dependency synchronization when custom renderer resources are involved;
|
|
154
|
+
- verify action-level serializer behavior with an API test when `v.serializer*` metadata changes returned fields.
|
|
155
|
+
|
|
156
|
+
Finish by confirming the backend contract, frontend resources, generated handoff, and user-visible locale labels all describe the same field behavior.
|
|
@@ -187,6 +187,44 @@ The important SSR-aware rule is:
|
|
|
187
187
|
|
|
188
188
|
That means stale-time behavior is part of the model runtime contract, not only a local query option.
|
|
189
189
|
|
|
190
|
+
### Per-fetch persistence bypass
|
|
191
|
+
|
|
192
|
+
The returned query's `refetch()` is extended by the model runtime with an opt-in per-fetch persistence bypass:
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
await query.refetch({ bypassPersister: true });
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The source-level execution path is:
|
|
199
|
+
|
|
200
|
+
```text
|
|
201
|
+
query.refetch({ bypassPersister: true })
|
|
202
|
+
-> BeanModelUseQuery refetch wrapper
|
|
203
|
+
-> remove bypassPersister from the public options
|
|
204
|
+
-> attach a per-fetch internal marker
|
|
205
|
+
-> TanStack Query fetch
|
|
206
|
+
-> BeanModelPersister wrapper
|
|
207
|
+
-> call queryFn(context) directly
|
|
208
|
+
-> TanStack Query updates in-memory query state
|
|
209
|
+
-> observers and query.data update
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
An ordinary refetch follows the existing persister path instead:
|
|
213
|
+
|
|
214
|
+
```text
|
|
215
|
+
query.refetch()
|
|
216
|
+
-> TanStack Query fetch
|
|
217
|
+
-> experimental query persister
|
|
218
|
+
-> possible persisted restore or restore-triggered refresh
|
|
219
|
+
-> normal persistence behavior
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
For a bypassed fetch, the marker makes the model persister skip persisted restore and the persister's save path for that fetch. The result still goes through TanStack Query's normal success/error handling, so the query's in-memory data, timestamps, observers, and reactive result continue to behave normally. The marker is scoped to the fetch and does not modify static query options or `meta.persister`; `meta.persister: false` remains the query-wide switch.
|
|
223
|
+
|
|
224
|
+
This option is not a force-new-request primitive. The model wrapper preserves TanStack Query cancellation and in-flight deduplication semantics, including the meaning of `cancelRefetch`. A fetch that has already been deduplicated cannot be retroactively converted into a bypassed fetch. The bypass call also does not automatically cancel an ordinary persistence callback that was already queued before it.
|
|
225
|
+
|
|
226
|
+
The current implementation transports the marker through the installed TanStack Query observer's internal fetch-options forwarding. `bypassPersister` is therefore a Zova model option, not a native public TanStack `RefetchOptions` field; the model wrapper keeps that internal detail out of the normal authoring surface.
|
|
227
|
+
|
|
190
228
|
## State helper families as one runtime family
|
|
191
229
|
|
|
192
230
|
The state helper layer lives mainly in:
|
|
@@ -31,7 +31,7 @@ A practical mental model is:
|
|
|
31
31
|
2. `$sdk` resolves a locale-scoped `ModelSdk`
|
|
32
32
|
3. `ModelSdk` exposes bootstrap, permissions, sdk, schema, Zod, and default-value helpers
|
|
33
33
|
4. `SysSdk` owns the lower cache/fetch layer for bootstrap/docs/schemas
|
|
34
|
-
5. `schema.ts` extracts request/query/filter/body/row/paged schema surfaces
|
|
34
|
+
5. `schema.ts` extracts request/query/filter/body/row/paged schema surfaces, applies scene-aware property selection, and normalizes `fieldSource` fields into canonical runtime keys
|
|
35
35
|
6. downstream resource/model/table consumers reuse those lower-level surfaces rather than rebuilding them independently
|
|
36
36
|
|
|
37
37
|
That means `a-openapi` is not only about generated SDK usage. It is also the lower-level schema/runtime bridge beneath the higher-level frontend runtime.
|
|
@@ -204,12 +204,21 @@ This file owns the main lower-level helpers for:
|
|
|
204
204
|
- scene-aware property loading
|
|
205
205
|
- JSON-schema-to-Zod conversion
|
|
206
206
|
|
|
207
|
+
### Scene overlays, `fieldSource`, and preserved aliases
|
|
208
|
+
|
|
207
209
|
The most important scene-aware rule is in `loadSchemaProperties(...)`:
|
|
208
210
|
|
|
209
211
|
- property metadata can be extended by `rest.*`
|
|
210
|
-
-
|
|
212
|
+
- for `form-view`, `form-create`, and `filter`, it merges base `rest`, the shared `rest.form` overlay, and then the exact scene overlay before reading `fieldSource`
|
|
213
|
+
- other scenes apply their exact overlay
|
|
211
214
|
- field ordering is resolved through `rest.order`
|
|
212
215
|
|
|
216
|
+
When that effective metadata supplies `fieldSource`, its nested source path becomes the canonical runtime `key`. This is the field identity that downstream form and structural consumers bind and render. The first differing original schema property name that resolves to this key is retained as `schemaKey`; later coalesced names are retained in `schemaKeys`. If a schema property already has the canonical name, `key` itself remains that original identity.
|
|
217
|
+
|
|
218
|
+
Several schema properties can therefore describe one canonical field without producing several runtime field records. Coalescing is by canonical key, so its result does not depend on declaration order. Ordinary properties that do not use `fieldSource` do not acquire alias metadata.
|
|
219
|
+
|
|
220
|
+
The preserved names are metadata aliases, not additional bindings. They let a structural consumer accept a DTO-facing field name while still handing the canonical key to the form runtime. See [Zova Form Under the Hood](/frontend/zova-form-under-the-hood#fieldsource-canonicalization-and-preserved-schema-aliases) for the binding consequence and [Form Layout Guide](/frontend/form-layout-guide#how-the-resolver-handles-the-declared-tree) for author-facing declaration rules.
|
|
221
|
+
|
|
213
222
|
That means `a-openapi` is not only a transport/schema lookup layer.
|
|
214
223
|
|
|
215
224
|
It is also the lower-level metadata shaping layer for schema-driven UI behavior.
|
|
@@ -243,6 +252,8 @@ Use these next steps depending on your question:
|
|
|
243
252
|
- if you want OpenAPI generation/config usage, read [OpenAPI SDK Guide](/frontend/openapi-sdk-guide)
|
|
244
253
|
- if you want schema-driven UI positioning, read [API Schema Guide](/frontend/api-schema-guide)
|
|
245
254
|
- if you want the resource-owner consumer side, read [ModelResource Internals Deep Dive](/frontend/model-resource-internals-deep-dive)
|
|
255
|
+
- if you want canonical schema keys to become form bindings, read [Zova Form Under the Hood](/frontend/zova-form-under-the-hood)
|
|
256
|
+
- if you want Form Layout field-declaration matching, read [Form Layout Guide](/frontend/form-layout-guide)
|
|
246
257
|
- if you want the table/resource consumer side, read [Zova Table Under the Hood](/frontend/zova-table-under-the-hood) and the resource deep dives
|
|
247
258
|
|
|
248
259
|
## Final takeaway
|
|
@@ -219,13 +219,25 @@ Set `layout: 'flow'` when compact fields should appear from left to right withou
|
|
|
219
219
|
|
|
220
220
|
## How the resolver handles the declared tree
|
|
221
221
|
|
|
222
|
-
Before rendering, `resolveFormLayout(...)` reconciles `formLayout` with the current scene's resolved schema properties. This makes the declaration a **placement overlay**, not an allow-list.
|
|
222
|
+
Before rendering, `resolveFormLayout(...)` reconciles `formLayout` with the current scene's resolved schema properties. This makes the declaration a **placement overlay**, not an allow-list. The preceding OpenAPI normalization step is explained in [OpenAPI Runtime Under the Hood](/frontend/a-openapi-under-the-hood#scene-overlays-fieldsource-and-preserved-aliases).
|
|
223
223
|
|
|
224
224
|
### Eligible and omitted fields
|
|
225
225
|
|
|
226
226
|
Only schema properties with `rest.visible !== false` are eligible. When an eligible visible field is absent from `formLayout`, the resolver appends it as a root-level field after the declared nodes, in schema-property order.
|
|
227
227
|
|
|
228
|
-
A
|
|
228
|
+
A field that uses `fieldSource` is represented at runtime by its nested canonical source key. The loader stores that source path in `key`, retains the first differing original schema name in `schemaKey`, and retains further coalesced names in `schemaKeys`. A property already named by its canonical key retains that identity in `key`. These preserved names are aliases for declaration matching, not separate fields or bindings.
|
|
229
|
+
|
|
230
|
+
Form Layout accepts three declaration forms in precedence order:
|
|
231
|
+
|
|
232
|
+
1. the exact eligible canonical key;
|
|
233
|
+
2. any uniquely mapped preserved schema alias from `schemaKey` or `schemaKeys`;
|
|
234
|
+
3. a unique relation-prefix shorthand.
|
|
235
|
+
|
|
236
|
+
For example, a real relation declaration `studentContentForm` can resolve to the sole `studentContentForm.descriptionMarkdown` source, while an intentional `fieldSource` schema alias such as `_descriptionMarkdown` can resolve to `content.descriptionMarkdown` even though it is not that source path's prefix. The resolved plan always renders and binds the canonical nested key. `fieldSource(...)` is a binding and canonical-key mapping mechanism; it neither infers nor declares the backend DTO field type. A projected field already has a schema, while a true virtual DTO key absent from the inferred projection must be defined with `$makeSchema(...)` and a concrete schema such as `z.string()`; see [Virtual fields in the DTO fields map](/backend/dto-infer-generation#virtual-fields-in-the-dto-fields-map).
|
|
237
|
+
|
|
238
|
+
An alias or relation prefix must resolve to exactly one eligible canonical source. If multiple visible source keys match, it is unresolved and receives `unknownField`; declare the exact canonical source key instead. Exact canonical matches win over colliding aliases. Invisible properties contribute neither eligible fields nor usable aliases.
|
|
239
|
+
|
|
240
|
+
Duplicate declarations are detected by canonical key, so an alias and `content.descriptionMarkdown` cannot render the same field twice. Resolved field names, duplicate identity, and tab paths all use the canonical key. If a declaration is removed because it is unknown, invisible, ambiguous, or duplicate, otherwise unplaced eligible canonical fields are still appended at the root.
|
|
229
241
|
|
|
230
242
|
If a field must not render, make it invisible in schema metadata. Leaving it out of `formLayout.children` is not enough.
|
|
231
243
|
|
|
@@ -324,7 +336,7 @@ ZovaRender.block('basic-pageentry:blockForm', {
|
|
|
324
336
|
});
|
|
325
337
|
```
|
|
326
338
|
|
|
327
|
-
`studentContentForm` is one
|
|
339
|
+
`studentContentForm` is one projected, `fieldSource`-mapped nested-relation field in the structural tree. It remains the DTO-facing declaration name, while the resolved plan rewrites it to `studentContentForm.descriptionMarkdown` before field-state lookup and rendering. Its Markdown renderer owns the nested source-field UI, while the separate `StudentContent` group expresses that it is a distinct content area rather than part of the responsive profile Grid.
|
|
328
340
|
|
|
329
341
|
`trainingRecords` is one field in the structural tree. Its `basic-details:formFieldDetails` renderer owns the nested details UI; Form Layout does not recursively arrange the properties inside each detail record.
|
|
330
342
|
|
|
@@ -371,8 +383,9 @@ Here `formFieldLayout.inline: true` controls how each field wrapper is presented
|
|
|
371
383
|
4. Use `formLayout` when the requirement is field placement, Grid or flow structure, groups, or tabs.
|
|
372
384
|
5. Use `layout`, `formFieldLayout`, `options`, or provider behaviors when the requirement is one field's wrapper or renderer.
|
|
373
385
|
6. Keep entry actions in page-entry toolbar blocks. Keep filter action semantics in `basic-page:blockFilterActions`; place that block inside Form Layout when the actions must share structural Grid or flow placement with fields.
|
|
374
|
-
7. For maintained Cabloy Basic list filters, prefer one inline flow section that explicitly lists every real filter-schema field in schema order and ends with one embedded `basic-page:blockFilterActions` block. Do not add
|
|
386
|
+
7. For maintained Cabloy Basic list filters, prefer one inline flow section that explicitly lists every real filter-schema field in schema order and ends with one embedded `basic-page:blockFilterActions` block. Do not add synthetic request-only filter fields, alter filter transforms, or combine it with a sibling action block.
|
|
375
387
|
8. Review field names against the scene-specific schema. Unlisted visible fields are appended; unknown and duplicate declarations are silently pruned from the rendered plan.
|
|
388
|
+
9. Prefer an exact canonical source path when an alias or relation prefix could match multiple visible fields. Do not list both an alias and its canonical key; canonical duplicate detection keeps only the first declaration.
|
|
376
389
|
|
|
377
390
|
## Source-reading and verification path
|
|
378
391
|
|
|
@@ -380,10 +393,14 @@ For source-level investigation, follow this order:
|
|
|
380
393
|
|
|
381
394
|
1. `vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx` or `studentSelectResItem.tsx`
|
|
382
395
|
2. `zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/resource/formLayout.ts`
|
|
383
|
-
3. `zova/src/suite-vendor/a-zova/modules/a-
|
|
384
|
-
4. `zova/src/suite/
|
|
385
|
-
5. `
|
|
386
|
-
|
|
387
|
-
|
|
396
|
+
3. `zova/src/suite-vendor/a-zova/modules/a-openapi/src/lib/schema.ts`
|
|
397
|
+
4. `zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/rest.ts`
|
|
398
|
+
5. `zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts`
|
|
399
|
+
6. `zova/src/suite/cabloy-basic/modules/basic-form/src/component/blockFormLayout/controller.tsx`
|
|
400
|
+
7. `zova/src/suite-vendor/a-zova/modules/a-openapi/test/lib/schema.test.ts`
|
|
401
|
+
8. `zova/src/suite-vendor/a-zova/modules/a-form/test/lib/formLayout.test.ts`
|
|
402
|
+
9. `vona/src/suite/a-training/modules/training-student/test/student.test.ts`
|
|
403
|
+
|
|
404
|
+
The OpenAPI loader test verifies canonical `fieldSource` keys, preserved aliases, scene overlays, coalescing, and declaration-order independence. The Form Layout test verifies canonical rewriting, alias and prefix resolution, ambiguity, duplicate detection, visibility, and tab paths. The Student test verifies that entry and filter DTO metadata preserves the current block nesting, optional IDs, Grid columns/spans, flow layout selection, and field order through OpenAPI generation. It is a contract-metadata test, not a browser end-to-end assertion for tabs, layout behavior, or error badges.
|
|
388
405
|
|
|
389
406
|
For the broader form runtime, continue with [Zova Form Under the Hood](/frontend/zova-form-under-the-hood) and [Zova Form Source Reading Map](/frontend/zova-form-source-reading-map).
|
|
@@ -360,6 +360,8 @@ Use this checklist before merging resource-model changes:
|
|
|
360
360
|
8. Does bootstrap/readiness stay inside the model boundary?
|
|
361
361
|
9. Are `$fetch`, `$sdk`, and model state composed coherently?
|
|
362
362
|
10. Would another screen reuse this model surface confidently?
|
|
363
|
+
11. Does ongoing render output read `query.data` or a model-derived reactive surface rather than an awaited `refetch()` snapshot?
|
|
364
|
+
12. Are awaited `refetch()` results limited to one-shot interaction/orchestration without creating a parallel ongoing render-state owner?
|
|
363
365
|
|
|
364
366
|
## When to use this page
|
|
365
367
|
|
|
@@ -121,9 +121,15 @@ const modelStudent = (await ctx.bean._getBean(
|
|
|
121
121
|
true,
|
|
122
122
|
)) as ModelStudent;
|
|
123
123
|
const querySummary = modelStudent.summary(id);
|
|
124
|
-
|
|
124
|
+
|
|
125
|
+
await querySummary.refetch({ bypassPersister: true });
|
|
126
|
+
$host.$appModal.dialog({
|
|
127
|
+
slotDefault: () => <ZMarkdownHtml html={querySummary.data?.descriptionHtml ?? ''} />,
|
|
128
|
+
});
|
|
125
129
|
```
|
|
126
130
|
|
|
131
|
+
`refetch({ bypassPersister: true })` is useful when this interaction needs an API-fresh result without restoring or scheduling a persistence save through the persister for that fetch. The successful result still updates the model-owned in-memory query, and the dialog remains bound to `querySummary.data`, so the query remains the source of its ongoing render state rather than transferring ownership to an awaited-result snapshot. The option affects only this fetch; normal query cancellation and in-flight deduplication rules still apply. It is not a force-new-request option: when an existing fetch is reused by TanStack Query, that fetch's semantics remain in effect. The bypassed fetch itself does not intentionally replace an existing persisted value; an already queued ordinary persistence callback is a separate operation and is not automatically cancelled. Use static `meta.persister: false` only when persistence should be disabled for the query generally.
|
|
132
|
+
|
|
127
133
|
### Avoid
|
|
128
134
|
|
|
129
135
|
Do not create a second model that independently caches the same student resource list/item state just because a custom action was added.
|
|
@@ -110,15 +110,13 @@ Recommended order:
|
|
|
110
110
|
|
|
111
111
|
1. [Router Tabs Mechanism](/frontend/router-tabs-mechanism)
|
|
112
112
|
2. [Router Tabs Admin and Web Comparison](/frontend/router-tabs-admin-web-comparison)
|
|
113
|
-
3.
|
|
113
|
+
3. inspect the current implementation and public comparison guidance for refactor boundaries
|
|
114
114
|
|
|
115
115
|
## Scope boundary
|
|
116
116
|
|
|
117
117
|
The public router-tabs docs explain the shared frontend mechanism and how to use it.
|
|
118
118
|
|
|
119
|
-
For
|
|
120
|
-
|
|
121
|
-
- `repo-docs-internal/architecture/router-tabs-design-boundaries.md`
|
|
119
|
+
For maintainer-only design boundaries, maintenance invariants, and refactor safety rules, consult an established internal-documentation home when the active repository provides one. The public mechanism, layout, route-meta, and Admin/Web comparison guides remain the shared implementation baseline.
|
|
122
120
|
|
|
123
121
|
## See also
|
|
124
122
|
|
|
@@ -278,6 +278,50 @@ const passportCode = await this.$passport.ensureFreshTempAuthToken(options);
|
|
|
278
278
|
|
|
279
279
|
Both paths reuse the same model-owned query. When `$QueryGetFresh(...)` finds stale data, it starts `query.suspense()` and returns `undefined` for the current render; reactive query state provides the replacement value on a later render. `$QueryEnsureFresh(...)` instead waits for that refresh and propagates query errors to the interaction flow.
|
|
280
280
|
|
|
281
|
+
## Render-driving state versus one-shot refetch results
|
|
282
|
+
|
|
283
|
+
A query established during render remains the owner of its ongoing state. Render paths should read the current reactive surface—typically `query.data`, `query.pending`, `query.error`, or a model-derived projection—and let query updates drive a later render.
|
|
284
|
+
|
|
285
|
+
An interaction or orchestration boundary may still await `query.refetch()` when it needs one result to decide whether to continue a command, navigate, show a notification, or open a dialog. That result is local to the current sequence; it does not transfer query ownership to the controller or render path.
|
|
286
|
+
|
|
287
|
+
Do not copy an awaited `refetch()` result into a second long-lived controller/render state that drives an open dialog or persistent component. If the UI remains mounted and displays query-backed data, bind it to `query.data` or a model-derived reactive surface so later refetches and model updates remain visible.
|
|
288
|
+
|
|
289
|
+
### Per-fetch persistence bypass
|
|
290
|
+
|
|
291
|
+
When one interaction needs an API-fresh result but should not restore persisted data or schedule a persistence save for that fetch, use the model query's per-fetch option:
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
await query.refetch({ bypassPersister: true });
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
`bypassPersister: true`:
|
|
298
|
+
|
|
299
|
+
- affects only the current fetch
|
|
300
|
+
- does not change the query's static options or `meta.persister`
|
|
301
|
+
- skips persisted-query restore for that fetch
|
|
302
|
+
- does not schedule that fetch through the persister's save path
|
|
303
|
+
- still lets TanStack Query update the in-memory query, timestamps, observers, and reactive `query.data`
|
|
304
|
+
- does not create a second query or cache owner
|
|
305
|
+
|
|
306
|
+
This option is not a force-new-request flag. Cancellation, `cancelRefetch`, and in-flight deduplication continue to follow TanStack Query semantics. If an existing fetch is reused, its already-established fetch semantics remain in effect. The bypassed fetch itself does not intentionally replace an existing persisted value; an already queued ordinary persistence callback is a separate operation and is not automatically cancelled.
|
|
307
|
+
|
|
308
|
+
This is different from static `meta.persister: false`: the static option disables persistence for the query generally, while `bypassPersister: true` opts out only for one fetch.
|
|
309
|
+
|
|
310
|
+
For example, an interaction can request a fresh summary and then open a dialog while the dialog remains bound to the query-owned state:
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
const querySummary = modelStudent.summary(id);
|
|
314
|
+
await querySummary.refetch({ bypassPersister: true });
|
|
315
|
+
|
|
316
|
+
$host.$appModal.dialog({
|
|
317
|
+
slotDefault: () => (
|
|
318
|
+
<ZMarkdownHtml html={querySummary.data?.descriptionHtml ?? ''} />
|
|
319
|
+
),
|
|
320
|
+
});
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
The awaited result may coordinate the current interaction, but ongoing rendering should continue to read `querySummary.data`.
|
|
324
|
+
|
|
281
325
|
## Practical rule 7: derive render-time state once per render when possible
|
|
282
326
|
|
|
283
327
|
Even when the query object is reused, a controller can still become noisy if it repeatedly derives the same values in several helper calls.
|
|
@@ -147,21 +147,29 @@ Use this path when you are asking questions like:
|
|
|
147
147
|
|
|
148
148
|
- how does a schema become rendered fields?
|
|
149
149
|
- where do field props come from?
|
|
150
|
+
- where does `fieldSource` become the canonical form key, and where are original schema aliases preserved?
|
|
150
151
|
- how are metadata expressions or render providers resolved?
|
|
151
152
|
|
|
152
153
|
### Read the docs first
|
|
153
154
|
|
|
154
155
|
- [API Schema Guide](/frontend/api-schema-guide)
|
|
156
|
+
- [OpenAPI Runtime Under the Hood](/frontend/a-openapi-under-the-hood)
|
|
155
157
|
- [Form Guide](/frontend/form-guide)
|
|
156
158
|
|
|
157
159
|
### Then read source in this order
|
|
158
160
|
|
|
159
|
-
1. `zova/src/suite-vendor/a-zova/modules/a-
|
|
160
|
-
2. `zova/src/suite-vendor/a-zova/modules/a-
|
|
161
|
-
3. `zova/src/suite-vendor/a-zova/modules/a-
|
|
161
|
+
1. `zova/src/suite-vendor/a-zova/modules/a-openapi/src/lib/schema.ts`
|
|
162
|
+
2. `zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/rest.ts`
|
|
163
|
+
3. `zova/src/suite-vendor/a-zova/modules/a-openapi/test/lib/schema.test.ts`
|
|
164
|
+
4. `zova/src/suite-vendor/a-zova/modules/a-form/src/component/form/controller.tsx`
|
|
165
|
+
5. `zova/src/suite-vendor/a-zova/modules/a-form/src/component/form/render.tsx`
|
|
166
|
+
6. `zova/src/suite-vendor/a-zova/modules/a-form/src/types/formField.ts`
|
|
162
167
|
|
|
163
168
|
### What each file clarifies
|
|
164
169
|
|
|
170
|
+
- `schema.ts` shows scene-aware `fieldSource` canonicalization and coalescing by canonical key
|
|
171
|
+
- `types/rest.ts` defines the preserved `schemaKey` and `schemaKeys` aliases
|
|
172
|
+
- `schema.test.ts` is the compact behavior matrix for canonical keys, aliases, scene overlays, declaration-order independence, and ordinary fields
|
|
165
173
|
- `form/controller.tsx` shows schema property loading, field CEL scope creation, and top-level field prop extraction
|
|
166
174
|
- `form/render.tsx` shows how schema properties become children when the form body is not manually overridden
|
|
167
175
|
- `types/formField.ts` shows the field render-context shapes that the runtime passes through to renderers and behaviors
|
|
@@ -224,12 +232,14 @@ Use this path when you are asking questions like:
|
|
|
224
232
|
|
|
225
233
|
- where does `formLayout` come from in a resource DTO?
|
|
226
234
|
- how are fields, embedded blocks, sections, groups, and tabs normalized before rendering?
|
|
235
|
+
- how do canonical keys, preserved schema aliases, and unique relation-prefix shorthand resolve to one field?
|
|
227
236
|
- why are omitted visible fields appended or duplicate fields removed?
|
|
228
237
|
- where does Cabloy Basic render responsive grids and tab error badges?
|
|
229
238
|
|
|
230
239
|
### Read the docs first
|
|
231
240
|
|
|
232
241
|
- [Form Layout Guide](/frontend/form-layout-guide)
|
|
242
|
+
- [OpenAPI Runtime Under the Hood](/frontend/a-openapi-under-the-hood)
|
|
233
243
|
- [Table + Resource CRUD Cookbook](/frontend/table-resource-crud-cookbook)
|
|
234
244
|
- [Resource Entry Page Deep Dive](/frontend/resource-entry-page-deep-dive)
|
|
235
245
|
|
|
@@ -238,18 +248,25 @@ Use this path when you are asking questions like:
|
|
|
238
248
|
1. `vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx`
|
|
239
249
|
2. `vona/src/suite/a-training/modules/training-student/src/dto/studentSelectResItem.tsx`
|
|
240
250
|
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-
|
|
242
|
-
5. `zova/src/suite/
|
|
243
|
-
6. `zova/src/suite/
|
|
244
|
-
7. `
|
|
251
|
+
4. `zova/src/suite-vendor/a-zova/modules/a-openapi/src/lib/schema.ts`
|
|
252
|
+
5. `zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/rest.ts`
|
|
253
|
+
6. `zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts`
|
|
254
|
+
7. `zova/src/suite/cabloy-basic/modules/basic-form/src/component/blockFormLayout/controller.tsx`
|
|
255
|
+
8. `zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockFilterActions/controller.tsx`
|
|
256
|
+
9. `zova/src/suite-vendor/a-zova/modules/a-openapi/test/lib/schema.test.ts`
|
|
257
|
+
10. `zova/src/suite-vendor/a-zova/modules/a-form/test/lib/formLayout.test.ts`
|
|
258
|
+
11. `vona/src/suite/a-training/modules/training-student/test/student.test.ts`
|
|
245
259
|
|
|
246
260
|
### What each file clarifies
|
|
247
261
|
|
|
248
262
|
- the Student DTOs show the entry and filter block composition that supplies layout metadata
|
|
249
263
|
- the OpenAPI type contract defines the legal node grammar and responsive values
|
|
250
|
-
-
|
|
251
|
-
-
|
|
264
|
+
- `schema.ts` resolves scene overlays, canonicalizes `fieldSource`, and coalesces aliases that target one canonical key
|
|
265
|
+
- `types/rest.ts` defines the preserved alias metadata
|
|
266
|
+
- the resolver filters visible fields, resolves exact canonical keys before unique aliases and unique prefixes, and records duplicate identity and tab paths by canonical key
|
|
267
|
+
- the Basic block controller renders sections, groups, tabs, field spans, and embedded blocks while delegating canonical field names to `$$form.renderField(...)`
|
|
252
268
|
- `blockFilterActions` shows how a block rendered inside Form Layout reuses the inherited form CEL scope to invoke `$$filter`
|
|
269
|
+
- the OpenAPI and Form Layout unit tests verify canonicalization, alias precedence, ambiguity, duplicates, visibility, root append, and tab paths
|
|
253
270
|
- the Student test verifies emitted metadata nesting, columns, spans, embedded action blocks, and optional IDs; it is not a browser rendering test
|
|
254
271
|
|
|
255
272
|
## 8. Resource-driven CRUD page integration
|
|
@@ -11,6 +11,7 @@ Use this page together with:
|
|
|
11
11
|
- [Zova Reactivity Under the Hood](/frontend/zova-reactivity-under-the-hood)
|
|
12
12
|
- [Behavior Guide](/frontend/behavior-guide)
|
|
13
13
|
- [API Schema Guide](/frontend/api-schema-guide)
|
|
14
|
+
- [OpenAPI Runtime Under the Hood](/frontend/a-openapi-under-the-hood)
|
|
14
15
|
|
|
15
16
|
Use this page after [Form Guide](/frontend/form-guide) when you want to move from the public authoring surface to the internal cooperation among form controllers, field controllers, schema metadata, provider config, behaviors, and CRUD integration.
|
|
16
17
|
|
|
@@ -233,6 +234,14 @@ A practical reading takeaway is:
|
|
|
233
234
|
- **schema is not only validation truth**
|
|
234
235
|
- **schema also drives ordering, render metadata, and scene-specific field behavior**
|
|
235
236
|
|
|
237
|
+
### `fieldSource` canonicalization and preserved schema aliases
|
|
238
|
+
|
|
239
|
+
`loadSchemaProperties(...)` constructs the effective property `rest` metadata before inspecting `fieldSource`: base metadata, the applicable shared `form` overlay, and then the exact scene overlay. When that metadata supplies `fieldSource`, its nested source path becomes the property's canonical `key`.
|
|
240
|
+
|
|
241
|
+
The form uses this canonical key for field-property lookup, CEL field scope, values, validation, and rendering. When an original schema property name differs, `schemaKey` preserves the first such name that reached the canonical key, while `schemaKeys` preserves later coalesced names. A property already named by its canonical key retains that identity in `key`. These preserved names are aliases for metadata consumers such as Form Layout, not independent form bindings.
|
|
242
|
+
|
|
243
|
+
Consequently, several DTO-facing schema names can represent one nested field without creating duplicate form state. The OpenAPI loader owns this normalization and coalescing; see [OpenAPI Runtime Under the Hood](/frontend/a-openapi-under-the-hood#scene-overlays-fieldsource-and-preserved-aliases) for its lower-level contract.
|
|
244
|
+
|
|
236
245
|
### The form owner supplies the schema lifetime
|
|
237
246
|
|
|
238
247
|
`ZForm` derives `properties` and `zodSchema` from the schema it receives. Its runtime does not own `$apiSchema` locale selection or refresh a facade retained by the page/controller. If the owner supplies a schema object created under an earlier locale, the form will faithfully render that object's titles and metadata.
|
|
@@ -445,6 +454,8 @@ That means automatic schema-driven rendering is not happening magically in the w
|
|
|
445
454
|
|
|
446
455
|
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.
|
|
447
456
|
|
|
457
|
+
For a field declaration, the shared resolver first accepts an exact canonical key, then a uniquely mapped preserved schema alias, and then a unique relation-prefix shorthand. It rewrites an alias or shorthand to the canonical key before calling `$$form.renderField(...)`. Exact canonical keys win over colliding aliases; ambiguous aliases or prefixes are reported as `unknownField`. Duplicate tracking and tab-path bookkeeping use the canonical key, while visible canonical fields not placed by a surviving declaration are appended afterward. See [Form Layout Guide](/frontend/form-layout-guide#how-the-resolver-handles-the-declared-tree) for the full DTO authoring rules.
|
|
458
|
+
|
|
448
459
|
Form Layout also supports a leaf `block` node. It wraps an existing resource block descriptor and the Basic renderer invokes it with the inherited `IJsxRenderContextForm`, including the same JSX runtime and CEL scope. The node has no schema property or field value; for example, a filter can place `basic-page:blockFilterActions` inside a flow section while that action block continues to read `$$filter` from the filter-owned form scope.
|
|
449
460
|
|
|
450
461
|
This keeps ownership separate:
|
|
@@ -265,7 +265,7 @@ The Commerce payment route requires authentication and uses the session SSR prof
|
|
|
265
265
|
|
|
266
266
|
The invariant is more important than a particular visual shell: server HTML and the hydration-time initial render must agree about private payment/order data. Do not render customer-owned payment state on the server when the request cannot authorize it, and do not let the provider return itself settle Commerce.
|
|
267
267
|
|
|
268
|
-
|
|
268
|
+
When the active repository provides the corresponding internal architecture note, maintainers can consult it for deeper SSR return and Passport recovery rationale. The public rule remains: settle on verified server facts; recover private browser state only after hydration and authorization.
|
|
269
269
|
|
|
270
270
|
## Reliability and security invariants
|
|
271
271
|
|
|
@@ -113,6 +113,9 @@ Examples:
|
|
|
113
113
|
- controller request or response changed
|
|
114
114
|
- validation changed
|
|
115
115
|
- OpenAPI metadata changed
|
|
116
|
+
- an existing resource field changed
|
|
117
|
+
|
|
118
|
+
For an existing resource-field change, first use [Existing Resource Field Update](/backend/resource-field-update) to make the persistence, DTO, locale, renderer, and migration decision explicit. Then continue with the applicable contract-loop branch.
|
|
116
119
|
|
|
117
120
|
Then use the **forward chain**:
|
|
118
121
|
|
|
@@ -357,6 +360,7 @@ Use the tutorial series as examples of the two chains:
|
|
|
357
360
|
|
|
358
361
|
- [Fullstack Introduction](/fullstack/introduction)
|
|
359
362
|
- [Backend OpenAPI to Frontend SDK](/fullstack/openapi-to-sdk)
|
|
363
|
+
- [Existing Resource Field Update](/backend/resource-field-update)
|
|
360
364
|
- [Frontend Metadata Back to Backend](/fullstack/frontend-metadata-to-backend)
|
|
361
365
|
- [Vona + Zova Integration](/fullstack/vona-zova-integration)
|
|
362
366
|
- [OpenAPI SDK Guide](/frontend/openapi-sdk-guide)
|