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.
- package/.claude/hooks/contract-loop-gate.ts +296 -0
- package/.claude/settings.json +16 -0
- package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +1 -0
- package/.claude/skills/cabloy-contract-loop/SKILL.md +103 -14
- package/.claude/skills/cabloy-contract-loop/references/contract-loop-map.md +126 -12
- package/.claude/skills/cabloy-contract-loop/references/resource-custom-state-pattern.md +148 -0
- package/.claude/skills/cabloy-contract-loop/references/verification-checklist.md +49 -13
- package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +11 -0
- package/.claude/skills/cabloy-frontend-scaffold/references/follow-up-checklist.md +2 -0
- package/.claude/skills/cabloy-module-removal/SKILL.md +144 -0
- package/.claude/skills/cabloy-resource-field-update/SKILL.md +274 -0
- package/.claude/skills/cabloy-resource-field-update/evals/evals.json +53 -0
- package/.claude/skills/cabloy-resource-field-update/references/custom-renderer-demo-checklist.md +102 -0
- package/.claude/skills/cabloy-resource-field-update/references/field-update-decision-tree.md +120 -0
- package/.claude/skills/cabloy-resource-field-update/references/follow-up-checklist.md +80 -0
- package/.claude/skills/cabloy-resource-field-update/references/verification-checklist.md +97 -0
- package/.claude/skills/cabloy-zova-source-reading/SKILL.md +221 -0
- package/.claude/skills/cabloy-zova-source-reading/references/analysis-modes.md +91 -0
- package/.claude/skills/cabloy-zova-source-reading/references/core-reading-paths.md +117 -0
- package/.github/workflows/docs-pages.yml +2 -0
- package/.github/workflows/vona-cov-pg.yml +2 -0
- package/.github/workflows/vona-test-crud.yml +4 -2
- package/.github/workflows/vona-test-mysql.yml +2 -0
- package/.github/workflows/vona-test-pg.yml +2 -0
- package/.github/workflows/vona-test-sqlite3.yml +2 -0
- package/.github/workflows/vona-tsc.yml +2 -0
- package/.github/workflows/zova-ui.yml +2 -0
- package/.gitignore +0 -4
- package/CHANGELOG.md +52 -0
- package/CLAUDE.md +12 -0
- package/README.md +15 -0
- package/cabloy-docs/.vitepress/config.mjs +89 -0
- package/cabloy-docs/ai/class-placement-rule.md +2 -0
- package/cabloy-docs/ai/cli-to-skill-map.md +14 -0
- package/cabloy-docs/ai/docs-skills-rules-mapping.md +14 -0
- package/cabloy-docs/ai/future-skill-roadmap.md +27 -9
- package/cabloy-docs/ai/introduction.md +1 -0
- package/cabloy-docs/ai/playbook-backend-module.md +6 -0
- package/cabloy-docs/ai/playbook-module-removal.md +164 -0
- package/cabloy-docs/ai/skills.md +11 -0
- package/cabloy-docs/backend/bean-scene-authoring.md +350 -0
- package/cabloy-docs/backend/cli.md +26 -1
- package/cabloy-docs/backend/dto-guide.md +6 -0
- package/cabloy-docs/backend/entity-guide.md +18 -0
- package/cabloy-docs/backend/foundation.md +28 -3
- package/cabloy-docs/backend/introduction.md +10 -0
- package/cabloy-docs/backend/serialization-guide.md +10 -0
- package/cabloy-docs/backend/service-guide.md +2 -0
- package/cabloy-docs/backend/status-guide.md +271 -0
- package/cabloy-docs/backend/websocket-call-flow.md +435 -0
- package/cabloy-docs/backend/websocket-guide.md +455 -0
- package/cabloy-docs/backend/websocket-protocol-guide.md +381 -0
- package/cabloy-docs/backend/websocket-usage-guide.md +356 -0
- package/cabloy-docs/frontend/api-guide.md +2 -0
- package/cabloy-docs/frontend/bean-scene-authoring.md +374 -0
- package/cabloy-docs/frontend/behavior-guide.md +449 -0
- package/cabloy-docs/frontend/cli.md +24 -0
- package/cabloy-docs/frontend/command-scene-authoring.md +495 -0
- package/cabloy-docs/frontend/design-principles.md +6 -0
- package/cabloy-docs/frontend/fetch-interceptor-guide.md +440 -0
- package/cabloy-docs/frontend/form-guide.md +795 -0
- package/cabloy-docs/frontend/foundation.md +29 -0
- package/cabloy-docs/frontend/introduction.md +17 -1
- package/cabloy-docs/frontend/ioc-and-beans.md +16 -9
- package/cabloy-docs/frontend/mock-guide.md +1 -0
- package/cabloy-docs/frontend/model-architecture.md +252 -39
- package/cabloy-docs/frontend/model-resource-best-practices.md +379 -0
- package/cabloy-docs/frontend/model-resource-cookbook.md +505 -0
- package/cabloy-docs/frontend/model-resource-owner-pattern.md +382 -0
- package/cabloy-docs/frontend/model-resource-usage-guide.md +318 -0
- package/cabloy-docs/frontend/model-state-guide.md +366 -13
- package/cabloy-docs/frontend/openapi-sdk-guide.md +5 -2
- package/cabloy-docs/frontend/page-guide.md +6 -0
- package/cabloy-docs/frontend/quickstart.md +4 -0
- package/cabloy-docs/frontend/reading-zova-for-vue-developers.md +266 -0
- package/cabloy-docs/frontend/router-tabs-admin-web-comparison.md +206 -0
- package/cabloy-docs/frontend/router-tabs-introduction.md +106 -0
- package/cabloy-docs/frontend/router-tabs-mechanism.md +469 -0
- package/cabloy-docs/frontend/router-tabs-overview.md +227 -0
- package/cabloy-docs/frontend/router-tabs-route-meta-cookbook.md +343 -0
- package/cabloy-docs/frontend/server-data.md +2 -0
- package/cabloy-docs/frontend/ssr-architecture-overview.md +211 -0
- package/cabloy-docs/frontend/ssr-build-deploy-guide.md +308 -0
- package/cabloy-docs/frontend/ssr-review-checklist.md +184 -0
- package/cabloy-docs/frontend/ssr-troubleshooting-guide.md +301 -0
- package/cabloy-docs/frontend/zova-form-source-reading-map.md +295 -0
- package/cabloy-docs/frontend/zova-form-under-the-hood.md +556 -0
- package/cabloy-docs/frontend/zova-reactivity-under-the-hood.md +320 -0
- package/cabloy-docs/frontend/zova-source-reading-map.md +327 -0
- package/cabloy-docs/frontend/zova-vs-vue3-comparison.md +308 -0
- package/cabloy-docs/fullstack/contract-loop-playbook.md +350 -0
- package/cabloy-docs/fullstack/framework-performance.md +3 -3
- package/cabloy-docs/fullstack/frontend-metadata-to-backend.md +44 -1
- package/cabloy-docs/fullstack/introduction.md +40 -0
- package/cabloy-docs/fullstack/openapi-to-sdk.md +19 -9
- package/cabloy-docs/fullstack/quickstart.md +7 -1
- package/cabloy-docs/fullstack/tutorial-1-first-module.md +111 -0
- package/cabloy-docs/fullstack/tutorial-2-first-crud.md +122 -0
- package/cabloy-docs/fullstack/tutorial-3-frontend-metadata-sharing.md +131 -0
- package/cabloy-docs/fullstack/tutorial-4-custom-level-renderers.md +144 -0
- package/cabloy-docs/fullstack/tutorial-5-backend-contract-sharing.md +146 -0
- package/cabloy-docs/fullstack/tutorial-6-one-contract-four-uses.md +170 -0
- package/cabloy-docs/fullstack/tutorials-overview.md +192 -0
- package/cabloy-docs/index.md +4 -3
- package/cabloy-docs/reference/bean-scene-boilerplates.md +75 -0
- package/cabloy-docs/reference/cli-reference.md +2 -0
- package/package.json +7 -2
- package/scripts/initTestData.ts +25 -0
- package/scripts/upgrade.ts +17 -2
- package/vona/packages-cli/cabloy-cli/package.json +2 -2
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.create.module.ts +4 -0
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/pnpm-lock.yaml +226 -1091
- package/vona/pnpm-workspace.yaml +0 -1
- package/vona/src/suite-vendor/a-vona/modules/a-core/assets/static/img/vona.svg +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/bean.permission.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-upload/package.json +2 -2
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/package.original.json +1 -1
- package/zova/packages-cli/cli/package.json +3 -3
- package/zova/packages-cli/cli-set-front/cli/templates/init/icon/boilerplate/icons/default/zova.svg +1 -1
- package/zova/packages-cli/cli-set-front/cli/templates/openapi/config/boilerplate/module/openapi.config.ts +6 -1
- package/zova/packages-cli/cli-set-front/package.json +3 -3
- package/zova/packages-cli/cli-set-front/src/lib/bean/cli.create.module.ts +4 -0
- package/zova/packages-cli/cli-set-front/src/lib/bean/cli.openapi.generate.ts +34 -4
- package/zova/packages-cli/cli-set-front/src/lib/command/create.bean.ts +5 -1
- package/zova/packages-utils/zova-vite/package.json +2 -2
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +282 -1311
- package/zova/pnpm-workspace.yaml +0 -1
- package/zova/src/suite/a-home/modules/home-icon/icons/social/cabloy.svg +1 -1
- package/zova/src/suite/a-home/modules/home-icon/icons/social/vona.svg +1 -1
- package/zova/src/suite/a-home/modules/home-icon/icons/social/zova.svg +1 -1
- package/zova/src/suite/a-home/modules/home-icon/src/.metadata/icons/groups/social.svg +3 -3
- package/zova/src/suite/cabloy-basic/modules/basic-select/src/component/formFieldSelect/controller.tsx +9 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +66 -16
- package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-routertabs/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts +60 -18
- package/zova/src/suite-vendor/a-zova/modules/a-table/cli/tableActionRow/boilerplate/{{sceneName}}.{{beanName}}.tsx_ +6 -1
- package/zova/src/suite-vendor/a-zova/modules/a-table/cli/tableCell/boilerplate/{{sceneName}}.{{beanName}}.tsx_ +6 -1
- package/zova/src/suite-vendor/a-zova/modules/a-table/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- 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.
|