cabloy 5.1.59 → 5.1.61

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 (149) hide show
  1. package/.claude/hooks/contract-loop-gate.ts +296 -0
  2. package/.claude/settings.json +16 -0
  3. package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +1 -0
  4. package/.claude/skills/cabloy-contract-loop/SKILL.md +103 -14
  5. package/.claude/skills/cabloy-contract-loop/references/contract-loop-map.md +126 -12
  6. package/.claude/skills/cabloy-contract-loop/references/resource-custom-state-pattern.md +148 -0
  7. package/.claude/skills/cabloy-contract-loop/references/verification-checklist.md +49 -13
  8. package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +11 -0
  9. package/.claude/skills/cabloy-frontend-scaffold/references/follow-up-checklist.md +2 -0
  10. package/.claude/skills/cabloy-module-removal/SKILL.md +144 -0
  11. package/.claude/skills/cabloy-resource-field-update/SKILL.md +274 -0
  12. package/.claude/skills/cabloy-resource-field-update/evals/evals.json +53 -0
  13. package/.claude/skills/cabloy-resource-field-update/references/custom-renderer-demo-checklist.md +102 -0
  14. package/.claude/skills/cabloy-resource-field-update/references/field-update-decision-tree.md +120 -0
  15. package/.claude/skills/cabloy-resource-field-update/references/follow-up-checklist.md +80 -0
  16. package/.claude/skills/cabloy-resource-field-update/references/verification-checklist.md +97 -0
  17. package/.claude/skills/cabloy-zova-source-reading/SKILL.md +221 -0
  18. package/.claude/skills/cabloy-zova-source-reading/references/analysis-modes.md +91 -0
  19. package/.claude/skills/cabloy-zova-source-reading/references/core-reading-paths.md +117 -0
  20. package/.github/workflows/docs-pages.yml +2 -0
  21. package/.github/workflows/vona-cov-pg.yml +2 -0
  22. package/.github/workflows/vona-test-crud.yml +4 -2
  23. package/.github/workflows/vona-test-mysql.yml +2 -0
  24. package/.github/workflows/vona-test-pg.yml +2 -0
  25. package/.github/workflows/vona-test-sqlite3.yml +2 -0
  26. package/.github/workflows/vona-tsc.yml +2 -0
  27. package/.github/workflows/zova-ui.yml +2 -0
  28. package/.gitignore +0 -4
  29. package/CHANGELOG.md +52 -0
  30. package/CLAUDE.md +12 -0
  31. package/README.md +15 -0
  32. package/cabloy-docs/.vitepress/config.mjs +89 -0
  33. package/cabloy-docs/ai/class-placement-rule.md +2 -0
  34. package/cabloy-docs/ai/cli-to-skill-map.md +14 -0
  35. package/cabloy-docs/ai/docs-skills-rules-mapping.md +14 -0
  36. package/cabloy-docs/ai/future-skill-roadmap.md +27 -9
  37. package/cabloy-docs/ai/introduction.md +1 -0
  38. package/cabloy-docs/ai/playbook-backend-module.md +6 -0
  39. package/cabloy-docs/ai/playbook-module-removal.md +164 -0
  40. package/cabloy-docs/ai/skills.md +11 -0
  41. package/cabloy-docs/backend/bean-scene-authoring.md +350 -0
  42. package/cabloy-docs/backend/cli.md +26 -1
  43. package/cabloy-docs/backend/dto-guide.md +6 -0
  44. package/cabloy-docs/backend/entity-guide.md +18 -0
  45. package/cabloy-docs/backend/foundation.md +28 -3
  46. package/cabloy-docs/backend/introduction.md +10 -0
  47. package/cabloy-docs/backend/serialization-guide.md +10 -0
  48. package/cabloy-docs/backend/service-guide.md +2 -0
  49. package/cabloy-docs/backend/status-guide.md +271 -0
  50. package/cabloy-docs/backend/websocket-call-flow.md +435 -0
  51. package/cabloy-docs/backend/websocket-guide.md +455 -0
  52. package/cabloy-docs/backend/websocket-protocol-guide.md +381 -0
  53. package/cabloy-docs/backend/websocket-usage-guide.md +356 -0
  54. package/cabloy-docs/frontend/api-guide.md +2 -0
  55. package/cabloy-docs/frontend/bean-scene-authoring.md +374 -0
  56. package/cabloy-docs/frontend/behavior-guide.md +449 -0
  57. package/cabloy-docs/frontend/cli.md +24 -0
  58. package/cabloy-docs/frontend/command-scene-authoring.md +495 -0
  59. package/cabloy-docs/frontend/design-principles.md +6 -0
  60. package/cabloy-docs/frontend/fetch-interceptor-guide.md +440 -0
  61. package/cabloy-docs/frontend/form-guide.md +795 -0
  62. package/cabloy-docs/frontend/foundation.md +29 -0
  63. package/cabloy-docs/frontend/introduction.md +17 -1
  64. package/cabloy-docs/frontend/ioc-and-beans.md +16 -9
  65. package/cabloy-docs/frontend/mock-guide.md +1 -0
  66. package/cabloy-docs/frontend/model-architecture.md +252 -39
  67. package/cabloy-docs/frontend/model-resource-best-practices.md +379 -0
  68. package/cabloy-docs/frontend/model-resource-cookbook.md +505 -0
  69. package/cabloy-docs/frontend/model-resource-owner-pattern.md +382 -0
  70. package/cabloy-docs/frontend/model-resource-usage-guide.md +318 -0
  71. package/cabloy-docs/frontend/model-state-guide.md +366 -13
  72. package/cabloy-docs/frontend/openapi-sdk-guide.md +5 -2
  73. package/cabloy-docs/frontend/page-guide.md +6 -0
  74. package/cabloy-docs/frontend/quickstart.md +4 -0
  75. package/cabloy-docs/frontend/reading-zova-for-vue-developers.md +266 -0
  76. package/cabloy-docs/frontend/router-tabs-admin-web-comparison.md +206 -0
  77. package/cabloy-docs/frontend/router-tabs-introduction.md +106 -0
  78. package/cabloy-docs/frontend/router-tabs-mechanism.md +469 -0
  79. package/cabloy-docs/frontend/router-tabs-overview.md +227 -0
  80. package/cabloy-docs/frontend/router-tabs-route-meta-cookbook.md +343 -0
  81. package/cabloy-docs/frontend/server-data.md +2 -0
  82. package/cabloy-docs/frontend/ssr-architecture-overview.md +211 -0
  83. package/cabloy-docs/frontend/ssr-build-deploy-guide.md +308 -0
  84. package/cabloy-docs/frontend/ssr-review-checklist.md +184 -0
  85. package/cabloy-docs/frontend/ssr-troubleshooting-guide.md +301 -0
  86. package/cabloy-docs/frontend/zova-form-source-reading-map.md +295 -0
  87. package/cabloy-docs/frontend/zova-form-under-the-hood.md +556 -0
  88. package/cabloy-docs/frontend/zova-reactivity-under-the-hood.md +320 -0
  89. package/cabloy-docs/frontend/zova-source-reading-map.md +327 -0
  90. package/cabloy-docs/frontend/zova-vs-vue3-comparison.md +308 -0
  91. package/cabloy-docs/fullstack/contract-loop-playbook.md +350 -0
  92. package/cabloy-docs/fullstack/framework-performance.md +3 -3
  93. package/cabloy-docs/fullstack/frontend-metadata-to-backend.md +44 -1
  94. package/cabloy-docs/fullstack/introduction.md +40 -0
  95. package/cabloy-docs/fullstack/openapi-to-sdk.md +19 -9
  96. package/cabloy-docs/fullstack/quickstart.md +7 -1
  97. package/cabloy-docs/fullstack/tutorial-1-first-module.md +111 -0
  98. package/cabloy-docs/fullstack/tutorial-2-first-crud.md +122 -0
  99. package/cabloy-docs/fullstack/tutorial-3-frontend-metadata-sharing.md +131 -0
  100. package/cabloy-docs/fullstack/tutorial-4-custom-level-renderers.md +144 -0
  101. package/cabloy-docs/fullstack/tutorial-5-backend-contract-sharing.md +146 -0
  102. package/cabloy-docs/fullstack/tutorial-6-one-contract-four-uses.md +170 -0
  103. package/cabloy-docs/fullstack/tutorials-overview.md +192 -0
  104. package/cabloy-docs/index.md +4 -3
  105. package/cabloy-docs/reference/bean-scene-boilerplates.md +75 -0
  106. package/cabloy-docs/reference/cli-reference.md +2 -0
  107. package/package.json +7 -2
  108. package/scripts/initTestData.ts +25 -0
  109. package/scripts/upgrade.ts +17 -2
  110. package/vona/packages-cli/cabloy-cli/package.json +2 -2
  111. package/vona/packages-cli/cli/package.json +1 -1
  112. package/vona/packages-cli/cli-set-api/package.json +1 -1
  113. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.create.module.ts +4 -0
  114. package/vona/packages-vona/vona/package.json +1 -1
  115. package/vona/pnpm-lock.yaml +226 -1091
  116. package/vona/pnpm-workspace.yaml +0 -1
  117. package/vona/src/suite-vendor/a-vona/modules/a-core/assets/static/img/vona.svg +1 -1
  118. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  119. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  120. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/bean.permission.ts +1 -1
  121. package/vona/src/suite-vendor/a-vona/modules/a-upload/package.json +2 -2
  122. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  123. package/zova/package.original.json +1 -1
  124. package/zova/packages-cli/cli/package.json +3 -3
  125. package/zova/packages-cli/cli-set-front/cli/templates/init/icon/boilerplate/icons/default/zova.svg +1 -1
  126. package/zova/packages-cli/cli-set-front/cli/templates/openapi/config/boilerplate/module/openapi.config.ts +6 -1
  127. package/zova/packages-cli/cli-set-front/package.json +3 -3
  128. package/zova/packages-cli/cli-set-front/src/lib/bean/cli.create.module.ts +4 -0
  129. package/zova/packages-cli/cli-set-front/src/lib/bean/cli.openapi.generate.ts +34 -4
  130. package/zova/packages-cli/cli-set-front/src/lib/command/create.bean.ts +5 -1
  131. package/zova/packages-utils/zova-vite/package.json +2 -2
  132. package/zova/packages-zova/zova/package.json +2 -2
  133. package/zova/pnpm-lock.yaml +282 -1311
  134. package/zova/pnpm-workspace.yaml +0 -1
  135. package/zova/src/suite/a-home/modules/home-icon/icons/social/cabloy.svg +1 -1
  136. package/zova/src/suite/a-home/modules/home-icon/icons/social/vona.svg +1 -1
  137. package/zova/src/suite/a-home/modules/home-icon/icons/social/zova.svg +1 -1
  138. package/zova/src/suite/a-home/modules/home-icon/src/.metadata/icons/groups/social.svg +3 -3
  139. package/zova/src/suite/cabloy-basic/modules/basic-select/src/component/formFieldSelect/controller.tsx +9 -0
  140. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  141. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +66 -16
  142. package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
  143. package/zova/src/suite-vendor/a-zova/modules/a-routertabs/package.json +1 -1
  144. package/zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts +60 -18
  145. package/zova/src/suite-vendor/a-zova/modules/a-table/cli/tableActionRow/boilerplate/{{sceneName}}.{{beanName}}.tsx_ +6 -1
  146. package/zova/src/suite-vendor/a-zova/modules/a-table/cli/tableCell/boilerplate/{{sceneName}}.{{beanName}}.tsx_ +6 -1
  147. package/zova/src/suite-vendor/a-zova/modules/a-table/package.json +1 -1
  148. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  149. package/zova/src/suite-vendor/a-zova/package.json +4 -4
