cabloy 5.1.148 → 5.1.150

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 (95) hide show
  1. package/.cabloy-version +1 -1
  2. package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +2 -1
  3. package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +1 -0
  4. package/CHANGELOG.md +33 -0
  5. package/CLAUDE.md +1 -0
  6. package/package.json +1 -1
  7. package/repo-docs/.vitepress/config.mjs +4 -0
  8. package/repo-docs/backend/dto-guide.md +7 -6
  9. package/repo-docs/backend/dto-infer-generation.md +25 -5
  10. package/repo-docs/backend/markdown-guide.md +1 -0
  11. package/repo-docs/backend/relations-guide.md +1 -0
  12. package/repo-docs/frontend/a-model-under-the-hood.md +38 -0
  13. package/repo-docs/frontend/a-openapi-under-the-hood.md +13 -2
  14. package/repo-docs/frontend/form-layout-guide.md +39 -8
  15. package/repo-docs/frontend/markdown-guide.md +1 -0
  16. package/repo-docs/frontend/model-resource-best-practices.md +2 -0
  17. package/repo-docs/frontend/model-resource-cookbook.md +7 -1
  18. package/repo-docs/frontend/use-state-data-best-practices.md +44 -0
  19. package/repo-docs/frontend/zova-form-source-reading-map.md +26 -9
  20. package/repo-docs/frontend/zova-form-under-the-hood.md +11 -0
  21. package/repo-docs/fullstack/edition-collaboration-differences.md +1 -0
  22. package/repo-docs/fullstack/one-to-one-companion-resource-guide.md +447 -0
  23. package/repo-e2e/specs/a-commerce.spec.ts +40 -0
  24. package/repo-e2e/specs/cabloy-basic.spec.ts +176 -1
  25. package/vona/packages-cli/cli/package.json +1 -1
  26. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/controller/{{resourceName}}.ts_ +2 -2
  27. package/vona/packages-cli/cli-set-api/package.json +1 -1
  28. package/vona/pnpm-lock.yaml +83 -75
  29. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +108 -108
  30. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/skuSelectResItem.tsx +12 -0
  31. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/sku.ts +19 -1
  32. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/skuPresentation.test.ts +53 -4
  33. package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +148 -148
  34. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockAdjust.tsx +37 -3
  35. package/vona/src/suite/a-training/modules/training-student/package.json +1 -0
  36. package/vona/src/suite/a-training/modules/training-student/src/.metadata/index.ts +177 -103
  37. package/vona/src/suite/a-training/modules/training-student/src/bean/meta.index.ts +1 -0
  38. package/vona/src/suite/a-training/modules/training-student/src/bean/meta.version.ts +19 -1
  39. package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +3 -0
  40. package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +3 -0
  41. package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +22 -2
  42. package/vona/src/suite/a-training/modules/training-student/src/dto/studentSummary.tsx +7 -1
  43. package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +22 -2
  44. package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +25 -3
  45. package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +0 -3
  46. package/vona/src/suite/a-training/modules/training-student/src/entity/studentContent.tsx +36 -0
  47. package/vona/src/suite/a-training/modules/training-student/src/model/student.ts +6 -0
  48. package/vona/src/suite/a-training/modules/training-student/src/model/studentContent.ts +22 -0
  49. package/vona/src/suite/a-training/modules/training-student/src/service/student.ts +43 -14
  50. package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +137 -8
  51. package/zova/packages-zova/zova/package.json +2 -2
  52. package/zova/pnpm-lock.yaml +6 -2
  53. package/zova/src/suite/a-commerce/modules/commerce-trade/cli/openapi.config.ts +1 -0
  54. package/zova/src/suite/a-commerce/modules/commerce-trade/package.json +6 -1
  55. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/component/tableCellActionAdjustStock.ts +34 -0
  56. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +81 -6
  57. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradeStockBalance.ts +32 -0
  58. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/schemas.ts +44 -44
  59. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +356 -338
  60. package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradeStockBalance.ts +17 -0
  61. package/zova/src/suite/a-commerce/modules/commerce-trade/src/bean/tableCell.actionAdjustStock.tsx +35 -0
  62. package/zova/src/suite/a-commerce/modules/commerce-trade/src/component/tableCellActionAdjustStock/controller.tsx +138 -0
  63. package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/en-us.ts +3 -0
  64. package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +3 -0
  65. package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/stockBalance.ts +36 -0
  66. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +4 -4
  67. package/zova/src/suite/a-training/modules/training-student/package.json +3 -1
  68. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +44 -44
  69. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +360 -339
  70. package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.actionSummary.tsx +19 -8
  71. package/zova/src/suite/cabloy-basic/modules/basic-app/src/bean/behavior.appModal.tsx +2 -0
  72. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  73. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +2 -1
  74. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/test/model/resource.types.ts +11 -0
  75. package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
  76. package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
  77. package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts +69 -12
  78. package/zova/src/suite-vendor/a-zova/modules/a-form/test/lib/formLayout.test.ts +157 -0
  79. package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
  80. package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.persister.ts +3 -1
  81. package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useQuery.ts +11 -13
  82. package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useState.ts +9 -7
  83. package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useStateGeneral.ts +5 -12
  84. package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/queryPersister.ts +17 -0
  85. package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/queryRefetch.ts +34 -0
  86. package/zova/src/suite-vendor/a-zova/modules/a-model/src/types/query.ts +27 -3
  87. package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/queryPersister.test.ts +115 -0
  88. package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/queryRefetch.test.ts +84 -0
  89. package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
  90. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/lib/schema.ts +27 -5
  91. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/model/sdk.ts +2 -2
  92. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/rest.ts +4 -0
  93. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/schema.ts +2 -5
  94. package/zova/src/suite-vendor/a-zova/modules/a-openapi/test/lib/schema.test.ts +179 -0
  95. package/zova/src/suite-vendor/a-zova/package.json +4 -4
