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,469 @@
1
+ # Router Tabs Mechanism
2
+
3
+ This guide explains how the router-tabs mechanism works in Zova within the Cabloy monorepo.
4
+
5
+ For the business meaning of the mechanism, see [Router Tabs Overview](/frontend/router-tabs-overview).
6
+
7
+ ## Why this mechanism exists
8
+
9
+ The router-tabs mechanism is designed to support a workbench-style frontend navigation model.
10
+
11
+ Its main purpose is to separate:
12
+
13
+ - business-level grouping
14
+ - page-instance-level switching
15
+
16
+ In code, that separation is expressed mainly through:
17
+
18
+ - `tabKey`
19
+ - `componentKey`
20
+
21
+ These two fields are related, but they do not mean the same thing.
22
+
23
+ In the current Cabloy Basic frontend source, the shared router-tabs model lives in:
24
+
25
+ - `zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts`
26
+
27
+ The Admin layout integration appears in:
28
+
29
+ - `zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/controller.tsx`
30
+ - `zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.tabs.tsx`
31
+
32
+ The Web layout reuses the same model through:
33
+
34
+ - `zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/controller.tsx`
35
+ - `zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.tabs.tsx`
36
+
37
+ ## Core data model
38
+
39
+ The main tab model is `RouteTab`.
40
+
41
+ Representative shape:
42
+
43
+ ```typescript
44
+ export interface RouteTab extends RouteTabBase {
45
+ items?: IRouteViewRouteItem[];
46
+ updatedAt: number;
47
+ info: RouteTabInfo;
48
+ }
49
+ ```
50
+
51
+ A `RouteTab` represents one level-1 tab.
52
+
53
+ Its `items` array contains the level-2 tab items associated with that level-1 tab.
54
+
55
+ Representative related types include:
56
+
57
+ - `RouteTab`
58
+ - `RouteTabInfo`
59
+ - `IRouteViewRouteItem`
60
+ - `IRouteViewRouteMeta`
61
+ - `IPageMeta`
62
+
63
+ The important conceptual split is:
64
+
65
+ - `RouteTab` is the level-1 workspace
66
+ - `RouteTab.items[]` are the level-2 work items
67
+
68
+ Representative source definitions:
69
+
70
+ - `zova/src/suite-vendor/a-zova/modules/a-routertabs/src/types/tabs.ts`
71
+ - `zova/src/suite-vendor/a-zova/modules/a-router/src/types/routerView.ts`
72
+ - `zova/src/suite-vendor/a-zova/modules/a-router/src/types/pageMeta.ts`
73
+
74
+ ## Route-to-tab mapping
75
+
76
+ When a route is processed, the model prepares a route meta record containing:
77
+
78
+ - `fullPath`
79
+ - `componentKey`
80
+ - `tabKey`
81
+ - `keepAlive`
82
+
83
+ Representative pattern:
84
+
85
+ ```typescript
86
+ prepareRouteMeta(route: RouteLocationNormalizedLoadedGeneric): IRouteViewRouteMeta {
87
+ const fullPath = route.fullPath;
88
+ const componentKey = this.__handleRoutePropComponentKey(route);
89
+ const tabKey = this._handleRouteProp(route, 'tabKey') || componentKey;
90
+ const keepAlive = this._handleRouteProp(route, 'keepAlive');
91
+ return { tabKey, componentKey, fullPath, keepAlive };
92
+ }
93
+ ```
94
+
95
+ This is the central mapping step from routing state into tab state.
96
+
97
+ Representative implementation:
98
+
99
+ - `zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts` lines 493-530
100
+
101
+ ## `tabKey`: level-1 grouping identity
102
+
103
+ `tabKey` decides which level-1 tab a route belongs to.
104
+
105
+ This is the grouping key.
106
+
107
+ If several routes should belong to the same business workspace, they should share the same `tabKey`.
108
+
109
+ If a route does not explicitly provide one, the mechanism falls back to `componentKey`.
110
+
111
+ That fallback means:
112
+
113
+ - every route can still open as a tab
114
+ - a route only joins an existing level-1 group when grouping is intentionally defined
115
+
116
+ This makes `tabKey` the business-grouping surface rather than the page-instance surface.
117
+
118
+ ### Recommended `tabKey` rule
119
+
120
+ Use a stable `tabKey` for the durable business workspace identity, not for a temporary route variant.
121
+
122
+ Good candidates include:
123
+
124
+ - a module link such as `/user/list`
125
+ - a stable named business entry
126
+ - a menu identity that should continue to represent the same workspace over time
127
+
128
+ Avoid using `tabKey` values that change only because query, params, or transient task context changes.
129
+
130
+ ## `componentKey`: level-2 instance identity
131
+
132
+ `componentKey` decides whether the current route should be treated as the same page instance or as a distinct one.
133
+
134
+ This is the level-2 identity key.
135
+
136
+ The mechanism first checks route meta for `componentKey`. If it is not provided, it derives the value from route name or route path.
137
+
138
+ Representative logic:
139
+
140
+ - explicit `meta.componentKey` has priority
141
+ - if route name exists and `componentKeyMode === 'nameOnly'`, use the route name
142
+ - otherwise use the route path
143
+
144
+ This matters because different pages need different reuse rules.
145
+
146
+ Representative implementation:
147
+
148
+ - `zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts` lines 519-530
149
+ - `zova/src/suite/a-demo/modules/demo-basic/src/routes.ts` lines 20-28
150
+
151
+ ### Example: shared instance
152
+
153
+ If a route uses `componentKeyMode: 'nameOnly'`, routes with different params can still share one logical page instance.
154
+
155
+ This is useful when the page should behave as one reusable workspace rather than opening a separate tab for each path variation.
156
+
157
+ ### Example: separate instance
158
+
159
+ If the effective `componentKey` changes with the route path, different page entries can appear as separate level-2 tab items.
160
+
161
+ This is useful when different records or route shapes should remain independently open.
162
+
163
+ ### Recommended `componentKey` rule
164
+
165
+ Use `componentKey` to answer one question only:
166
+
167
+ - should these route visits reuse one page instance, or should they remain separately open?
168
+
169
+ If the answer is reuse, keep `componentKey` coarse and stable.
170
+
171
+ If the answer is parallel open work, keep `componentKey` fine enough to distinguish those instances.
172
+
173
+ Do not use `componentKey` as a substitute for business grouping. That is `tabKey`'s job.
174
+
175
+ ## Tab creation flow
176
+
177
+ The main flow is:
178
+
179
+ 1. route enters the router-view tab system
180
+ 2. route meta is prepared
181
+ 3. the model calls `addTab`
182
+ 4. the system either creates a new level-1 tab or updates an existing one
183
+ 5. the current active tab keys are updated
184
+
185
+ Representative route-forwarding pattern:
186
+
187
+ ```typescript
188
+ forwardRoute(route: RouteLocationNormalizedLoadedGeneric) {
189
+ const routeMeta = this.prepareRouteMeta(route);
190
+ this.addTab(routeMeta);
191
+ }
192
+ ```
193
+
194
+ Inside `addTab`, the mechanism:
195
+
196
+ - finds the existing level-1 tab by `tabKey`
197
+ - resolves the display info for that tab
198
+ - creates a new tab if needed
199
+ - inserts a newly created level-1 tab near the current context while keeping affixed tabs as a contiguous prefix
200
+ - otherwise updates the tab and its items without reordering an already existing level-1 tab
201
+
202
+ Representative implementation:
203
+
204
+ - `zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts` lines 122-184
205
+ - `zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts` lines 483-486
206
+
207
+ ## Level-1 tab structure and the anchor item
208
+
209
+ When a new level-1 tab is created, the first item is usually created from the current route itself.
210
+
211
+ That first item often acts as the anchor route for the level-1 tab.
212
+
213
+ This is why the items list may contain an item whose `componentKey` equals `tabKey`.
214
+
215
+ In the Admin layout, level-2 rendering intentionally skips that first anchor item. That means:
216
+
217
+ - the level-1 tab still has a route anchor
218
+ - the level-2 row only shows the additional work items
219
+
220
+ This is an important part of the mechanism.
221
+
222
+ The shared model also preserves these ordering rules:
223
+
224
+ - the anchor item stays first within the workspace item list
225
+ - new level-2 items open near the current active item when they belong to the current workspace
226
+ - revisiting an already open level-2 item updates it in place instead of moving it
227
+
228
+ Representative implementation:
229
+
230
+ - `zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts` lines 142-158
231
+ - `zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.tabs.tsx` lines 67-70
232
+
233
+ ## Tab activation behavior
234
+
235
+ Activating a level-1 tab is different from activating a level-2 tab.
236
+
237
+ ### Activating a level-1 tab
238
+
239
+ When a level-1 tab becomes active, the system tries to navigate back through that tab's anchor route or first route item.
240
+
241
+ Representative behavior:
242
+
243
+ ```typescript
244
+ const tabItemFirst = tab.items?.[0];
245
+ const path = tabItemFirst?.componentKey === tabKey ? tabItemFirst.fullPath : tabKey;
246
+ await this.$router.push(path);
247
+ ```
248
+
249
+ This means a level-1 tab is not just a label. It remains connected to a restorable route context.
250
+
251
+ ### Activating a level-2 tab
252
+
253
+ When a level-2 tab item becomes active, the system pushes its own `fullPath`.
254
+
255
+ That gives each work item an exact route target.
256
+
257
+ Representative implementation:
258
+
259
+ - `zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts` lines 351-373
260
+
261
+ ## Tab info and menu integration
262
+
263
+ In the Admin layout, level-1 tab info is usually derived from the menu model.
264
+
265
+ Representative initialization pattern:
266
+
267
+ - `getInitialTabs`
268
+ - `getTabInfo`
269
+
270
+ This means the level-1 title and icon are normally tied to business navigation identity, not to the runtime page title of a specific inner task.
271
+
272
+ That is why the first-level and second-level labels come from different sources.
273
+
274
+ Representative implementation:
275
+
276
+ - `zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/controller.tsx` lines 75-107
277
+ - `zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/controller.tsx` lines 73-107
278
+
279
+ ## Page metadata and level-2 state
280
+
281
+ Level-2 tab items can carry `pageMeta`.
282
+
283
+ Representative page metadata includes:
284
+
285
+ - `pageTitle`
286
+ - `pageDirty`
287
+ - `formMeta`
288
+
289
+ This metadata shapes the visible state of the level-2 tab.
290
+
291
+ ### `pageTitle`
292
+
293
+ `pageTitle` is typically used as the displayed title of the level-2 item.
294
+
295
+ This is what allows the second level to show task-specific names instead of module names.
296
+
297
+ ### `pageDirty`
298
+
299
+ If `pageDirty` is true, the UI can show a dirty indicator such as an asterisk.
300
+
301
+ This tells the user that the current work item has unsaved changes.
302
+
303
+ ### `formMeta.formScene`
304
+
305
+ The form scene can also affect the icon or visual state.
306
+
307
+ Typical cases include:
308
+
309
+ - `create`
310
+ - `edit`
311
+
312
+ This helps the user distinguish draft-like work from modification work at a glance.
313
+
314
+ Representative implementation:
315
+
316
+ - `zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts` lines 212-233
317
+ - `zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts` lines 488-490
318
+ - `zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.tabs.tsx` lines 113-128
319
+
320
+ ## Keep-alive behavior
321
+
322
+ The model computes a `keepAliveInclude` list from tab items.
323
+
324
+ Representative rule:
325
+
326
+ - include each `componentKey` whose tab item is not explicitly `keepAlive: false`
327
+ - avoid duplicates
328
+
329
+ This keeps routed page instances alive while they remain part of the active tab model.
330
+
331
+ This is a key part of the workbench experience because users expect tabs to preserve state across switching.
332
+
333
+ For the broader SSR client-only boundary pattern, also see [SSR ClientOnly](/frontend/ssr-client-only).
334
+
335
+ Representative implementation:
336
+
337
+ - `zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts` lines 452-465
338
+ - `zova/src/suite-vendor/a-zova/modules/a-routertabs/src/component/routerViewTabs/controller.tsx` lines 36-42
339
+
340
+ ## Cache behavior
341
+
342
+ The tab model supports both in-memory and persistent state depending on configuration.
343
+
344
+ Representative options include:
345
+
346
+ - `cache`
347
+ - `max`
348
+ - `maxItems`
349
+
350
+ If `cache` is enabled, the model can store tab state through a persistent model-state path instead of memory-only state.
351
+
352
+ This allows the workbench state to survive refreshes more effectively.
353
+
354
+ ### Why dirty state is reset
355
+
356
+ When cached tab state is restored, the mechanism resets `pageDirty` flags.
357
+
358
+ This is an important safety boundary.
359
+
360
+ A restored tab should not automatically claim that the user still has unsaved work unless the application can truly re-establish that state reliably.
361
+
362
+ That is why the mechanism restores the workspace structure but clears fragile dirty markers.
363
+
364
+ Representative implementation:
365
+
366
+ - `zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts` lines 71-87
367
+ - `zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts` lines 532-545
368
+ - `zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.tabs.tsx` lines 24-25
369
+ - `zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.tabs.tsx` lines 25-26
370
+
371
+ ## Capacity control
372
+
373
+ The model supports two capacity limits:
374
+
375
+ - `max`: maximum number of level-1 tabs
376
+ - `maxItems`: maximum number of level-2 items per level-1 tab
377
+
378
+ When limits are exceeded, the mechanism prunes older entries based on update time.
379
+
380
+ This keeps the workbench from growing without bound.
381
+
382
+ There is also special handling for affixed tabs such as fixed entry tabs. Those should not be pruned in the same way as normal tabs.
383
+
384
+ Representative implementation:
385
+
386
+ - `zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts` lines 382-423
387
+
388
+ ## Rendering model in Admin and Web layouts
389
+
390
+ The current Cabloy Basic source shows two different presentations of the same model.
391
+
392
+ ### Admin layout
393
+
394
+ The Admin layout renders:
395
+
396
+ - level-1 tabs in a lifted tab row
397
+ - level-2 tabs in a bordered tab row for the current level-1 tab
398
+
399
+ Important implementation points include:
400
+
401
+ - level-1 uses tab info such as title and icon
402
+ - level-2 uses page-level metadata such as `pageTitle`
403
+ - level-2 skips the anchor item
404
+ - close actions differ for level-1 and level-2 items
405
+
406
+ Representative implementation:
407
+
408
+ - `zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.tabs.tsx`
409
+
410
+ ### Web layout
411
+
412
+ The Web layout reuses the same router-tabs model but renders top-level workspaces as horizontal menu items and nested menu groups.
413
+
414
+ That means the framework contract should be understood as a route-grouping and workbench-state mechanism, not only as one specific double-tab-row UI.
415
+
416
+ Representative implementation:
417
+
418
+ - `zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.tabs.tsx`
419
+
420
+ ## Extension guidance
421
+
422
+ When designing pages that participate in this mechanism, ask these questions:
423
+
424
+ ### Should several routes belong to one business workspace?
425
+
426
+ If yes, define a stable `tabKey`.
427
+
428
+ ### Should several route variants reuse one page instance?
429
+
430
+ If yes, consider `componentKey` or `componentKeyMode`.
431
+
432
+ ### Should the page show task-specific titles or state in the second-level tabs?
433
+
434
+ If yes, provide `pageMeta` updates such as `pageTitle`, `pageDirty`, or form scene metadata.
435
+
436
+ ### Should the workbench survive refreshes?
437
+
438
+ If yes, evaluate whether `cache` should be enabled.
439
+
440
+ ### Should users be able to open several records in parallel?
441
+
442
+ If yes, ensure that `componentKey` is fine-grained enough to keep those records distinct.
443
+
444
+ ## Common pitfalls
445
+
446
+ Typical mistakes include:
447
+
448
+ - using `tabKey` as if it were only a route identity
449
+ - making `tabKey` unstable across equivalent business routes
450
+ - making `componentKey` too coarse and collapsing distinct tasks into one tab item
451
+ - making `componentKey` too fine and opening too many nearly identical tab items
452
+ - assuming dirty state can be restored safely after every reload
453
+ - treating one layout's rendering choice as if it were the full framework contract
454
+
455
+ ## Summary
456
+
457
+ The router-tabs mechanism depends on a clear split:
458
+
459
+ - `tabKey` groups routes into level-1 workspaces
460
+ - `componentKey` identifies level-2 work items or page instances
461
+
462
+ With page metadata, keep-alive behavior, caching, and capacity control layered on top, the result is a workbench-style navigation model that fits complex admin workflows much better than plain route switching.
463
+
464
+ ## See also
465
+
466
+ - [Router Tabs Introduction](/frontend/router-tabs-introduction)
467
+ - [Router Tabs Overview](/frontend/router-tabs-overview)
468
+ - [Router Tabs Route Meta Cookbook](/frontend/router-tabs-route-meta-cookbook)
469
+ - [Router Tabs Admin and Web Comparison](/frontend/router-tabs-admin-web-comparison)
@@ -0,0 +1,227 @@
1
+ # Router Tabs Overview
2
+
3
+ This guide explains the business meaning of the router tabs mechanism in Zova within the Cabloy monorepo.
4
+
5
+ In the current Cabloy Basic frontend source, the same mechanism supports more than one layout style. The Admin layout shows the two-level meaning explicitly, while the Web layout reuses the same model with a menu-oriented top-level presentation.
6
+
7
+ ## Why router tabs matter
8
+
9
+ In a simple application, a left-side menu plus route navigation can be enough.
10
+
11
+ In an admin-style application, that is often not enough.
12
+
13
+ Users usually need to:
14
+
15
+ - move across several business modules
16
+ - keep the current working context of each module
17
+ - open more than one task inside the same module
18
+ - switch between unfinished tasks without losing their place
19
+
20
+ That is the main problem solved by router tabs.
21
+
22
+ The mechanism is not only a visual tab bar. It is a workbench-style navigation model.
23
+
24
+ ## The two levels at a glance
25
+
26
+ In the Admin layout, the two levels serve different purposes:
27
+
28
+ - level-1 tabs represent business modules or workspaces
29
+ - level-2 tabs represent concrete tasks or page instances inside the current workspace
30
+
31
+ This separation is the key idea.
32
+
33
+ A user should be able to say both:
34
+
35
+ - "I am working in Order Management"
36
+ - "Inside Order Management, I currently have Create Order and Edit Order 2026001 open"
37
+
38
+ That is exactly what the two levels express.
39
+
40
+ ## Business meaning of level-1 tabs
41
+
42
+ A level-1 tab represents the broader business area the user is working in.
43
+
44
+ Typical examples include:
45
+
46
+ - Home
47
+ - User Management
48
+ - Order Management
49
+ - Content Management
50
+
51
+ This level should stay relatively stable.
52
+
53
+ Its job is not to reflect every temporary page the user opens. Its job is to preserve the higher-level workspace identity.
54
+
55
+ In practice, this gives the user a stable top-level navigation surface even when the detailed work inside a module changes frequently.
56
+
57
+ In Cabloy Basic Admin, the current level-1 title and icon are typically derived from the menu model rather than from the current inner page title. That is why the first level behaves more like a business workspace than like a raw route list.
58
+
59
+ ## Business meaning of level-2 tabs
60
+
61
+ A level-2 tab represents a concrete working item inside the current level-1 tab.
62
+
63
+ Typical examples include:
64
+
65
+ - a create page
66
+ - an edit page
67
+ - a detail page
68
+ - a preview page
69
+ - multiple records opened in parallel
70
+
71
+ This makes the interaction model much closer to a desktop workbench or IDE.
72
+
73
+ Instead of repeatedly returning to a list and reopening records one by one, the user can keep several work items open and switch between them directly.
74
+
75
+ In practice, the second level is where task identity becomes visible. A level-2 item can represent "Create User", "Edit User A", or "Edit User B" even though all of them still belong to the same level-1 workspace such as User Management.
76
+
77
+ ## Why not use only one level
78
+
79
+ A single-level tab system often becomes unstable in admin workflows.
80
+
81
+ If every open page appears at the top level, the tab bar can quickly become noisy:
82
+
83
+ - User Management
84
+ - Edit User A
85
+ - Edit User B
86
+ - Order Management
87
+ - Edit Order X
88
+ - Create Order
89
+ - Content Management
90
+ - Edit Article 1
91
+
92
+ At that point, the top level is no longer a clean business navigation layer.
93
+
94
+ The two-level design avoids that problem:
95
+
96
+ - level-1 tabs keep the business structure stable
97
+ - level-2 tabs hold the temporary work items
98
+
99
+ This keeps navigation and working context separate.
100
+
101
+ ## Typical admin scenarios
102
+
103
+ Two-level router tabs are especially useful in these cases.
104
+
105
+ ### Parallel work inside one module
106
+
107
+ A user may need to:
108
+
109
+ - create a new user
110
+ - edit an existing user
111
+ - compare two records
112
+ - check a detail page while keeping a form open
113
+
114
+ These are not separate business modules. They are parallel tasks inside the same module.
115
+
116
+ ### Frequent switching across modules
117
+
118
+ A user may need to:
119
+
120
+ - inspect an order
121
+ - jump to a user profile
122
+ - return to inventory
123
+ - go back to the previous order task
124
+
125
+ The mechanism allows the user to switch business areas without losing the active work context of each area.
126
+
127
+ ### Long-running form workflows
128
+
129
+ Some forms are not completed in one uninterrupted pass.
130
+
131
+ Users may leave a form, handle another task, and then return later.
132
+
133
+ A tabbed workbench model supports that better than a strict list-detail-return cycle.
134
+
135
+ ## Visual and interaction meaning
136
+
137
+ The two levels carry different kinds of information.
138
+
139
+ ### Level-1 tabs
140
+
141
+ Level-1 tabs usually communicate:
142
+
143
+ - business identity
144
+ - module title
145
+ - module icon
146
+ - whether the workspace is pinned or fixed
147
+
148
+ ### Level-2 tabs
149
+
150
+ Level-2 tabs usually communicate:
151
+
152
+ - page or record title
153
+ - current task identity
154
+ - whether the page is dirty
155
+ - whether the page is in create or edit mode
156
+
157
+ This means the user can see not only where they are working, but also what state the current work item is in.
158
+
159
+ ## Workbench continuity
160
+
161
+ Another important value is continuity.
162
+
163
+ A module tab is not just a shortcut to a menu entry. It acts more like a workspace anchor.
164
+
165
+ When the user returns to a level-1 tab, the system can restore the working context associated with that tab instead of forcing the user to start navigation from scratch each time.
166
+
167
+ This is one of the main reasons the mechanism feels more like a workbench than a simple menu wrapper.
168
+
169
+ ## Admin and Web layout perspective
170
+
171
+ The current Cabloy Basic source shows that router tabs are a mechanism first and a specific visual shape second.
172
+
173
+ ### Admin layout
174
+
175
+ The Admin layout exposes the two-level meaning directly:
176
+
177
+ - a first row for workspace-level tabs
178
+ - a second row for task-level items inside the active workspace
179
+
180
+ This is the clearest illustration of the level-1 and level-2 split.
181
+
182
+ ### Web layout
183
+
184
+ The Web layout reuses the same underlying tab model but presents the first level more like a menu-driven horizontal workspace surface.
185
+
186
+ That means future contributors should avoid assuming that the framework contract is identical to one concrete tab-row UI.
187
+
188
+ The durable idea is the route-to-workspace model, not only the exact Admin visual treatment.
189
+
190
+ ## When this pattern is a good fit
191
+
192
+ This pattern is a good fit when the frontend behaves like a business workbench, especially when users need:
193
+
194
+ - multi-step task flows
195
+ - repeated switching across modules
196
+ - several open records at once
197
+ - persistent working context
198
+ - form-heavy operations
199
+
200
+ It is less important for simple content sites or low-interaction flows where a single route transition is enough.
201
+
202
+ ## Recommended design questions
203
+
204
+ When deciding whether a page or feature should participate in router tabs, ask these questions first:
205
+
206
+ 1. is this a real business workspace or only a temporary route?
207
+ 2. should several related pages stay grouped under one stable module identity?
208
+ 3. do users need to keep more than one work item open at the same time?
209
+ 4. does the page need visible task state such as dirty/create/edit indicators?
210
+
211
+ If most answers are yes, the router-tabs workbench model is usually a strong fit.
212
+
213
+ ## Summary
214
+
215
+ The router tabs mechanism separates two different concerns:
216
+
217
+ - business-module switching
218
+ - task-level page switching inside the current module
219
+
220
+ That separation improves clarity, preserves working context, and supports parallel task handling in admin-style applications.
221
+
222
+ ## See also
223
+
224
+ - [Router Tabs Introduction](/frontend/router-tabs-introduction)
225
+ - [Router Tabs Mechanism](/frontend/router-tabs-mechanism)
226
+ - [Router Tabs Route Meta Cookbook](/frontend/router-tabs-route-meta-cookbook)
227
+ - [Router Tabs Admin and Web Comparison](/frontend/router-tabs-admin-web-comparison)