@@ -0,0 +1,343 @@
1
+ # Router Tabs Route Meta Cookbook
2
+
3
+ This guide provides practical route-meta recipes for the router-tabs mechanism in Zova within the Cabloy monorepo.
4
+
5
+ Read this together with:
6
+
7
+ - [Router Tabs Introduction](/frontend/router-tabs-introduction)
8
+ - [Page Route Guide](/frontend/page-route-guide)
9
+ - [Router Tabs Overview](/frontend/router-tabs-overview)
10
+ - [Router Tabs Mechanism](/frontend/router-tabs-mechanism)
11
+
12
+ ## Why this cookbook exists
13
+
14
+ The router-tabs mechanism is flexible, but the most important authoring choices are usually concentrated in route metadata.
15
+
16
+ In practice, contributors often need a quick answer to questions such as:
17
+
18
+ - when should several pages share one level-1 workspace?
19
+ - when should several route visits reuse one page instance?
20
+ - when should different records remain open in parallel?
21
+ - when should a routed page be excluded from keep-alive?
22
+
23
+ This cookbook answers those questions with focused examples.
24
+
25
+ ## The relevant route-meta surface
26
+
27
+ Representative route-meta fields for router tabs include:
28
+
29
+ - `tabKey`
30
+ - `componentKey`
31
+ - `componentKeyMode`
32
+ - `keepAlive`
33
+
34
+ Representative source definition:
35
+
36
+ - `zova/src/suite-vendor/a-zova/modules/a-router/src/types/router.ts`
37
+
38
+ ## Mental model before choosing a recipe
39
+
40
+ Use this decision split first:
41
+
42
+ - `tabKey` answers: which level-1 workspace should this route belong to?
43
+ - `componentKey` answers: should this route visit reuse an existing page instance or remain separately open?
44
+
45
+ If you keep that split clear, most router-tabs authoring decisions become straightforward.
46
+
47
+ ## Recipe 1: use the default behavior for a simple page
48
+
49
+ Use the default behavior when a page does not need special grouping or custom instance control.
50
+
51
+ ```typescript
52
+ export const routes: IModuleRoute[] = [
53
+ {
54
+ path: 'dashboard',
55
+ component: ZPageDashboard,
56
+ },
57
+ ];
58
+ ```
59
+
60
+ What this means in practice:
61
+
62
+ - the route can still participate in router tabs
63
+ - the effective `tabKey` falls back to the effective `componentKey`
64
+ - the page behaves like its own workspace by default
65
+
66
+ Use this when the page is simple and does not need to share a workspace with related pages.
67
+
68
+ ## Recipe 2: group several pages into one stable workspace with `tabKey`
69
+
70
+ Use a shared `tabKey` when several related routes should stay under one level-1 workspace.
71
+
72
+ ```typescript
73
+ export const routes: IModuleRoute[] = [
74
+ {
75
+ path: 'user/list',
76
+ component: ZPageUserList,
77
+ meta: {
78
+ tabKey: '/user/list',
79
+ },
80
+ },
81
+ {
82
+ path: 'user/create',
83
+ component: ZPageUserCreate,
84
+ meta: {
85
+ tabKey: '/user/list',
86
+ },
87
+ },
88
+ {
89
+ path: 'user/edit/:id',
90
+ component: ZPageUserEdit,
91
+ meta: {
92
+ tabKey: '/user/list',
93
+ },
94
+ },
95
+ ];
96
+ ```
97
+
98
+ What this means in practice:
99
+
100
+ - User List, User Create, and User Edit all belong to one level-1 workspace
101
+ - the top-level workspace remains stable
102
+ - the inner work items can still vary
103
+
104
+ Use this when the business workspace identity matters more than the specific route identity.
105
+
106
+ ## Recipe 3: reuse one page instance across param changes with `componentKeyMode: 'nameOnly'`
107
+
108
+ Use `componentKeyMode: 'nameOnly'` when different param values should still reuse one logical page instance.
109
+
110
+ ```typescript
111
+ export const routes: IModuleRoute[] = [
112
+ {
113
+ name: 'reportDetail',
114
+ path: 'report/:id?',
115
+ component: ZPageReportDetail,
116
+ meta: {
117
+ componentKeyMode: 'nameOnly',
118
+ },
119
+ },
120
+ ];
121
+ ```
122
+
123
+ What this means in practice:
124
+
125
+ - the route name becomes the effective page-instance identity
126
+ - visiting different param values can still reuse one page instance
127
+ - this is useful when the page behaves like one reusable tool surface
128
+
129
+ Representative source example:
130
+
131
+ - `zova/src/suite/a-demo/modules/demo-basic/src/routes.ts` lines 20-28
132
+
133
+ ## Recipe 4: use an explicit `componentKey` when you need full control
134
+
135
+ Use an explicit `componentKey` when the default path-based behavior is not precise enough.
136
+
137
+ ```typescript
138
+ export const routes: IModuleRoute[] = [
139
+ {
140
+ name: 'orderDetail',
141
+ path: 'order/:id',
142
+ component: ZPageOrderDetail,
143
+ meta: {
144
+ componentKey(route) {
145
+ return `order:${route.params.id}`;
146
+ },
147
+ },
148
+ },
149
+ ];
150
+ ```
151
+
152
+ What this means in practice:
153
+
154
+ - every order id gets its own page-instance identity
155
+ - several orders can remain open in parallel
156
+ - the page-instance rule is explicit instead of inferred indirectly
157
+
158
+ Use this when you want independently open task items for different business objects.
159
+
160
+ ## Recipe 5: combine stable `tabKey` with distinct `componentKey`
161
+
162
+ This is the most common workbench recipe.
163
+
164
+ Use it when several routes should stay in one workspace, but different records should still remain separately open.
165
+
166
+ ```typescript
167
+ export const routes: IModuleRoute[] = [
168
+ {
169
+ name: 'productEdit',
170
+ path: 'product/edit/:id',
171
+ component: ZPageProductEdit,
172
+ meta: {
173
+ tabKey: '/product/list',
174
+ componentKey(route) {
175
+ return `product-edit:${route.params.id}`;
176
+ },
177
+ },
178
+ },
179
+ ];
180
+ ```
181
+
182
+ What this means in practice:
183
+
184
+ - all product-edit work stays under the Product List workspace
185
+ - Product A and Product B can still remain open as separate level-2 work items
186
+ - this preserves both business grouping and parallel record editing
187
+
188
+ If you need only one recipe to remember, this is often the one.
189
+
190
+ ## Recipe 6: compute `tabKey` dynamically when the workspace identity is derived
191
+
192
+ Use a function `tabKey` when the workspace grouping depends on route context rather than a fixed string.
193
+
194
+ ```typescript
195
+ export const routes: IModuleRoute[] = [
196
+ {
197
+ name: 'contentScene',
198
+ path: 'content/:scene/:id?',
199
+ component: ZPageContentScene,
200
+ meta: {
201
+ tabKey(route) {
202
+ return `/content/${route.params.scene}`;
203
+ },
204
+ },
205
+ },
206
+ ];
207
+ ```
208
+
209
+ What this means in practice:
210
+
211
+ - each content scene becomes its own workspace grouping
212
+ - routes in the same scene stay together
213
+ - different scenes remain separated
214
+
215
+ Use this when the stable business workspace is derived from a route dimension such as scene, category, or module context.
216
+
217
+ ## Recipe 7: exclude a page from keep-alive with `keepAlive: false`
218
+
219
+ Use `keepAlive: false` when the page should not stay alive in the routed workbench cache.
220
+
221
+ ```typescript
222
+ export const routes: IModuleRoute[] = [
223
+ {
224
+ path: 'search/live',
225
+ component: ZPageSearchLive,
226
+ meta: {
227
+ keepAlive: false,
228
+ },
229
+ },
230
+ ];
231
+ ```
232
+
233
+ What this means in practice:
234
+
235
+ - the route can still participate in router tabs
236
+ - but its instance is excluded from the keep-alive include list
237
+ - switching away and back may rebuild the page state
238
+
239
+ Use this only when the page should not preserve the normal keep-alive workbench behavior.
240
+
241
+ ## Recipe 8: pin the workspace identity to the business entry, not to task routes
242
+
243
+ When choosing `tabKey`, prefer the stable business entry rather than temporary task pages.
244
+
245
+ Better:
246
+
247
+ ```typescript
248
+ meta: {
249
+ tabKey: '/invoice/list',
250
+ }
251
+ ```
252
+
253
+ Usually worse:
254
+
255
+ ```typescript
256
+ meta: {
257
+ tabKey: '/invoice/edit/123',
258
+ }
259
+ ```
260
+
261
+ Why:
262
+
263
+ - the first form preserves a stable Invoice workspace
264
+ - the second form turns one temporary task into the workspace identity itself
265
+
266
+ That usually weakens the business meaning of the level-1 surface.
267
+
268
+ ## Recipe 9: decide between `componentKeyMode` and explicit `componentKey`
269
+
270
+ Use this rule of thumb:
271
+
272
+ ### Prefer `componentKeyMode: 'nameOnly'` when
273
+
274
+ - one named page should behave as one reusable inner tool
275
+ - params do not need separate open instances
276
+ - the route name is already a good page-instance identity
277
+
278
+ ### Prefer explicit `componentKey` when
279
+
280
+ - different records should remain separately open
281
+ - the reuse rule depends on route params or query details
282
+ - you want the instance boundary to be obvious in the route definition
283
+
284
+ In the current implementation, `nameOnly` is the special opt-in mode. Otherwise the router-tabs model falls back to path-based distinction unless an explicit `componentKey` is provided.
285
+
286
+ ## Recipe 10: avoid using `tabKey` and `componentKey` for the same purpose
287
+
288
+ Avoid these two mistakes:
289
+
290
+ ### Mistake A: using `tabKey` to distinguish separate records
291
+
292
+ ```typescript
293
+ meta: {
294
+ tabKey(route) {
295
+ return `/customer/${route.params.id}`;
296
+ },
297
+ }
298
+ ```
299
+
300
+ This often turns every record into its own top-level workspace and makes the level-1 surface noisy.
301
+
302
+ ### Mistake B: using `componentKey` to simulate business grouping
303
+
304
+ ```typescript
305
+ meta: {
306
+ componentKey: '/customer/list',
307
+ }
308
+ ```
309
+
310
+ This can collapse different work items into one reused page instance even when users expect parallel open tasks.
311
+
312
+ The safer rule is:
313
+
314
+ - use `tabKey` for workspace grouping
315
+ - use `componentKey` for page-instance reuse or separation
316
+
317
+ ## Recommended authoring checklist
318
+
319
+ Before finalizing route meta for a page, ask:
320
+
321
+ 1. what is the stable business workspace identity?
322
+ 2. should this page join an existing workspace or create its own?
323
+ 3. should several visits reuse one page instance or open separately?
324
+ 4. should this page preserve normal keep-alive behavior?
325
+ 5. would the chosen keys still make sense after future route refactors?
326
+
327
+ If the answers are stable, the route-meta design is usually in good shape.
328
+
329
+ ## Summary
330
+
331
+ The most useful router-tabs recipes are built from one simple split:
332
+
333
+ - `tabKey` controls workspace grouping
334
+ - `componentKey` controls page-instance identity
335
+
336
+ Once that split is clear, route meta becomes a practical and predictable tool for shaping the workbench experience.
337
+
338
+ ## See also
339
+
340
+ - [Router Tabs Introduction](/frontend/router-tabs-introduction)
341
+ - [Router Tabs Overview](/frontend/router-tabs-overview)
342
+ - [Router Tabs Mechanism](/frontend/router-tabs-mechanism)
343
+ - [Router Tabs Admin and Web Comparison](/frontend/router-tabs-admin-web-comparison)
@@ -27,6 +27,8 @@ These layers define the server-data abstraction ladder:
27
27
 