package/.cabloy-version CHANGED
@@ -1 +1 @@
1
- 5.1.148
1
+ 5.1.150
@@ -18,7 +18,8 @@ After generating or extending a backend thread, check which follow-up layers app
18
18
  - treat the top-level `$Dto.create(...)` / `$Dto.update(...)` default omissions as a write-input authorization boundary, not as a reason to mechanically narrow ordinary `$Dto.get(...)` contracts
19
19
  - when a narrowed DTO or OpenAPI schema is expected to remove physical response fields, separately shape and verify the action response; declaration narrowing alone does not guarantee runtime field stripping
20
20
  - when stable Entity, Model, relation, or query truth exists, define the DTO projection first with `$Dto.*` and, where needed, `columns`, `include`, or `dtoClass`
21
- - for an inferred field, 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 field
21
+ - for an inferred/projected field, 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
22
+ - before using `$makeMetadata(...)`, confirm the `fields` key already has an inferred/projected schema; a true virtual key added only through `@Dto({ fields })` must use `$makeSchema(...)` with a final concrete `z.<type>()` schema, because `fieldSource(...)` maps a source path but supplies no missing type; see [Virtual fields in the DTO fields map](../../../../repo-docs/backend/dto-infer-generation.md#virtual-fields-in-the-dto-fields-map)
22
23
  - when inference cannot express the contract clearly, choose an explicit DTO deliberately; see [Default-first three-layer DTO authoring](../../../../repo-docs/backend/dto-infer-generation.md#default-first-three-layer-dto-authoring)
23
24
  - frontend contract impact
24
25
  - `@Api.field(...)` / `$makeSchema(...)` ordering: framework guards now preserve previously attached OpenAPI metadata across schema rebuilds, but structure-shaping schemaLike is still order-sensitive
@@ -180,6 +180,7 @@ Check whether the feature needs:
180
180
  - schema-driven UI or `$apiSchema` review
181
181
  - SSR hydration-equivalence review: classify state as SSR-required or intentionally deferred; keep server HTML and the hydration-time client render equivalent; defer private, cookie-unavailable, or browser-only query/load/render branches to an explicit post-hydration, admission, mounted, or interaction boundary
182
182
  - distinguish `$useStateData(...)` query ownership from readiness waits: `disableSuspenseOnInit` only skips its init-time suspense kick and does not prevent query creation or fetches; choose `$QueryEnsureLoaded(...)` or freshness helpers only at the later boundary that needs them
183
+ - verify that render-driving UI reads model/query-owned reactive state (`query.data` or a model-derived surface); keep awaited `refetch()` results local to one-shot interaction/orchestration and never as a parallel ongoing controller/render state copy
183
184
  - reverse fullstack handoff when newly added frontend resources will later be consumed by backend metadata or backend tooling
184
185
 
185
186
  If the frontend change introduces resources such as a custom form-field renderer, table-cell renderer, or other generated metadata that backend `ZovaRender.field(...)` / `ZovaRender.cell(...)` will consume, do not treat the task as frontend-only cleanup.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.1.150
4
+
5
+ ### Features
6
+
7
+ - Add `bypassPersister` support.
8
+ - Update query functionality.
9
+ - Add runbook updates.
10
+ - Add schema alias support.
11
+ - Add SKU stock availability support.
12
+ - Update related functionality and resources.
13
+
14
+ ### Bug Fixes
15
+
16
+ - Fix end-to-end tests.
17
+ - Correct `bypassPersister` behavior.
18
+ - Update schema tests.
19
+
20
+ ### Improvements
21
+
22
+ - Refactor the student model and service.
23
+ - Improve documentation for `bypassPersister`.
24
+
25
+ ## 5.1.149
26
+
27
+ ### Features
28
+
29
+ - Add student markdown support.
30
+ - Update application functionality.
31
+
32
+ ### Bug Fixes
33
+
34
+ - Fix the layout of the `fieldSource` field.
35
+
3
36
  ## 5.1.148
4
37
 
5
38
  ### Improvements
package/CLAUDE.md CHANGED
@@ -65,6 +65,7 @@ Before inventing a custom implementation path:
65
65
  - In Zova page routes, any route with dynamic `params` must define `route.name`; do not rely on unnamed path-keyed routes for typed `$params`. Static routes should omit `route.name` unless a documented named-route requirement exists; use `$router.getPagePath(...)` for canonical static URL generation rather than adding a name or alias for convenience. Ordinary business routes without `locale` params should omit app-config aliases unless a documented system, compatibility, or user-facing URL exception requires one. Choose `ssrProfile` from the page's rendering contract: Web remains `public` by default, while `session` is an explicit choice for cookie-backed state, protected admission, personalized first paint, or private SSR data; the absence of a locale parameter alone does not select a profile. `requiresAuth` is independent, so anonymous routes must explicitly use `requiresAuth: false`. Regenerate page metadata after route changes.
66
66
  - For frontend async state that affects rendering or interaction across consumers, prefer model-owned `$useStateData(...)` over controller-managed fetch/cache state.
67
67
  - Default to establishing such query state during render. Use `disableSuspenseOnInit: true` only for relatively stable query-backed state when you want to skip the init-time `query.suspense()` kick; it does not prevent query creation, fetches, or hydration-time rendering. If strict readiness is needed later, wait explicitly at the interaction boundary.
68
+ - For render-driving async state, read the current query-owned reactive surface (`query.data` or a model-derived projection); an awaited `query.refetch()` result is valid only for one-shot interaction or orchestration and must not become a parallel ongoing controller/render state owner.
68
69
  - In SSR, keep server HTML and the client's hydration-time initial render equivalent. When server rendering intentionally omits private, cookie-unavailable, or browser-only state, keep the same neutral shell or placeholder through hydration and defer its query/load/render branch to an explicit post-hydration, admission, mounted, or interaction boundary.
69
70
  - Keep repo-wide AI rules in `CLAUDE.md` short and durable; put branching Zova analysis workflows in `.claude/skills/`.
70
71
  - Do not modify shared environment identity or ports merely to bypass a busy resource. For intentional parallel worktree setup, follow `repo-docs/fullstack/parallel-worktree-environment.md`; otherwise wait for the shared resource or ask the user. Create or change worktree-local environment overrides only through the explicitly invoked `cabloy-worktree-environment` skill and its confirmation phase, and only in `vona/env/.env.local` and `zova/env/.env.local`; never modify flavor-, mode-, app-mode-, or runtime-specific `.env.*.local` files. The skill derives the standard `APP_NAME`, `SERVER_LISTEN_PORT`, `DEV_SERVER_PORT`, `DEV_SERVER_HMR_PORT`, and API-derived `API_BASE_URL` tuple only from Git worktree metadata and fixed port baselines; never read or expose `.env*` content while recommending values. Admin and Web are alternative commands using this shared tuple and must not run concurrently in one worktree; use another linked worktree for concurrent use. Detect the active edition before choosing scripts, and never run `npm run init` as an automatic follow-up.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cabloy",
3
- "version": "5.1.148",
3
+ "version": "5.1.150",
4
4
  "gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
5
5
  "description": "A Node.js fullstack framework",
6
6
  "keywords": [
@@ -125,6 +125,10 @@ const fullstackGroups = [
125
125
  text: 'Frontend Metadata Back to Backend',
126
126
  link: '/fullstack/frontend-metadata-to-backend',
127
127
  },
128
+ {
129
+ text: 'One-to-One Companion Resource',
130
+ link: '/fullstack/one-to-one-companion-resource-guide',
131
+ },
128
132
  {
129
133
  text: 'Edition Collaboration Differences',
130
134
  link: '/fullstack/edition-collaboration-differences',
@@ -183,7 +183,7 @@ A practical split is:
183
183
  - use inferred DTOs when the contract closely follows model structure or query shape
184
184
  - wrap inferred DTOs in a named DTO class when reuse or discoverability becomes more important
185
185
 
186
- When stable Entity, Model, relation, or query truth already exists, prefer inference first: use `$Dto.*` with `columns`, `include`, or `dtoClass` to define the projection; use `@Dto({ fields })` with `$makeMetadata(...)` for metadata-only differences or `$makeSchema(...)` for schema and validation differences; add an `@Api.field(...)` member only for a genuinely new field absent from the inferred contract. Keep an explicit DTO as a deliberate fallback when no suitable upstream truth exists or the inferred composition is less clear.
186
+ When stable Entity, Model, relation, or query truth already exists, prefer inference first: use `$Dto.*` with `columns`, `include`, or `dtoClass` to define the projection; use `@Dto({ fields })` with `$makeMetadata(...)` only for metadata-only differences on an existing inferred/projected field, or `$makeSchema(...)` for schema and validation differences. A true virtual `fields` key absent from that projection must use `$makeSchema(...)` with a final concrete `z.<type>()` schema; `fieldSource(...)` maps its binding but supplies no type. Add an `@Api.field(...)` member only for a genuinely new declared DTO property. See [Virtual fields in the DTO fields map](/backend/dto-infer-generation#virtual-fields-in-the-dto-fields-map) for the full decision rule. Keep an explicit DTO as a deliberate fallback when no suitable upstream truth exists or the inferred composition is less clear.
187
187
 
188
188
  Advanced inferred DTO shaping can also stay named and reusable through helper options such as `dtoClass`, especially for relation-aware contracts and nested DTO surfaces. `$Dto.get(...)` otherwise keeps the complete model-aware read shape by default; use `columns` or `dtoClass` for a genuine business projection, not merely to remove `iid` or `deleted`. For the canonical authoring sequence, see [Default-first three-layer DTO authoring](/backend/dto-infer-generation#default-first-three-layer-dto-authoring) and [Default read shape versus a public projection](/backend/dto-infer-generation#default-read-shape-versus-a-public-projection).
189
189
 
@@ -205,11 +205,12 @@ When creating DTOs:
205
205
 
206
206
  1. check whether an Entity, Model, relation, or query shape already provides suitable upstream contract truth
207
207
  2. define the inferred projection before redeclaring fields: choose the `$Dto.*` helper and, where needed, `columns`, `include`, or `dtoClass`
208
- 3. use `$makeMetadata(...)` for metadata-only refinement and `$makeSchema(...)` for schema or validation refinement of an inferred field
209
- 4. use `@Api.field(...)` for a genuinely new field, not as a second declaration of an inferred field
210
- 5. keep DTO validation and OpenAPI concerns aligned, and keep the final structure-defining schema last when using schema-like composition
211
- 6. decide whether the contract should remain inferred, be wrapped in a named inferred DTO, or deliberately fall back to an explicit DTO
212
- 7. treat DTO design as part of the contract between backend handlers, models, and frontend integration
208
+ 3. use `$makeMetadata(...)` for metadata-only refinement and `$makeSchema(...)` for schema or validation refinement of an inferred/projected field
209
+ 4. for a true virtual key added only through `@Dto({ fields })`, use `$makeSchema(...)` with a final concrete `z.<type>()` schema; `fieldSource(...)` supplies no missing type
210
+ 5. use `@Api.field(...)` for a genuinely new declared DTO property, not as a second declaration of an inferred field
211
+ 6. keep DTO validation and OpenAPI concerns aligned, and keep the final structure-defining schema last when using schema-like composition
212
+ 7. decide whether the contract should remain inferred, be wrapped in a named inferred DTO, or deliberately fall back to an explicit DTO
213
+ 8. treat DTO design as part of the contract between backend handlers, models, and frontend integration
213
214
 
214
215
  ## Where to read next
215
216
 
@@ -193,6 +193,25 @@ export class DtoStudentSelectReq extends $Dto.queryPage(EntityStudent, [
193
193
 
194
194
  Framework DTO composition preserves inherited OpenAPI metadata when a field schema is refined. That includes metadata such as titles, render information, and scene identity. It does **not** make structure-shaping schema-like arguments order-independent: keep the final structure-defining argument last and verify the effective schema rather than assuming a metadata merge can repair a reordered schema.
195
195
 
196
+ #### Virtual fields in the DTO fields map
197
+
198
+ The Layer 2 `$makeMetadata(...)` rule has a precondition: the field key must already be supplied by the inferred projection, whether through the `$Dto.*` base, `columns`, `include`, or `dtoClass`. It overlays metadata on that existing runtime schema.
199
+
200
+ A **true virtual DTO field** is a key added only through `@Dto({ fields })` and absent from that inherited projection. Define such a key with `$makeSchema(...)` and a final concrete Zod schema, even when its purpose is only to map a renderer to a nested source:
201
+
202
+ ```typescript
203
+ const descriptionMarkdownField = $makeSchema(
204
+ ZovaRender.fieldSource('content.descriptionMarkdown'),
205
+ ZovaRender.field('basic-markdown:formFieldMarkdown'),
206
+ v.optional(),
207
+ z.string(),
208
+ );
209
+ ```
210
+
211
+ `z.string()` establishes the virtual key's validation and emitted OpenAPI type. `fieldSource(...)` only maps the DTO-facing key to the canonical source path used by downstream schema-driven UI; it does not supply a type. `$makeMetadata(...)` alone has no inherited schema to overlay for a true virtual key, so its metadata is attached to an unconstrained fallback rather than a meaningful field schema.
212
+
213
+ Use a class-body `@Api.field(...)` member instead when the new field should be a declared, statically typed DTO property rather than a contract-only `fields`-map key.
214
+
196
215
  `@Dto({ fields })` changes the runtime contract and metadata. It does not rewrite the TypeScript property type inferred from the `$Dto.*` base class. Do not add a duplicate `declare` field or a second field decorator solely to mirror a runtime schema restriction unless a separate static contract is genuinely required and is type-compatible with the inferred base.
197
216
 
198
217
  ### Layer 3: add contract-only fields
@@ -507,10 +526,11 @@ When evaluating a return shape or input contract that closely follows model stru
507
526
  2. is the contract get/list/query/create/update/aggregate/group oriented, and which `$Dto.*` helper matches it?
508
527
  3. should `columns`, `include`, `with`, or `dtoClass` define the projection boundary?
509
528
  4. should the inferred DTO stay inline or be wrapped in a named DTO class?
510
- 5. for every local difference, is it metadata-only (`$makeMetadata(...)`) or schema-affecting (`$makeSchema(...)`)?
511
- 6. is every `@Api.field(...)` member genuinely new instead of a redeclared inferred field?
512
- 7. if `$makeSchema(...)` is used, is the structure-defining schema last and is emitted schema/OpenAPI verification planned?
513
- 8. does the resulting DTO also affect OpenAPI and frontend generation paths?
514
- 9. is CRUD generation already giving enough contract structure that another handwritten DTO would be redundant, or is an explicit DTO clearer?
529
+ 5. for every local difference, does the `fields` key already have an inferred/projected schema, or is it a true virtual key?
530
+ 6. use `$makeMetadata(...)` only for metadata-only refinement of an inferred/projected field; define every true virtual key with `$makeSchema(...)` and a final concrete `z.<type>()` schema
531
+ 7. is every `@Api.field(...)` member genuinely new instead of a redeclared inferred field?
532
+ 8. if `$makeSchema(...)` is used, is the structure-defining schema last and is emitted schema/OpenAPI verification planned?
533
+ 9. does the resulting DTO also affect OpenAPI and frontend generation paths?
534
+ 10. is CRUD generation already giving enough contract structure that another handwritten DTO would be redundant, or is an explicit DTO clearer?
515
535
 
516
536
  That helps reduce redundant type work and keeps contracts closer to the model truth.
@@ -7,6 +7,7 @@ Use this page together with:
7
7
  - [Frontend Markdown Guide](/frontend/markdown-guide)
8
8
  - [Validation Guide](/backend/validation-guide)
9
9
  - [Serialization Guide](/backend/serialization-guide)
10
+ - [One-to-One Companion Resource Architecture](/fullstack/one-to-one-companion-resource-guide) when Markdown source and derived HTML belong to an aggregate-owned companion record.
10
11
 
11
12
  ## What `a-markdown` provides
12
13
 
@@ -239,6 +239,7 @@ Read this guide together with:
239
239
  - [ORM Aggregate and Group Guide](/backend/orm-aggregate-group-guide)
240
240
  - [Multi-Database and Datasource Guide](/backend/multi-database-datasource)
241
241
  - [DTO Infer and Generation](/backend/dto-infer-generation)
242
+ - [One-to-One Companion Resource Architecture](/fullstack/one-to-one-companion-resource-guide) for aggregate-owned optional companion records, bounded projections, lifecycle ownership, and one-row-per-parent invariants.
242
243
 
243
244
  ## Implementation checks for model-relationship changes
244
245
 
@@ -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,12 +219,26 @@ 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 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.
241
+
228
242
  If a field must not render, make it invisible in schema metadata. Leaving it out of `formLayout.children` is not enough.
229
243
 
230
244
  ### Invalid declarations and diagnostics
@@ -260,7 +274,7 @@ Current behavior boundaries:
260
274
 
261
275
  ## Complete entry-form example
262
276
 
263
- The Student create DTO is the canonical complete example. It uses optional structural IDs, two tabs, a titled group, a responsive profile section, and a nested-details field:
277
+ The Student create DTO is the canonical complete example. It uses optional structural IDs, two tabs, separate semantic groups for profile and companion content, a responsive profile section, and a nested-details field:
264
278
 
265
279
  ```tsx
266
280
  ZovaRender.block('basic-pageentry:blockForm', {
@@ -290,6 +304,16 @@ ZovaRender.block('basic-pageentry:blockForm', {
290
304
  },
291
305
  ],
292
306
  },
307
+ {
308
+ type: 'group',
309
+ title: $locale('StudentContent'),
310
+ children: [
311
+ {
312
+ type: 'section',
313
+ children: [{ type: 'field', name: 'studentContentForm' }],
314
+ },
315
+ ],
316
+ },
293
317
  ],
294
318
  },
295
319
  {
@@ -312,6 +336,8 @@ ZovaRender.block('basic-pageentry:blockForm', {
312
336
  });
313
337
  ```
314
338
 
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.
340
+
315
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.
316
342
 
317
343
  Student update and view DTOs use the same structural shape. The normal form scene controls readonly behavior, while the page-entry toolbar decides whether Submit, Back, or other actions are available.
@@ -357,8 +383,9 @@ Here `formFieldLayout.inline: true` controls how each field wrapper is presented
357
383
  4. Use `formLayout` when the requirement is field placement, Grid or flow structure, groups, or tabs.
358
384
  5. Use `layout`, `formFieldLayout`, `options`, or provider behaviors when the requirement is one field's wrapper or renderer.
359
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.
360
- 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.
361
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.
362
389
 
363
390
  ## Source-reading and verification path
364
391
 
@@ -366,10 +393,14 @@ For source-level investigation, follow this order:
366
393
 
367
394
  1. `vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx` or `studentSelectResItem.tsx`
368
395
  2. `zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/resource/formLayout.ts`
369
- 3. `zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts`
370
- 4. `zova/src/suite/cabloy-basic/modules/basic-form/src/component/blockFormLayout/controller.tsx`
371
- 5. `vona/src/suite/a-training/modules/training-student/test/student.test.ts`
372
-
373
- 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.
374
405
 
375
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).
@@ -17,6 +17,7 @@ Use this page together with:
17
17
  - [SSR ClientOnly](/frontend/ssr-client-only)
18
18
  - [Backend Markdown Guide](/backend/markdown-guide)
19
19
  - [Image Guide](/frontend/image-guide)
20
+ - [One-to-One Companion Resource Architecture](/fullstack/one-to-one-companion-resource-guide) for the aggregate, projection, contract-loop, and edition-aware boundaries around Markdown companion content.
20
21
 
21
22
  > [!TIP]
22
23
  > **Choose the component by data contract**
@@ -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.
@@ -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:
@@ -66,3 +66,4 @@ That is exactly the kind of mistake good fullstack docs should prevent.
66
66
  - If you want the shared bidirectional model first, continue with [Contract Loop Playbook](/fullstack/contract-loop-playbook).
67
67
  - If your current task is the forward contract bridge, continue with [Backend OpenAPI to Frontend SDK](/fullstack/openapi-to-sdk).
68
68
  - If your current task is the reverse metadata/resource bridge, continue with [Frontend Metadata Back to Backend](/fullstack/frontend-metadata-to-backend).
69
+ - If a parent owns an optional one-to-one content or extension record, continue with [One-to-One Companion Resource Architecture](/fullstack/one-to-one-companion-resource-guide).