cabloy 5.1.108 → 5.1.110

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/.cabloy-version +1 -1
  2. package/.gitignore +5 -2
  3. package/CHANGELOG.md +43 -0
  4. package/cabloy-docs/.vitepress/config.mjs +1 -0
  5. package/cabloy-docs/ai/playbook-module-removal.md +1 -1
  6. package/cabloy-docs/ai/virtual-decorator-guidance.md +9 -9
  7. package/cabloy-docs/backend/websocket-call-flow.md +1 -1
  8. package/cabloy-docs/backend/websocket-guide.md +1 -1
  9. package/cabloy-docs/frontend/fetch-interceptor-guide.md +8 -8
  10. package/cabloy-docs/frontend/form-guide.md +3 -1
  11. package/cabloy-docs/frontend/form-layout-guide.md +322 -0
  12. package/cabloy-docs/frontend/introduction.md +1 -0
  13. package/cabloy-docs/frontend/resource-entry-page-deep-dive.md +2 -0
  14. package/cabloy-docs/frontend/router-tabs-layout-integration.md +2 -1
  15. package/cabloy-docs/frontend/scripts.md +56 -1
  16. package/cabloy-docs/frontend/ssr-build-deploy-guide.md +1 -1
  17. package/cabloy-docs/frontend/ssr-overview.md +6 -0
  18. package/cabloy-docs/frontend/ssr-review-checklist.md +1 -1
  19. package/cabloy-docs/frontend/ssr-troubleshooting-guide.md +9 -9
  20. package/cabloy-docs/frontend/table-resource-crud-cookbook.md +33 -1
  21. package/cabloy-docs/frontend/zova-form-source-reading-map.md +36 -4
  22. package/cabloy-docs/frontend/zova-form-under-the-hood.md +13 -0
  23. package/cabloy-docs/fullstack/framework-performance.md +3 -3
  24. package/cabloy-docs/fullstack/quickstart.md +14 -0
  25. package/cabloy-docs/reference/repo-scripts.md +79 -0
  26. package/e2e/config/playwright.basic.config.ts +3 -0
  27. package/e2e/config/playwright.commerce.config.ts +3 -0
  28. package/e2e/config/playwright.shared.config.ts +38 -0
  29. package/e2e/scripts/e2e.ts +33 -0
  30. package/e2e/scripts/startE2eVona.ts +38 -0
  31. package/e2e/scripts/testE2eClean.ts +56 -0
  32. package/e2e/specs/a-basic/basic.spec.ts +271 -0
  33. package/e2e/specs/a-commerce/commerce.spec.ts +61 -0
  34. package/package.json +17 -1
  35. package/scripts/upgrade.ts +182 -7
  36. package/test-results/.last-run.json +4 -0
  37. package/vona/packages-vona/vona/package.json +1 -1
  38. package/vona/pnpm-lock.yaml +304 -45
  39. package/vona/src/suite/a-commerce/modules/commerce-catalog/package.json +52 -0
  40. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +29 -0
  41. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/this.ts +2 -0
  42. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/index.ts +1 -0
  43. package/vona/src/suite/a-commerce/modules/commerce-catalog/tsconfig.build.json +11 -0
  44. package/vona/src/suite/a-commerce/modules/commerce-catalog/tsconfig.json +7 -0
  45. package/vona/src/suite/a-commerce/modules/commerce-member/package.json +52 -0
  46. package/vona/src/suite/a-commerce/modules/commerce-member/src/.metadata/index.ts +29 -0
  47. package/vona/src/suite/a-commerce/modules/commerce-member/src/.metadata/this.ts +2 -0
  48. package/vona/src/suite/a-commerce/modules/commerce-member/src/index.ts +1 -0
  49. package/vona/src/suite/a-commerce/modules/commerce-member/tsconfig.build.json +11 -0
  50. package/vona/src/suite/a-commerce/modules/commerce-member/tsconfig.json +7 -0
  51. package/vona/src/suite/a-commerce/modules/commerce-payment/package.json +52 -0
  52. package/vona/src/suite/a-commerce/modules/commerce-payment/src/.metadata/index.ts +29 -0
  53. package/vona/src/suite/a-commerce/modules/commerce-payment/src/.metadata/this.ts +2 -0
  54. package/vona/src/suite/a-commerce/modules/commerce-payment/src/index.ts +1 -0
  55. package/vona/src/suite/a-commerce/modules/commerce-payment/tsconfig.build.json +11 -0
  56. package/vona/src/suite/a-commerce/modules/commerce-payment/tsconfig.json +7 -0
  57. package/vona/src/suite/a-commerce/modules/commerce-promotion/package.json +52 -0
  58. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/index.ts +29 -0
  59. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/this.ts +2 -0
  60. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/index.ts +1 -0
  61. package/vona/src/suite/a-commerce/modules/commerce-promotion/tsconfig.build.json +11 -0
  62. package/vona/src/suite/a-commerce/modules/commerce-promotion/tsconfig.json +7 -0
  63. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/package.json +53 -0
  64. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/index.ts +81 -0
  65. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/this.ts +2 -0
  66. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/bean/ssrMenu.home.ts +21 -0
  67. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/bean/ssrSite.commerceAdmin.ts +34 -0
  68. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/index.ts +1 -0
  69. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/tsconfig.build.json +11 -0
  70. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/tsconfig.json +7 -0
  71. package/vona/src/suite/a-commerce/modules/commerce-siteweb/package.json +53 -0
  72. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/.metadata/index.ts +81 -0
  73. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/.metadata/this.ts +2 -0
  74. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/bean/ssrMenu.home.ts +20 -0
  75. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/bean/ssrSite.commerce.ts +34 -0
  76. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/index.ts +1 -0
  77. package/vona/src/suite/a-commerce/modules/commerce-siteweb/tsconfig.build.json +11 -0
  78. package/vona/src/suite/a-commerce/modules/commerce-siteweb/tsconfig.json +7 -0
  79. package/vona/src/suite/a-commerce/modules/commerce-trade/package.json +52 -0
  80. package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +29 -0
  81. package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/this.ts +2 -0
  82. package/vona/src/suite/a-commerce/modules/commerce-trade/src/index.ts +1 -0
  83. package/vona/src/suite/a-commerce/modules/commerce-trade/tsconfig.build.json +11 -0
  84. package/vona/src/suite/a-commerce/modules/commerce-trade/tsconfig.json +7 -0
  85. package/vona/src/suite/a-commerce/package.json +18 -0
  86. package/vona/src/suite/a-commerce/tsconfig.base.json +4 -0
  87. package/vona/src/suite/a-commerce/tsconfig.json +28 -0
  88. package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +2 -0
  89. package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +2 -0
  90. package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +50 -1
  91. package/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectResItem.tsx +21 -1
  92. package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +47 -1
  93. package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +47 -1
  94. package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +61 -2
  95. package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
  96. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/bean/bean.database.ts +1 -1
  97. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/bean/schedule.softDeletionPrune.ts +1 -1
  98. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/bean.model/bean.model_meta.ts +1 -1
  99. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoAggregate.ts +1 -1
  100. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoCreate.ts +1 -1
  101. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoGroup.ts +1 -1
  102. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoSelectAndCount.ts +1 -1
  103. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoUpdate.ts +1 -1
  104. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/modelCacheBase.ts +1 -1
  105. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/relations.ts +1 -1
  106. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/relationsDynamic.ts +1 -1
  107. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/relationsMutate.ts +1 -1
  108. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/relationsStatic.ts +1 -1
  109. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/database.ts +1 -1
  110. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/databaseDialectBase_.ts +10 -3
  111. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/db_.ts +1 -1
  112. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  113. package/zova/env/.env.cabloyCommerce +24 -0
  114. package/zova/env/.env.cabloyCommerceAdmin +19 -0
  115. package/zova/package.original.json +10 -1
  116. package/zova/packages-zova/zova/package.json +2 -2
  117. package/zova/pnpm-lock.yaml +145 -31
  118. package/zova/src/front/config/config/config.cabloyCommerce.ts +16 -0
  119. package/zova/src/front/config/config/config.cabloyCommerceAdmin.ts +13 -0
  120. package/zova/src/suite/a-commerce/modules/commerce-catalog/package.json +52 -0
  121. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +26 -0
  122. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/this.ts +2 -0
  123. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/index.ts +1 -0
  124. package/zova/src/suite/a-commerce/modules/commerce-catalog/tsconfig.build.json +13 -0
  125. package/zova/src/suite/a-commerce/modules/commerce-catalog/tsconfig.json +5 -0
  126. package/zova/src/suite/a-commerce/modules/commerce-member/package.json +52 -0
  127. package/zova/src/suite/a-commerce/modules/commerce-member/src/.metadata/index.ts +26 -0
  128. package/zova/src/suite/a-commerce/modules/commerce-member/src/.metadata/this.ts +2 -0
  129. package/zova/src/suite/a-commerce/modules/commerce-member/src/index.ts +1 -0
  130. package/zova/src/suite/a-commerce/modules/commerce-member/tsconfig.build.json +13 -0
  131. package/zova/src/suite/a-commerce/modules/commerce-member/tsconfig.json +5 -0
  132. package/zova/src/suite/a-commerce/modules/commerce-payment/package.json +52 -0
  133. package/zova/src/suite/a-commerce/modules/commerce-payment/src/.metadata/index.ts +26 -0
  134. package/zova/src/suite/a-commerce/modules/commerce-payment/src/.metadata/this.ts +2 -0
  135. package/zova/src/suite/a-commerce/modules/commerce-payment/src/index.ts +1 -0
  136. package/zova/src/suite/a-commerce/modules/commerce-payment/tsconfig.build.json +13 -0
  137. package/zova/src/suite/a-commerce/modules/commerce-payment/tsconfig.json +5 -0
  138. package/zova/src/suite/a-commerce/modules/commerce-promotion/package.json +52 -0
  139. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/index.ts +26 -0
  140. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/this.ts +2 -0
  141. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/index.ts +1 -0
  142. package/zova/src/suite/a-commerce/modules/commerce-promotion/tsconfig.build.json +13 -0
  143. package/zova/src/suite/a-commerce/modules/commerce-promotion/tsconfig.json +5 -0
  144. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/package.json +52 -0
  145. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/index.ts +26 -0
  146. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/this.ts +2 -0
  147. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/index.ts +1 -0
  148. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/tsconfig.build.json +13 -0
  149. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/tsconfig.json +5 -0
  150. package/zova/src/suite/a-commerce/modules/commerce-siteweb/package.json +52 -0
  151. package/zova/src/suite/a-commerce/modules/commerce-siteweb/src/.metadata/index.ts +26 -0
  152. package/zova/src/suite/a-commerce/modules/commerce-siteweb/src/.metadata/this.ts +2 -0
  153. package/zova/src/suite/a-commerce/modules/commerce-siteweb/src/index.ts +1 -0
  154. package/zova/src/suite/a-commerce/modules/commerce-siteweb/tsconfig.build.json +13 -0
  155. package/zova/src/suite/a-commerce/modules/commerce-siteweb/tsconfig.json +5 -0
  156. package/zova/src/suite/a-commerce/modules/commerce-trade/package.json +52 -0
  157. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +26 -0
  158. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/this.ts +2 -0
  159. package/zova/src/suite/a-commerce/modules/commerce-trade/src/index.ts +1 -0
  160. package/zova/src/suite/a-commerce/modules/commerce-trade/tsconfig.build.json +13 -0
  161. package/zova/src/suite/a-commerce/modules/commerce-trade/tsconfig.json +5 -0
  162. package/zova/src/suite/a-commerce/package.json +18 -0
  163. package/zova/src/suite/a-commerce/tsconfig.base.json +4 -0
  164. package/zova/src/suite/a-commerce/tsconfig.json +4 -0
  165. package/zova/src/suite/cabloy-basic/modules/basic-form/src/.metadata/component/blockFormLayout.ts +31 -0
  166. package/zova/src/suite/cabloy-basic/modules/basic-form/src/.metadata/index.ts +13 -0
  167. package/zova/src/suite/cabloy-basic/modules/basic-form/src/component/blockFormLayout/controller.tsx +188 -0
  168. package/zova/src/suite/cabloy-basic/modules/basic-page/src/.metadata/component/blockFilterActions.ts +31 -0
  169. package/zova/src/suite/cabloy-basic/modules/basic-page/src/.metadata/index.ts +13 -0
  170. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockFilter/controller.tsx +61 -31
  171. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockFilterActions/controller.tsx +51 -0
  172. package/zova/src/suite/cabloy-basic/modules/basic-page/src/types/page.ts +10 -0
  173. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockForm/controller.tsx +9 -2
  174. package/zova/src/suite-vendor/a-zova/modules/a-app/package.json +1 -1
  175. package/zova/src/suite-vendor/a-zova/modules/a-app/src/component/app/controller.tsx +6 -0
  176. package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +5 -3
  177. package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/form/controller.tsx +44 -3
  178. package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/form/render.tsx +22 -4
  179. package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/formField/controller.tsx +3 -2
  180. package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts +203 -0
  181. package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/index.ts +1 -0
  182. package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/formField.ts +7 -4
  183. package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/formLayout.ts +63 -0
  184. package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/index.ts +1 -0
  185. package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
  186. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/action.ts +1 -1
  187. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/resource/formLayout.ts +56 -0
  188. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/resource/index.ts +1 -0
  189. package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
  190. package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssr.ts +19 -1
  191. package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/monkey.ts +5 -0
  192. package/zova/src/suite-vendor/a-zova/package.json +5 -5