28
28
  Use `$fetch` when you need a relatively direct HTTP-oriented access path.
29
29
 
30
+ When the transport concern itself needs framework-level handling such as auth headers, response-envelope normalization, SSR short-circuiting, or mock fallback, read [Fetch Interceptor Guide](/frontend/fetch-interceptor-guide) together with this page.
31
+
30
32
  ### `$api`
31
33
 
32
34
  Use `$api` when you want business-oriented service access rather than scattering request details across pages and components.
@@ -0,0 +1,211 @@
1
+ # SSR Architecture Overview
2
+
3
+ This guide explains the public SSR architecture model in Zova within the Cabloy monorepo.
4
+
5
+ ## Why this page exists
6
+
7
+ The other SSR pages explain focused topics such as init data, client-only boundaries, SEO meta, and env behavior.
8
+
9
+ This page explains the larger mental model:
10
+
11
+ - where SSR starts
12
+ - how Vona and Zova cooperate
13
+ - what the generated frontend bundle contributes
14
+ - how server render hands off to client hydration
15
+
16
+ That model helps contributors and AI workflows place SSR changes in the correct layer instead of treating SSR as a single opaque step.
17
+
18
+ ## The shortest accurate SSR model
19
+
20
+ Cabloy SSR is a coordinated fullstack flow:
21
+
22
+ 1. **Vona** receives the request and decides whether the request should be handled as SSR
23
+ 2. **Vona SSR integration** loads the built frontend SSR bundle for the matching site
24
+ 3. **Zova SSR runtime** resolves the page route and renders HTML on the server
25
+ 4. **Zova SSR state/meta logic** injects the data needed for hydration
26
+ 5. **the browser** hydrates the page and continues as a normal client application
27
+
28
+ The important point is that SSR is not frontend-only and not backend-only.
29
+
30
+ It is one connected request flow across both sides of the framework.
31
+
32
+ ## The four practical layers
33
+
34
+ In these docs, **frontend build output** includes the SSR bundle entry plus the client assets used after render.
35
+
36
+ ### 1. Vona SSR orchestration
37
+
38
+ This layer owns the outer request lifecycle.
39
+
40
+ Its responsibilities include:
41
+
42
+ - receiving the HTTP request
43
+ - matching the request to the correct SSR site
44
+ - deciding between dev proxy, built static asset, or SSR render
45
+ - loading the built SSR entry for the site
46
+ - writing the final HTML response
47
+
48
+ A practical way to think about this layer is:
49
+
50
+ - **Vona decides whether SSR should happen and which site should handle it**
51
+
52
+ ### 2. Generated frontend SSR bundle
53
+
54
+ Each SSR site produces a built bundle that the backend can load.
55
+
56
+ This bundle contributes:
57
+
58
+ - the SSR entry used by the backend
59
+ - the client assets used after render
60
+ - the manifest/preload information needed by the SSR runtime
61
+
62
+ A practical way to think about this layer is:
63
+
64
+ - **the frontend build produces the server-renderable application package**
65
+
66
+ ### 3. Zova server-side SSR runtime
67
+
68
+ This layer owns the actual server render of the frontend application.
69
+
70
+ Its responsibilities include:
71
+
72
+ - resolving the target route inside the frontend application
73
+ - creating the SSR context
74
+ - rendering the application to HTML
75
+ - collecting preload information for used modules/assets
76
+ - returning the final HTML shell
77
+
78
+ A practical way to think about this layer is:
79
+
80
+ - **Zova turns the built frontend application into server-rendered HTML**
81
+
82
+ ### 4. Zova hydration handoff
83
+
84
+ After server render, the framework still needs to transfer state into the browser correctly.
85
+
86
+ This layer owns:
87
+
88
+ - injecting initial SSR state into the HTML
89
+ - injecting SSR-aware meta output
90
+ - preserving data needed for hydration
91
+ - resuming on the client without redoing the whole first-screen work unnecessarily
92
+
93
+ A practical way to think about this layer is:
94
+
95
+ - **SSR finishes only when hydration has a clean handoff path**
96
+
97
+ ## End-to-end request flow
98
+
99
+ A useful high-level sequence is:
100
+
101
+ ```text
102
+ browser request
103
+ -> Vona receives the URL
104
+ -> Vona matches the SSR site
105
+ -> Vona chooses dev proxy, static asset, or SSR render
106
+ -> Vona loads the built frontend SSR entry
107
+ -> Zova resolves the frontend route
108
+ -> Zova renders HTML on the server
109
+ -> Zova injects state/meta/preload output
110
+ -> Vona returns the HTML response
111
+ -> browser hydrates the page
112
+ ```
113
+
114
+ This sequence is the durable contract even if specific internal implementation details evolve.
115
+
116
+ ## What each side owns
117
+
118
+ ### Vona mainly owns
119
+
120
+ - request entry
121
+ - SSR site matching
122
+ - HTTP response lifecycle
123
+ - SSR site-level environment assembly
124
+ - backend-side integration with the built frontend bundle
125
+
126
+ ### Zova mainly owns
127
+
128
+ - frontend route resolution during SSR
129
+ - server rendering of the frontend application
130
+ - SSR state and meta injection
131
+ - hydration-aware frontend runtime behavior
132
+
133
+ ### The built bundle mainly owns
134
+
135
+ - the page/component tree itself
136
+ - the emitted SSR entry
137
+ - the client assets used after render
138
+
139
+ ## Why this split matters
140
+
141
+ This split helps avoid common mistakes.
142
+
143
+ ### Mistake 1: treating SSR as frontend-only
144
+
145
+ That misses the fact that Vona decides when SSR runs and how the frontend bundle is entered.
146
+
147
+ ### Mistake 2: treating SSR as backend-only
148
+
149
+ That misses the fact that route resolution, HTML render, state injection, and hydration behavior are frontend runtime responsibilities.
150
+
151
+ ### Mistake 3: debugging the wrong layer
152
+
153
+ For example:
154
+
155
+ - if the request never reaches the intended SSR site, start from the Vona side
156
+ - if the page route or rendered HTML is wrong after the frontend bundle is entered, continue on the Zova side
157
+ - if hydration or SSR-transferred state is wrong, focus on the SSR state/meta layer rather than only the outer request layer
158
+
159
+ ## How to reason about SSR changes
160
+
161
+ When editing SSR-sensitive code, ask these questions first:
162
+
163
+ 1. does this change affect **request routing** or **frontend rendering**?
164
+ 2. does it affect the **server render result** or only the **client hydration result**?
165
+ 3. does it belong to **site integration**, **frontend runtime**, or **page-level application code**?
166
+ 4. does the active edition change only the UI layer, or does it change the SSR workflow itself?
167
+
168
+ That framing usually tells you where the change belongs before you start coding.
169
+
170
+ ## Edition impact
171
+
172
+ For Cabloy Basic and Cabloy Start, the SSR architecture contract is shared at the framework level.
173
+
174
+ That means the same high-level model still applies:
175
+
176
+ - backend request entry
177
+ - frontend SSR bundle
178
+ - server render
179
+ - hydration handoff
180
+
181
+ What can differ by edition is usually:
182
+
183
+ - UI library assumptions
184
+ - site baselines
185
+ - frontend flavor names
186
+ - project assets and generated output paths
187
+
188
+ So the architecture model is shared, while some concrete frontend examples remain edition-sensitive.
189
+
190
+ ## Recommended reading order
191
+
192
+ Use this order when you need the shortest path from mental model to implementation detail:
193
+
194
+ 1. this page for the architecture overview
195
+ 2. [SSR Overview](/frontend/ssr-overview)
196
+ 3. [SSR Init Data](/frontend/ssr-init-data)
197
+ 4. [SSR ClientOnly](/frontend/ssr-client-only)
198
+ 5. [SSR SEO Meta](/frontend/ssr-seo-meta)
199
+ 6. [SSR Env](/frontend/ssr-env)
200
+ 7. [Fullstack Vona + Zova Integration](/fullstack/vona-zova-integration)
201
+
202
+ ## Implementation checks for architecture-sensitive SSR changes
203
+
204
+ Before finalizing an SSR-related change, ask:
205
+
206
+ 1. does the request still enter the correct SSR site?
207
+ 2. does the server render still produce the intended HTML?
208
+ 3. does the client reuse the server-provided state during hydration?
209
+ 4. does the change assume a UI or theme behavior that is actually edition-specific?
210
+
211
+ That keeps SSR work aligned with the Cabloy fullstack model rather than drifting into generic frontend-only or backend-only assumptions.