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.
Files changed (122) hide show
  1. package/.cabloy-version +1 -1
  2. package/.claude/commands/release.md +12 -1
  3. package/.claude/hooks/contract-loop-gate.ts +35 -17
  4. package/.claude/skills/cabloy-backend-scaffold/SKILL.md +4 -3
  5. package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +3 -2
  6. package/.claude/skills/cabloy-contract-loop/SKILL.md +4 -3
  7. package/.claude/skills/cabloy-domain-planning/SKILL.md +4 -3
  8. package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +5 -3
  9. package/.claude/skills/cabloy-master-detail/SKILL.md +4 -3
  10. package/.claude/skills/cabloy-module-removal/SKILL.md +5 -4
  11. package/.claude/skills/cabloy-resource-field-update/SKILL.md +8 -5
  12. package/.claude/skills/cabloy-spec-execution/SKILL.md +169 -0
  13. package/.claude/skills/cabloy-spec-execution/evals/evals.json +71 -0
  14. package/.claude/skills/cabloy-spec-execution/references/execution-protocol.md +137 -0
  15. package/.claude/skills/cabloy-spec-execution/references/status-and-evidence.md +117 -0
  16. package/.claude/skills/cabloy-spec-generation/SKILL.md +227 -0
  17. package/.claude/skills/cabloy-spec-generation/evals/evals.json +83 -0
  18. package/.claude/skills/cabloy-spec-generation/references/repo-aware-discovery.md +101 -0
  19. package/.claude/skills/cabloy-spec-generation/references/repo-specs-document-set.md +267 -0
  20. package/.claude/skills/cabloy-spec-generation/references/traceability-and-status-rules.md +135 -0
  21. package/.claude/skills/cabloy-spec-generation/scripts/generate-implementation-charts.mjs +350 -0
  22. package/.claude/skills/cabloy-spec-generation/scripts/generate-implementation-charts.test.mjs +202 -0
  23. package/.claude/skills/cabloy-workflow/SKILL.md +21 -10
  24. package/.claude/skills/cabloy-workflow/evals/evals.json +8 -2
  25. package/.claude/skills/cabloy-workflow/references/edition-detection.md +5 -3
  26. package/.claude/skills/cabloy-zova-source-reading/SKILL.md +4 -3
  27. package/CHANGELOG.md +41 -0
  28. package/CLAUDE.md +12 -11
  29. package/package.json +4 -2
  30. package/repo-docs/.vitepress/config.mjs +4 -0
  31. package/repo-docs/ai/docs-skills-rules-mapping.md +6 -6
  32. package/repo-docs/ai/future-skill-roadmap.md +2 -2
  33. package/repo-docs/ai/introduction.md +5 -5
  34. package/repo-docs/ai/playbook-contract-regeneration.md +3 -9
  35. package/repo-docs/ai/repo-guidance.md +5 -4
  36. package/repo-docs/ai/rules-and-config.md +2 -2
  37. package/repo-docs/ai/virtual-decorator-guidance.md +1 -3
  38. package/repo-docs/backend/cache-guide.md +36 -0
  39. package/repo-docs/backend/dto-guide.md +7 -6
  40. package/repo-docs/backend/dto-infer-generation.md +25 -5
  41. package/repo-docs/backend/introduction.md +1 -1
  42. package/repo-docs/backend/migration-and-changes.md +27 -2
  43. package/repo-docs/backend/model-guide.md +1 -1
  44. package/repo-docs/backend/resource-field-update.md +156 -0
  45. package/repo-docs/frontend/a-model-under-the-hood.md +38 -0
  46. package/repo-docs/frontend/a-openapi-under-the-hood.md +13 -2
  47. package/repo-docs/frontend/form-layout-guide.md +26 -9
  48. package/repo-docs/frontend/model-resource-best-practices.md +2 -0
  49. package/repo-docs/frontend/model-resource-cookbook.md +7 -1
  50. package/repo-docs/frontend/router-tabs-introduction.md +2 -4
  51. package/repo-docs/frontend/use-state-data-best-practices.md +44 -0
  52. package/repo-docs/frontend/zova-form-source-reading-map.md +26 -9
  53. package/repo-docs/frontend/zova-form-under-the-hood.md +11 -0
  54. package/repo-docs/fullstack/a-pay-payment-suite.md +1 -1
  55. package/repo-docs/fullstack/contract-loop-playbook.md +4 -0
  56. package/repo-docs/fullstack/one-to-one-companion-resource-guide.md +2 -2
  57. package/repo-docs/reference/package-map.md +1 -1
  58. package/repo-e2e/specs/a-commerce.spec.ts +40 -0
  59. package/scripts/release.ts +48 -1
  60. package/scripts/upgrade.ts +18 -7
  61. package/vona/packages-cli/cli/package.json +1 -1
  62. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/controller/{{resourceName}}.ts_ +2 -2
  63. package/vona/packages-cli/cli-set-api/package.json +1 -1
  64. package/vona/pnpm-lock.yaml +83 -75
  65. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +108 -108
  66. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/skuSelectResItem.tsx +12 -0
  67. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/sku.ts +19 -1
  68. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/skuPresentation.test.ts +53 -4
  69. package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +148 -148
  70. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockAdjust.tsx +37 -3
  71. package/vona/src/suite/a-training/modules/training-student/src/.metadata/index.ts +2 -2
  72. package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +7 -6
  73. package/vona/src/suite/a-training/modules/training-student/src/dto/studentSummary.tsx +1 -1
  74. package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +7 -6
  75. package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +10 -7
  76. package/vona/src/suite/a-training/modules/training-student/src/model/student.ts +4 -4
  77. package/vona/src/suite/a-training/modules/training-student/src/service/student.ts +39 -67
  78. package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +56 -37
  79. package/zova/packages-zova/zova/package.json +2 -2
  80. package/zova/pnpm-lock.yaml +5 -5
  81. package/zova/src/suite/a-commerce/modules/commerce-trade/cli/openapi.config.ts +1 -0
  82. package/zova/src/suite/a-commerce/modules/commerce-trade/package.json +6 -1
  83. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/component/tableCellActionAdjustStock.ts +34 -0
  84. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +81 -6
  85. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradeStockBalance.ts +32 -0
  86. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/schemas.ts +44 -44
  87. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +356 -338
  88. package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradeStockBalance.ts +17 -0
  89. package/zova/src/suite/a-commerce/modules/commerce-trade/src/bean/tableCell.actionAdjustStock.tsx +35 -0
  90. package/zova/src/suite/a-commerce/modules/commerce-trade/src/component/tableCellActionAdjustStock/controller.tsx +138 -0
  91. package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/en-us.ts +3 -0
  92. package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +3 -0
  93. package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/stockBalance.ts +36 -0
  94. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +4 -4
  95. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +8 -8
  96. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +11 -11
  97. package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.actionSummary.tsx +2 -2
  98. package/zova/src/suite/cabloy-basic/modules/basic-app/src/bean/behavior.appModal.tsx +2 -0
  99. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  100. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +2 -1
  101. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/test/model/resource.types.ts +11 -0
  102. package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
  103. package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
  104. package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts +58 -9
  105. package/zova/src/suite-vendor/a-zova/modules/a-form/test/lib/formLayout.test.ts +157 -0
  106. package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
  107. package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.persister.ts +3 -1
  108. package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useQuery.ts +11 -13
  109. package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useState.ts +9 -7
  110. package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useStateGeneral.ts +5 -12
  111. package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/queryPersister.ts +17 -0
  112. package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/queryRefetch.ts +34 -0
  113. package/zova/src/suite-vendor/a-zova/modules/a-model/src/types/query.ts +27 -3
  114. package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/queryPersister.test.ts +115 -0
  115. package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/queryRefetch.test.ts +84 -0
  116. package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
  117. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/lib/schema.ts +27 -5
  118. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/model/sdk.ts +2 -2
  119. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/rest.ts +4 -0
  120. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/schema.ts +2 -5
  121. package/zova/src/suite-vendor/a-zova/modules/a-openapi/test/lib/schema.test.ts +179 -0
  122. 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` logic
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 and applies scene-aware property selection
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
- - scene-specific overlays such as `table`, `form`, `form-view`, `form-create`, and `filter` are applied
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 virtual relation field that uses `fieldSource` is represented at runtime by its nested source key. A declared relation name can therefore resolve to exactly one eligible source key with that name as its prefix; for example, `studentContentForm` resolves to `studentContentForm.descriptionMarkdown`. This preserves the DTO's business-level layout tree while rendering the actual nested form value. If more than one source key has that prefix, declare the exact source key instead.
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 virtual nested-relation field in the structural tree. 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.
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 virtual request fields, alter filter transforms, or combine it with a sibling action block.
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-form/src/lib/formLayout.ts`
384
- 4. `zova/src/suite/cabloy-basic/modules/basic-form/src/component/blockFormLayout/controller.tsx`
385
- 5. `vona/src/suite/a-training/modules/training-student/test/student.test.ts`
386
-
387
- 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.
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
- const { data: summary } = await querySummary.refetch();
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. `repo-docs-internal/architecture/router-tabs-design-boundaries.md`
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 internal design boundaries, maintenance invariants, and refactor safety rules, see:
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-form/src/component/form/controller.tsx`
160
- 2. `zova/src/suite-vendor/a-zova/modules/a-form/src/component/form/render.tsx`
161
- 3. `zova/src/suite-vendor/a-zova/modules/a-form/src/types/formField.ts`
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-form/src/lib/formLayout.ts`
242
- 5. `zova/src/suite/cabloy-basic/modules/basic-form/src/component/blockFormLayout/controller.tsx`
243
- 6. `zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockFilterActions/controller.tsx`
244
- 7. `vona/src/suite/a-training/modules/training-student/test/student.test.ts`
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
- - the resolver reconciles field metadata with visible schema fields, generated IDs, diagnostics, and preserved embedded blocks
251
- - the Basic block controller renders sections, groups, tabs, field spans, and embedded blocks while delegating fields to `$$form.renderField(...)`
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
- For the deeper SSR return and Passport recovery rationale, maintainers can consult the internal architecture note. The public rule remains: settle on verified server facts; recover private browser state only after hydration and authorization.
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)