package/.cabloy-version CHANGED
@@ -1 +1 @@
1
- 5.1.108
1
+ 5.1.110
package/.gitignore CHANGED
@@ -1,6 +1,10 @@
1
1
  node_modules
2
2
  miniprogram_npm
3
3
  .assets/
4
+
5
+ test-results/
6
+ playwright-report/
7
+ blob-report/
4
8
  **/vite.config.ts.timestamp-*
5
9
  **/coverage
6
10
  **/dist/
@@ -72,5 +76,4 @@ vona/docker-compose.yml
72
76
  /vona/src/backend/play
73
77
  !/zova/packages-utils/quasar-app-extension-zova/dist
74
78
 
75
- vona/src/suite/cabloy-basic/modules/basic-siteadmin/assets/site
76
- vona/src/suite/cabloy-basic/modules/basic-siteweb/assets/site
79
+ **/assets/site
package/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.1.110
4
+
5
+ ### Features
6
+
7
+ - Add form layout sections for flow forms.
8
+ - Add a Commerce development E2E workflow.
9
+ - Establish the A-Commerce Phase 20 foundation.
10
+ - Upgrade the Basic E2E baseline.
11
+ - Add a Basic SSR E2E test skeleton.
12
+
13
+ ### Bug Fixes
14
+
15
+ - Complete the Zova SSR hydration lifecycle.
16
+ - Clarify E2E upgrade repair behavior.
17
+
18
+ ### Improvements
19
+
20
+ - Rename and reorganize the E2E runner and harness files.
21
+ - Standardize E2E test selection.
22
+ - Normalize Commerce script commands.
23
+ - Add coverage for the Student flow filter layout.
24
+ - Update Student selection item and controller components.
25
+ - Update framework performance documentation.
26
+ - Add an A-Commerce planning baseline.
27
+ - Add a Form Layout guide.
28
+
29
+ ## 5.1.109
30
+
31
+ ### Features
32
+
33
+ - Add DTO-driven form layouts.
34
+ - Add configurable filter form layouts.
35
+ - Add a student view form layout.
36
+ - Display form error counts on layout tabs.
37
+ - Allow form layout IDs to be optional.
38
+
39
+ ### Improvements
40
+
41
+ - Upgrade TanStack Vue Form.
42
+ - Replace the deprecated TanStack Form store hook.
43
+ - Localize form layout tab state.
44
+ - Clarify DTO render field typing boundaries in the documentation.
45
+
3
46
  ## 5.1.108
4
47
 
5
48
  ### Features
@@ -429,6 +429,7 @@ export default defineConfig({
429
429
  items: [
430
430
  { text: 'Component Guide', link: '/frontend/component-guide' },
431
431
  { text: 'Form Guide', link: '/frontend/form-guide' },
432
+ { text: 'Form Layout Guide', link: '/frontend/form-layout-guide' },
432
433
  { text: 'Image Guide', link: '/frontend/image-guide' },
433
434
  { text: 'File Guide', link: '/frontend/file-guide' },
434
435
  {
@@ -161,4 +161,4 @@ Not:
161
161
 
162
162
  1. delete a few generated files first
163
163
  2. guess which paths matter
164
- 3. leave workspace dependencies or generated registrations stale
164
+ 3. leave workspace dependencies or generated registrations stale
@@ -153,15 +153,15 @@ If a class does not need container-managed behavior, it usually belongs in `src/
153
153
 
154
154
  ## Decision table
155
155
 
156
- | Scenario | Use `@Virtual()` | Recommended placement | Reason |
157
- | --- | --- | --- | --- |
158
- | independent business service | No | `src/service` | it should own its own runtime resource boundary |
159
- | runtime-anchor base with container behavior | Yes | `src/service` or `src/service/*_.ts` | it is container-managed but belongs logically to the parent bean system |
160
- | bean-scene facade that intentionally stays on shorthand surface | Yes, if it is a bridge node | `src/bean` | it participates in shorthand lookup but is not a new ownership center |
161
- | compatibility / dialect / variant node built on a parent bean | Yes | same scene as the concrete variant type | runtime ownership should inherit from the parent chain |
162
- | pure helper or superclass convenience class | No | `src/lib` | no bean ownership semantics are needed |
163
- | class kept in `src/bean` only to expose shortcut lookup | Usually no | re-evaluate placement | `@Virtual()` is not a shorthand filter |
164
- | misplaced bean-scene class that should not be globally visible | No | move to `src/lib` or `src/service` | fix placement, not metadata |
156
+ | Scenario | Use `@Virtual()` | Recommended placement | Reason |
157
+ | --------------------------------------------------------------- | --------------------------- | --------------------------------------- | ----------------------------------------------------------------------- |
158
+ | independent business service | No | `src/service` | it should own its own runtime resource boundary |
159
+ | runtime-anchor base with container behavior | Yes | `src/service` or `src/service/*_.ts` | it is container-managed but belongs logically to the parent bean system |
160
+ | bean-scene facade that intentionally stays on shorthand surface | Yes, if it is a bridge node | `src/bean` | it participates in shorthand lookup but is not a new ownership center |
161
+ | compatibility / dialect / variant node built on a parent bean | Yes | same scene as the concrete variant type | runtime ownership should inherit from the parent chain |
162
+ | pure helper or superclass convenience class | No | `src/lib` | no bean ownership semantics are needed |
163
+ | class kept in `src/bean` only to expose shortcut lookup | Usually no | re-evaluate placement | `@Virtual()` is not a shorthand filter |
164
+ | misplaced bean-scene class that should not be globally visible | No | move to `src/lib` or `src/service` | fix placement, not metadata |
165
165
 
166
166
  ## Worked examples
167
167
 
@@ -432,4 +432,4 @@ If you need the broader context next, read:
432
432
  - [Web Socket Guide](/backend/websocket-guide) for the architecture and extension model
433
433
  - [Web Socket Usage Guide](/backend/websocket-usage-guide) for server-side authoring patterns
434
434
  - [Web Socket Protocol Guide](/backend/websocket-protocol-guide) for the client-visible wire format
435
- - [Broadcast Guide](/backend/broadcast-guide) and [Worker Guide](/backend/worker-guide) for cross-worker delivery context
435
+ - [Broadcast Guide](/backend/broadcast-guide) and [Worker Guide](/backend/worker-guide) for cross-worker delivery context
@@ -452,4 +452,4 @@ When extending or reviewing Web Socket behavior, ask:
452
452
  4. should delivery be point-to-point or namespace-wide?
453
453
  5. is the behavior local to one worker, or should it propagate through broadcast?
454
454
 
455
- That helps backend extensions stay aligned with the existing `a-socket` architecture instead of bypassing the framework transport model.
455
+ That helps backend extensions stay aligned with the existing `a-socket` architecture instead of bypassing the framework transport model.
@@ -258,13 +258,13 @@ That distinction matters because the order is part of the onion dependency model
258
258
 
259
259
  A source-aligned hook matrix for the current built-ins is:
260
260
 
261
- | Interceptor | onRequest | onRequestError | onResponse | onResponseError |
262
- | --- | --- | --- | --- | --- |
263
- | `mock` | - | - | - | yes |
264
- | `headers` | yes | - | - | - |
265
- | `jwt` | yes | - | - | - |
266
- | `performAction` | yes | - | - | - |
267
- | `body` | - | - | yes | yes |
261
+ | Interceptor | onRequest | onRequestError | onResponse | onResponseError |
262
+ | --------------- | --------- | -------------- | ---------- | --------------- |
263
+ | `mock` | - | - | - | yes |
264
+ | `headers` | yes | - | - | - |
265
+ | `jwt` | yes | - | - | - |
266
+ | `performAction` | yes | - | - | - |
267
+ | `body` | - | - | yes | yes |
268
268
 
269
269
  This table is only a reading shortcut. The authoritative source is still the concrete bean implementation under `a-interceptor/src/bean/`.
270
270
 
@@ -437,4 +437,4 @@ When you use this page to reason about current behavior, verify:
437
437
  3. `ServiceComposer` still merges `config.interceptors[...]` into the final onion options
438
438
  4. the built-in bean roles still match their current source files
439
439
 
440
- That keeps the explanation tied to current source rather than to stale assumptions.
440
+ That keeps the explanation tied to current source rather than to stale assumptions.
@@ -413,7 +413,9 @@ Use `layout` for concerns such as:
413
413
  - label text
414
414
  - icon prefix/suffix
415
415
  - field wrapper presentation
416
- - row-level layout intent
416
+ - one field's row-level presentation intent
417
+
418
+ `layout` is a field-level surface. When the requirement is to arrange multiple schema fields into responsive sections, groups, or tabs, use DTO-driven `formLayout` with `basic-form:blockFormLayout`; see [Form Layout Guide](/frontend/form-layout-guide).
417
419
 
418
420
  ### Use `options` for renderer-specific input props
419
421
 
@@ -0,0 +1,322 @@
1
+ # Form Layout Guide
2
+
3
+ Use Form Layout when a schema-driven resource form needs a deliberate structure: field order, Grid or flow sections, groups, or tabs.
4
+
5
+ This guide covers the **structural** layout contract authored in DTO metadata and rendered through `basic-form:blockFormLayout`. It does not replace [Form Guide](/frontend/form-guide), which explains `ZForm`, field rendering, validation, and manual or mixed forms.
6
+
7
+ > [!TIP]
8
+ > Cabloy Basic implements the renderer described here with DaisyUI and Tailwind CSS. The `formLayout` contract and its resolver are shared Zova surfaces, but group, Grid, flow, and tab presentation are Basic-specific. Do not assume Cabloy Start uses identical markup or styling.
9
+
10
+ ## The layout layers are different
11
+
12
+ Several APIs contain the word “layout,” but they own different concerns:
13
+
14
+ | Surface | Owns | Does not own |
15
+ | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
16
+ | `formLayout` | Field placement, Grid/flow sections, groups, responsive spans, and tabs | Field renderer selection, validation rules, submit policy |
17
+ | `basic-form:blockFormLayout` | Resolving and rendering a structural `formLayout` tree in Cabloy Basic | Page-entry or filter actions |
18
+ | `layout`, `formFieldLayout`, `FormFieldLayout` | One field's label and wrapper presentation: inline/block mode, icons, borders, header/footer, class, and style | Sections, Grid/flow placement, groups, or tabs |
19
+
20
+ For example, `formFieldLayout: { inline: false }` makes each field use a block-style wrapper. It does not create a grid. Pair it with `basic-form:blockFormLayout` when the fields also need structural placement.
21
+
22
+ Read [Form Guide](/frontend/form-guide) for field wrapper and provider customization, and [Behavior Guide](/frontend/behavior-guide) for the `FormFieldLayout` behavior pipeline.
23
+
24
+ ## Compose the layout through resource blocks
25
+
26
+ For resource forms, author structural layout in backend DTO metadata with `ZovaRender.block(...)`. The helper creates contract metadata; it does not render the form itself. Zova later consumes the generated schema metadata and renders the registered blocks.
27
+
28
+ ### Entry form composition
29
+
30
+ A resource entry DTO normally nests the layout block inside the page-entry form block:
31
+
32
+ ```tsx
33
+ @Dto({
34
+ blocks: [
35
+ ZovaRender.block('basic-pageentry:blockPageEntry', {
36
+ blocks: [
37
+ ZovaRender.block('basic-pageentry:blockForm', {
38
+ blocks: [
39
+ ZovaRender.block('basic-form:blockFormLayout', {
40
+ formLayout: {
41
+ children: [/* fields, groups, sections, or tabs */],
42
+ },
43
+ }),
44
+ ],
45
+ }),
46
+ ZovaRender.block('basic-pageentry:blockToolbarRow', {
47
+ actions: [/* Submit, Back, and other page-entry actions */],
48
+ }),
49
+ ],
50
+ }),
51
+ ],
52
+ })
53
+ ```
54
+
55
+ The responsibilities stay separate:
56
+
57
+ - the DTO supplies the structural contract metadata
58
+ - `basic-pageentry:blockForm` bridges page-entry form state, schema, and scene into `ZForm`
59
+ - `basic-form:blockFormLayout` places schema fields
60
+ - `basic-pageentry:blockToolbarRow` owns page-entry actions and their scene/permission rules
61
+
62
+ Form Layout changes neither readonly behavior nor actions. Create, update, and view scenes can reuse the same structure while the normal form and page-entry pipelines decide field state and available actions. See [Resource Entry Page Deep Dive](/frontend/resource-entry-page-deep-dive) for the wider entry-page runtime.
63
+
64
+ ### Filter form composition
65
+
66
+ A filter uses the filter block as its host and keeps filter actions as an explicit sibling block:
67
+
68
+ ```tsx
69
+ ZovaRender.block('basic-page:blockFilter', {
70
+ formFieldLayout: { inline: false },
71
+ blocks: [
72
+ ZovaRender.block('basic-form:blockFormLayout', {
73
+ formLayout: {
74
+ children: [
75
+ /* structural nodes */
76
+ ],
77
+ },
78
+ }),
79
+ ZovaRender.block('basic-page:blockFilterActions'),
80
+ ],
81
+ });
82
+ ```
83
+
84
+ A nonempty `blocks` list replaces `ZForm`'s automatic body and footer. Therefore a structured filter must explicitly include `basic-page:blockFilterActions`; it owns Search and Reset and keeps the filter's existing normalization and page-query handoff. See [Table + Resource CRUD Cookbook](/frontend/table-resource-crud-cookbook#use-blocks-for-a-structural-filter-layout) for the filter ownership model.
85
+
86
+ ## Layout node grammar
87
+
88
+ `formLayout` has a root `children` array. The node types deliberately have limited nesting rather than arbitrary recursion:
89
+
90
+ ```text
91
+ formLayout
92
+ ├─ field
93
+ ├─ section
94
+ │ └─ field
95
+ ├─ group
96
+ │ ├─ field
97
+ │ ├─ group
98
+ │ └─ section
99
+ └─ tabs
100
+ └─ tab
101
+ ├─ field
102
+ ├─ group
103
+ └─ section
104
+ ```
105
+
106
+ | Node | Key properties | Allowed children | Use it for |
107
+ | --------- | ---------------------------------------------------------- | ------------------------ | ----------------------------------------- |
108
+ | `field` | required `name`; optional `span` | none | Place one resolved schema field |
109
+ | `section` | optional `id`, `title`, `description`, `layout`, `columns` | fields only | A Grid or wrapping flow field layout |
110
+ | `group` | optional `id`, `title`, `description` | fields, groups, sections | A semantic, bordered fieldset-style group |
111
+ | `tabs` | optional `id` | tabs only | One tab container |
112
+ | `tab` | optional `id`; required `title` | fields, groups, sections | One tab panel |
113
+
114
+ A section is a layout boundary. It uses the Grid strategy by default; set `layout: 'flow'` for compact, left-packed fields that wrap at their intrinsic widths. Use a group when the fields need a semantic or visual boundary, and place a section inside that group when it also needs Grid columns or flow placement. There is no separate `row` node: Grid and flow placement create rows automatically.
115
+
116
+ Nested tabs are not part of the current contract. Likewise, a section cannot contain a group or another section.
117
+
118
+ ## Grid sections and field spans
119
+
120
+ Grid is the default section strategy: omit `layout` or set `layout: 'grid'`. Both `section.columns` and `field.span` use the same responsive shape:
121
+
122
+ ```ts
123
+ {
124
+ default?: 1 | 2 | 3 | 4,
125
+ md?: 1 | 2 | 3 | 4,
126
+ lg?: 1 | 2 | 3 | 4,
127
+ }
128
+ ```
129
+
130
+ - `columns` chooses how many columns a section has at each breakpoint.
131
+ - `span` chooses how many of those columns a field occupies.
132
+ - In Cabloy Basic, these become Tailwind classes such as `grid-cols-2`, `md:grid-cols-2`, `col-span-2`, and `md:col-span-2`.
133
+ - A section with no `columns.default` renders as one column.
134
+ - A field with no `span` takes its normal grid cell.
135
+
136
+ The following compact filter structure becomes one column by default, two columns at `md`, and makes `createdAt` use both medium columns:
137
+
138
+ ```tsx
139
+ formLayout: {
140
+ children: [
141
+ {
142
+ type: 'section',
143
+ columns: { default: 1, md: 2 },
144
+ children: [
145
+ { type: 'field', name: 'name' },
146
+ { type: 'field', name: 'level' },
147
+ { type: 'field', name: 'createdAt', span: { default: 1, md: 2 } },
148
+ ],
149
+ },
150
+ ],
151
+ },
152
+ ```
153
+
154
+ ## Flow sections
155
+
156
+ Set `layout: 'flow'` when compact fields should appear from left to right without being distributed across equal Grid columns. Cabloy Basic renders a flow section as a wrapping flex layout, so each field keeps its renderer-defined width and moves to the next line only when space runs out.
157
+
158
+ ```tsx
159
+ {
160
+ type: 'section',
161
+ layout: 'flow',
162
+ children: [
163
+ { type: 'field', name: 'name' },
164
+ { type: 'field', name: 'level' },
165
+ { type: 'field', name: 'createdAt' },
166
+ ],
167
+ }
168
+ ```
169
+
170
+ `columns` and `span` are Grid-only settings. Omit them from flow sections; they do not determine flex widths or offsets. Use `formFieldLayout.inline` independently when the fields themselves should use compact inline wrappers.
171
+
172
+ ## How the resolver handles the declared tree
173
+
174
+ Before rendering, `resolveFormLayout(...)` reconciles `formLayout` with the current scene's resolved schema properties. This makes the declaration a **placement overlay**, not an allow-list.
175
+
176
+ ### Eligible and omitted fields
177
+
178
+ 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.
179
+
180
+ If a field must not render, make it invisible in schema metadata. Leaving it out of `formLayout.children` is not enough.
181
+
182
+ ### Invalid declarations and diagnostics
183
+
184
+ The resolver keeps the first occurrence of each field and removes later duplicates. It also removes field names that are not eligible in the current schema scene.
185
+
186
+ | Situation | Resolver result | Diagnostic |
187
+ | ----------------------------------------- | --------------------------------------------- | ---------------- |
188
+ | Unknown or invisible field name | Field is removed | `unknownField` |
189
+ | Repeated field name | Later field is removed | `duplicateField` |
190
+ | Repeated structural ID | Later group, section, tabs, or tab is removed | `duplicateId` |
191
+ | Structural node has no surviving children | Node is removed | none |
192
+
193
+ Groups, sections, tab containers, and tabs receive an ID even when the DTO omits one. The resolver derives it from the node type and index path, for example `tabs-0` or `section-0-1-0`. Omit IDs for simple static layouts; provide explicit IDs when external state, diagnostics, tests, or future extensions need a stable structural reference.
194
+
195
+ > [!WARNING]
196
+ > Diagnostics are returned in the resolved layout plan, but the current `basic-form:blockFormLayout` renderer does not display or log them. Treat field names and explicit IDs as metadata that must be reviewed and tested, rather than expecting a visible authoring error at runtime.
197
+
198
+ ## Tabs and validation feedback
199
+
200
+ The Basic renderer keeps active-tab state locally for each `tabs` node. If no saved active tab remains valid, it falls back to the first surviving tab.
201
+
202
+ It renders native tab buttons and panels with `role="tablist"`, `role="tab"`, `role="tabpanel"`, `aria-selected`, `aria-controls`, and `aria-labelledby` relationships. A tab receives an error badge when fields below it have validation errors. The badge counts fields with errors, not individual error messages.
203
+
204
+ Field error messages remain the responsibility of the field-layout behavior. The structural layout only summarizes their presence on tabs.
205
+
206
+ Current behavior boundaries:
207
+
208
+ - an error badge does not automatically activate or focus the tab containing the invalid field
209
+ - tab state belongs to the `basic-form:blockFormLayout` component, not the form globally
210
+ - the contract does not support nested tabs
211
+ - Form Layout does not select field renderers, change field visibility, or alter validation and submit policies
212
+
213
+ ## Complete entry-form example
214
+
215
+ 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:
216
+
217
+ ```tsx
218
+ ZovaRender.block('basic-pageentry:blockForm', {
219
+ blocks: [
220
+ ZovaRender.block('basic-form:blockFormLayout', {
221
+ formLayout: {
222
+ children: [
223
+ {
224
+ type: 'tabs',
225
+ children: [
226
+ {
227
+ type: 'tab',
228
+ title: $locale('BasicInformation'),
229
+ children: [
230
+ {
231
+ type: 'group',
232
+ title: $locale('StudentProfile'),
233
+ children: [
234
+ {
235
+ type: 'section',
236
+ columns: { default: 1, md: 2 },
237
+ children: [
238
+ { type: 'field', name: 'name' },
239
+ { type: 'field', name: 'mobile' },
240
+ { type: 'field', name: 'imageId' },
241
+ ],
242
+ },
243
+ ],
244
+ },
245
+ ],
246
+ },
247
+ {
248
+ type: 'tab',
249
+ title: $locale('TrainingRecords'),
250
+ children: [
251
+ { type: 'field', name: 'level' },
252
+ {
253
+ type: 'section',
254
+ children: [{ type: 'field', name: 'trainingRecords' }],
255
+ },
256
+ ],
257
+ },
258
+ ],
259
+ },
260
+ ],
261
+ },
262
+ }),
263
+ ],
264
+ });
265
+ ```
266
+
267
+ `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.
268
+
269
+ 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.
270
+
271
+ ## Complete filter-form example
272
+
273
+ The Student list filter combines field-wrapper and structural layout concerns:
274
+
275
+ ```tsx
276
+ ZovaRender.block('basic-page:blockFilter', {
277
+ formFieldLayout: { inline: true },
278
+ blocks: [
279
+ ZovaRender.block('basic-form:blockFormLayout', {
280
+ formLayout: {
281
+ children: [
282
+ {
283
+ type: 'section',
284
+ layout: 'flow',
285
+ children: [
286
+ { type: 'field', name: 'name' },
287
+ { type: 'field', name: 'level' },
288
+ { type: 'field', name: 'createdAt' },
289
+ ],
290
+ },
291
+ ],
292
+ },
293
+ }),
294
+ ZovaRender.block('basic-page:blockFilterActions'),
295
+ ],
296
+ });
297
+ ```
298
+
299
+ Here `formFieldLayout.inline: true` controls how each field wrapper is presented. The flow section keeps those compact wrappers left-packed and wraps them when necessary. `basic-page:blockFilterActions` remains required because the custom blocks replace automatic filter body/footer content.
300
+
301
+ ## Authoring checklist
302
+
303
+ 1. Start with DTO or resource metadata; do not hand-patch generated `.zova-rest` artifacts.
304
+ 2. Use `formLayout` when the requirement is field placement, Grid or flow structure, groups, or tabs.
305
+ 3. Use `layout`, `formFieldLayout`, `options`, or provider behaviors when the requirement is one field's wrapper or renderer.
306
+ 4. Keep entry actions in page-entry toolbar blocks and filter actions in `basic-page:blockFilterActions`.
307
+ 5. Review field names against the scene-specific schema. Unlisted visible fields are appended; unknown and duplicate declarations are silently pruned from the rendered plan.
308
+ 6. Use the smallest layout that communicates the form structure; reserve tabs for genuinely separate field groups.
309
+
310
+ ## Source-reading and verification path
311
+
312
+ For source-level investigation, follow this order:
313
+
314
+ 1. `vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx` or `studentSelectResItem.tsx`
315
+ 2. `zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/resource/formLayout.ts`
316
+ 3. `zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts`
317
+ 4. `zova/src/suite/cabloy-basic/modules/basic-form/src/component/blockFormLayout/controller.tsx`
318
+ 5. `vona/src/suite/a-training/modules/training-student/test/student.test.ts`
319
+
320
+ 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.
321
+
322
+ 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).
@@ -80,6 +80,7 @@ Use this path when the task is about UI composition, component contracts, form o
80
80
 
81
81
  - [Component Guide](/frontend/component-guide)
82
82
  - [Form Guide](/frontend/form-guide)
83
+ - [Form Layout Guide](/frontend/form-layout-guide)
83
84
  - [Image Guide](/frontend/image-guide)
84
85
  - [Zova Form Under the Hood](/frontend/zova-form-under-the-hood)
85
86
  - [Zova Form Source Reading Map](/frontend/zova-form-source-reading-map)
@@ -187,6 +187,8 @@ Instead, it consumes `$$pageEntry` from the host render context and passes the c
187
187
 
188
188
  This is the clearest source-confirmed proof that `blockForm` is a bridge, not the main owner of entry-page orchestration.
189
189
 
190
+ A resource DTO can provide a nested `basic-form:blockFormLayout` block here to arrange the schema fields into sections, groups, or tabs. That block only owns structural placement; read [Form Layout Guide](/frontend/form-layout-guide) for the DTO grammar and keep this page focused on the entry runtime handoff.
191
+
190
192
  ### 7. `blockToolbarRow` renders the action row
191
193
 
192
194
  The action row lives in:
@@ -15,6 +15,7 @@ Read this page together with:
15
15
 
16
16
  > [!TIP]
17
17
  > **Router ecosystem docs path**
18
+ >
18
19
  > 1. **[Page Route Guide](/frontend/page-route-guide)** — learn the public route-record and layout surface
19
20
  > 2. **[Zova Router Under the Hood](/frontend/zova-router-under-the-hood)** — learn how the core router runtime cooperates
20
21
  > 3. **[Router View Hosts Guide](/frontend/router-view-hosts-guide)** — learn how routed pages are actually hosted
@@ -364,4 +365,4 @@ That means the integration story here is accurate for the current Basic Admin an
364
365
 
365
366
  If [Router Tabs Mechanism](/frontend/router-tabs-mechanism) explains how the shared tabs model works, this page explains how the real Basic layouts turn that shared model into visible shell behavior.
366
367
 
367
- That is the missing bridge between framework-level tabs semantics and the actual layout code contributors edit.
368
+ That is the missing bridge between framework-level tabs semantics and the actual layout code contributors edit.
@@ -28,7 +28,62 @@ npm run dev:zova:web
28
28
  npm run build:zova
29
29
  ```
30
30
 
31
- These map to Basic-specific Zova flavors in this repository.
31
+ These map to Basic-specific Zova flavors in this repository. Commerce follows the same aggregate-and-surface pattern:
32
+
33
+ ```bash
34
+ # Customer Web or Operator Admin development server
35
+ npm run dev:zova:commerce:web
36
+ npm run dev:zova:commerce:admin
37
+
38
+ # Both Commerce SSR/REST artifact pairs, or one surface only
39
+ npm run build:zova:commerce
40
+ npm run build:zova:commerce:web
41
+ npm run build:zova:commerce:admin
42
+ ```
43
+
44
+ ## Basic SSR browser acceptance
45
+
46
+ The default Basic Web and Admin sites have browser smoke commands that exercise Vona SSR dispatch at `7102`, not a standalone Zova development-server port.
47
+
48
+ Prepare current artifacts explicitly when the relevant frontend SSR output changed:
49
+
50
+ ```bash
51
+ # Web only
52
+ npm run build:zova:web
53
+
54
+ # Admin only
55
+ npm run build:zova:admin
56
+
57
+ # Both default Basic sites
58
+ npm run build:zova
59
+ npm run deps:vona
60
+ ```
61
+
62
+ Then use the managed clean local acceptance command:
63
+
64
+ ```bash
65
+ npm run test:e2e:basic:clean
66
+ ```
67
+
68
+ It resets Vona-managed test data and the local Redis namespace, starts one development Vona worker, and runs the complete suite by default. The E2E commands consume already-built artifacts; they do not rebuild them.
69
+
70
+ The suite command family is consistent across Basic and Commerce:
71
+
72
+ ```text
73
+ test:e2e:<suite> complete suite
74
+ test:e2e:<suite>:web all @web surface scenarios
75
+ test:e2e:<suite>:admin all @admin surface scenarios
76
+ test:e2e:<suite>:clean managed clean local suite run
77
+ ```
78
+
79
+ Use Playwright tags after npm's argument delimiter for feature/category selection instead of adding one root script per scenario:
80
+
81
+ ```bash
82
+ npm run test:e2e:basic:clean -- --grep @flow
83
+ npm run test:e2e:basic -- --grep ATP-BASIC-FLOW-01
84
+ ```
85
+
86
+ For the complete tag vocabulary, managed-runner argument boundaries, and externally managed-target examples, see [Repo Scripts](/reference/repo-scripts#ssr-browser-checks). For a separately managed Basic target, set `BASIC_E2E_BASE_URL`; the equivalent Commerce commands use `COMMERCE_E2E_BASE_URL`. The caller owns external-target data, cache, and artifact freshness.
32
87
 
33
88
  ## Zova script model
34
89
 
@@ -305,4 +305,4 @@ Before finalizing an SSR build/deploy change, ask:
305
305
  4. if the change crosses the frontend/backend SSR boundary, did I verify the aligned fullstack build path?
306
306
  5. did I verify built behavior rather than relying only on dev behavior?
307
307
 
308
- That keeps SSR deployment guidance aligned with the real Cabloy monorepo workflow rather than with generic frontend-only assumptions.
308
+ That keeps SSR deployment guidance aligned with the real Cabloy monorepo workflow rather than with generic frontend-only assumptions.
@@ -28,6 +28,12 @@ For admin systems, SSR still needs to cooperate with client-facing behavior such
28
28
 
29
29
  A central SSR capability is preparing initial data on the server, synchronizing it to the client, and completing hydration naturally.
30
30
 
31
+ ### Initial hydration completion
32
+
33
+ For browser work that must wait for the initial SSR handoff, register `this.$ssr.onHydrated(...)`. The callback runs once only after the SSR root and every currently tracked Zova nested hydration boundary complete, including deferred client updates. Root mount alone is not sufficient when an async component or controller is still hydrating.
34
+
35
+ This lifecycle applies only to the initial client hydration of SSR HTML. It is not an SPA-startup or later client-navigation readiness signal. Do not render a client-ready marker into server HTML; add it from an `onHydrated(...)` callback when browser-visible evidence is required.
36
+
31
37
  ### SEO meta
32
38
 
33
39
  SSR also supports flexible SEO metadata handling.
@@ -181,4 +181,4 @@ Before accepting an SSR-related change, ask:
181
181
  3. did the review verify edition/theme assumptions when the change was SSR-UI-sensitive?
182
182
  4. did the review use the smallest correct checklist rather than relying on intuition alone?
183
183
 
184
- That keeps SSR reviews consistent across human contributors and AI-assisted workflows.
184
+ That keeps SSR reviews consistent across human contributors and AI-assisted workflows.
@@ -51,15 +51,15 @@ If you identify the failing layer first, most SSR debugging becomes much faster.
51
51
 
52
52
  Use this quick map before diving deeper.
53
53
 
54
- | Symptom | Start here |
55
- | --- | --- |
56
- | request does not hit the expected SSR page | Vona SSR orchestration |
57
- | dev works but prod fails | frontend build output + SSR env/runtime |
58
- | HTML appears but hydration is wrong | client hydration |
54
+ | Symptom | Start here |
55
+ | --------------------------------------------------------- | --------------------------------------- |
56
+ | request does not hit the expected SSR page | Vona SSR orchestration |
57
+ | dev works but prod fails | frontend build output + SSR env/runtime |
58
+ | HTML appears but hydration is wrong | client hydration |
59
59
  | server-rendered data is missing or refetched unexpectedly | Zova SSR server render + init data flow |
60
- | SEO/meta output is missing | Zova SSR server render + meta flow |
61
- | first paint theme is wrong | SSR env + theme rules |
62
- | static assets 404 after SSR deploy | frontend build output |
60
+ | SEO/meta output is missing | Zova SSR server render + meta flow |
61
+ | first paint theme is wrong | SSR env + theme rules |
62
+ | static assets 404 after SSR deploy | frontend build output |
63
63
 
64
64
  ## Symptom 1: the request does not hit the expected SSR page
65
65
 
@@ -298,4 +298,4 @@ Before finalizing an SSR fix, ask:
298
298
  3. am I reusing the intended framework abstraction instead of adding a parallel workaround?
299
299
  4. does the fix accidentally assume an edition-specific UI or theme rule is universal?
300
300
 
301
- That keeps SSR debugging aligned with the Cabloy fullstack model and reduces one-off fixes that solve only the symptom.
301
+ That keeps SSR debugging aligned with the Cabloy fullstack model and reduces one-off fixes that solve only the symptom.