cabloy 5.1.158 → 5.1.160

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 (120) hide show
  1. package/.cabloy-version +1 -1
  2. package/.claude/skills/cabloy-contract-loop/SKILL.md +24 -7
  3. package/.claude/skills/cabloy-contract-loop/evals/evals.json +12 -0
  4. package/.claude/skills/cabloy-contract-loop/references/contract-loop-map.md +28 -0
  5. package/.claude/skills/cabloy-contract-loop/references/verification-checklist.md +19 -2
  6. package/.claude/skills/cabloy-resource-field-update/SKILL.md +18 -0
  7. package/.claude/skills/cabloy-zova-source-reading/SKILL.md +14 -0
  8. package/.claude/skills/cabloy-zova-source-reading/references/core-reading-paths.md +10 -0
  9. package/.github/workflows/docs-pages.yml +18 -2
  10. package/CHANGELOG.md +31 -0
  11. package/package.json +2 -1
  12. package/repo-docs/.vitepress/config.mjs +45 -4
  13. package/repo-docs/.vitepress/theme/components/GitHubRepositoriesNav.vue +255 -0
  14. package/repo-docs/.vitepress/theme/custom.css +21 -0
  15. package/repo-docs/.vitepress/theme/index.js +7 -1
  16. package/repo-docs/backend/department-management.md +98 -0
  17. package/repo-docs/backend/menu-authorization.md +124 -0
  18. package/repo-docs/backend/rbac-authorization.md +117 -0
  19. package/repo-docs/backend/resource-field-update.md +49 -2
  20. package/repo-docs/backend/role-management.md +92 -0
  21. package/repo-docs/backend/shared-rbac-architecture.md +339 -0
  22. package/repo-docs/backend/user-management.md +92 -0
  23. package/repo-docs/blogs/index.md +2 -0
  24. package/repo-docs/frontend/form-layout-guide.md +6 -6
  25. package/repo-docs/frontend/model-resource-owner-pattern.md +2 -0
  26. package/repo-docs/frontend/page-guide.md +12 -0
  27. package/repo-docs/frontend/page-meta-guide.md +27 -0
  28. package/repo-docs/frontend/permission-formscene-action-visibility-guide.md +5 -3
  29. package/repo-docs/frontend/ssr-architecture-overview.md +4 -0
  30. package/repo-docs/frontend/ssr-build-deploy-guide.md +1 -1
  31. package/repo-docs/frontend/table-action-visibility-permission-flow-guide.md +2 -0
  32. package/repo-docs/frontend/table-guide.md +75 -22
  33. package/repo-docs/frontend/table-resource-crud-cookbook.md +25 -4
  34. package/repo-docs/frontend/zova-form-source-reading-map.md +2 -2
  35. package/repo-docs/frontend/zova-form-under-the-hood.md +1 -1
  36. package/repo-docs/frontend/zova-reactivity-under-the-hood.md +65 -9
  37. package/repo-docs/frontend/zova-table-source-reading-map.md +54 -19
  38. package/repo-docs/frontend/zova-table-under-the-hood.md +70 -39
  39. package/repo-docs/fullstack/contract-loop-playbook.md +2 -0
  40. package/repo-docs/fullstack/ssr-site-and-flavor-setup.md +198 -0
  41. package/repo-docs/fullstack/vona-zova-integration.md +2 -0
  42. package/repo-e2e/docs/playwright.config.ts +37 -0
  43. package/repo-e2e/docs/specs/blogs-index.spec.ts +180 -0
  44. package/repo-e2e/docs/specs/github-repositories-nav.spec.ts +69 -0
  45. package/repo-e2e/docs/test-results/.last-run.json +4 -0
  46. package/repo-e2e/specs/cabloy-basic.spec.ts +136 -2
  47. package/test-results/.last-run.json +20 -2
  48. package/test-results/a-commerce-ATP-ADDR-01-aut-11dea-ss-through-Web-self-service/error-context.md +238 -0
  49. package/test-results/a-commerce-ATP-SPC-01-Coup-ef380-mantic-Admin-field-controls/error-context.md +240 -0
  50. package/test-results/a-commerce-ATP-SPC-02-Cate-9f120-on-and-publication-controls/error-context.md +238 -0
  51. package/test-results/a-commerce-ATP-SPC-02-Prod-5f92a-on-and-publication-controls/error-context.md +238 -0
  52. package/test-results/a-commerce-ATP-SPC-02-SKU--2e61e-ency-and-lifecycle-controls/error-context.md +238 -0
  53. package/test-results/a-commerce-ATP-SPC-04-Stoc-fa895--readonly-and-mutation-free/error-context.md +235 -0
  54. package/test-results/a-commerce-ATP-SPC-05-syst-fb406-e-without-mutation-controls/error-context.md +238 -0
  55. package/test-results/a-commerce-Commerce-sessio-4e963-ie-selects-raw-server-theme/error-context.md +226 -0
  56. package/test-results/a-commerce-Commerce-theme--062fe--without-hydration-mismatch/error-context.md +229 -0
  57. package/test-results/a-commerce-PayPal-browser--8672c-t-or-open-an-awaiting-order/error-context.md +238 -0
  58. package/test-results/a-commerce-Payment-callbac-f56c2--reconciles-after-hydration/error-context.md +238 -0
  59. package/test-results/a-commerce-Payment-cancell-3fc74-ified-provider-confirmation/error-context.md +238 -0
  60. package/test-results/a-commerce-Phase-50-60-aut-71d61--observes-operator-shipment/error-context.md +238 -0
  61. package/test-results/a-commerce-Phase-60-custom-407cd-ecutes-a-whole-order-refund/error-context.md +238 -0
  62. package/test-results/cabloy-basic-ATP-BASIC-TAB-e7253-and-pins-configured-columns/error-context.md +240 -0
  63. package/test-results/markdown-ATP-SPC-02-Produc-9d5c2-link-toolbar-edits-Markdown/error-context.md +211 -0
  64. package/test-results/markdown-ATP-SPC-02-Produc-9d6ea-t-editor-and-saves-Markdown/error-context.md +238 -0
  65. package/vona/packages-cli/cli/package.json +1 -1
  66. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/entity/{{resourceName}}.tsx_ +1 -1
  67. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/entity/{{resourceName}}.tsx_ +1 -1
  68. package/vona/packages-cli/cli-set-api/package.json +1 -1
  69. package/vona/packages-vona/vona/package.json +1 -1
  70. package/vona/pnpm-lock.yaml +79 -52
  71. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +3 -0
  72. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +3 -0
  73. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productCreate.tsx +2 -0
  74. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productUpdate.tsx +2 -0
  75. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productView.tsx +2 -0
  76. package/vona/src/suite/a-training/modules/training-record/src/dto/recordCreate.tsx +1 -0
  77. package/vona/src/suite/a-training/modules/training-record/src/dto/recordSelectReq.tsx +3 -7
  78. package/vona/src/suite/a-training/modules/training-record/src/dto/recordUpdate.tsx +1 -0
  79. package/vona/src/suite/a-training/modules/training-record/src/dto/recordView.tsx +1 -0
  80. package/vona/src/suite/a-training/modules/training-record/src/entity/record.tsx +8 -0
  81. package/vona/src/suite/a-training/modules/training-record/test/record.test.ts +51 -0
  82. package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +3 -0
  83. package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +3 -0
  84. package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +1 -0
  85. package/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectResItem.tsx +1 -0
  86. package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +1 -0
  87. package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +1 -0
  88. package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +10 -1
  89. package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +99 -1
  90. package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
  91. package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
  92. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  93. package/vona/src/suite-vendor/a-vona/modules/a-web/package.json +1 -1
  94. package/vona/src/suite-vendor/a-vona/modules/a-web/src/bean/pipe.filter.ts +85 -57
  95. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  96. package/zova/packages-cli/cli/package.json +2 -2
  97. package/zova/packages-cli/cli-set-front/cli/templates/rest/render.ts +2 -0
  98. package/zova/packages-cli/cli-set-front/cli/templates/rest/rest.ts +13 -3
  99. package/zova/packages-cli/cli-set-front/package.json +1 -1
  100. package/zova/packages-zova/zova/package.json +2 -2
  101. package/zova/pnpm-lock.yaml +16 -16
  102. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx +31 -11
  103. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockTable/controller.tsx +3 -0
  104. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsx +1 -1
  105. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/render.tsx +68 -8
  106. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  107. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +4 -0
  108. package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
  109. package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
  110. package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts +0 -6
  111. package/zova/src/suite-vendor/a-zova/modules/a-form/test/lib/formLayout.test.ts +16 -9
  112. package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
  113. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/model/sdk.ts +4 -0
  114. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/rest.ts +13 -2
  115. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/schema.ts +1 -0
  116. package/zova/src/suite-vendor/a-zova/modules/a-table/package.json +1 -1
  117. package/zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/controller.tsx +50 -0
  118. package/zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/render.tsx +60 -7
  119. package/zova/src/suite-vendor/a-zova/modules/a-table/src/types/table.ts +6 -4
  120. package/zova/src/suite-vendor/a-zova/package.json +4 -4
@@ -47,7 +47,7 @@ If you only remember one idea, remember this one:
47
47
  That leads to three common authoring surfaces:
48
48
 
49
49
  - `ZTable` — the root table component
50
- - schema `rest.table` metadata — the default source of visibility, order, and render decisions
50
+ - schema metadata — `ZovaRender.column(...)` supplies table-column layout/capability metadata, while shared/table-scene overlays supply effective visibility, order, and render metadata
51
51
  - `tableCell` beans — the main extension surface for reusable cell rendering
52
52
 
53
53
  ## One running example through this guide: Student list page
@@ -123,17 +123,12 @@ A practical reading takeaway is:
123
123
 
124
124
  In the default path, `ZTable` reads table-scene metadata from the row schema.
125
125
 
126
- The key table-facing metadata surface is the schema field `rest.table`, especially values such as:
127
-
128
- - `order`
129
- - `visible`
130
- - `render`
131
- - `columnProps`
126
+ The table sees the **effective table metadata**, not only one literal source object. It combines shared field `rest` metadata with the table-scene `rest.table` overlay, then orders the resulting properties by effective `rest.order`.
132
127
 
133
128
  A simplified mental model is:
134
129
 
135
130
  ```text
136
- schema row -> load table properties -> sort by order -> filter by visible -> build columns -> render cells
131
+ schema row -> merge table metadata -> sort by order -> filter by visible -> build columns -> render cells
137
132
  ```
138
133
 
139
134
  That means the default list-page question becomes less:
@@ -146,6 +141,58 @@ and more:
146
141
 
147
142
  This is why table work often belongs in the broader contract loop when the real source of truth is backend field metadata.
148
143
 
144
+ ## Step 4: Configure physical columns with `ZovaRender.column(...)`
145
+
146
+ `ZovaRender.column(...)` is a shared Cabloy/Zova schema-metadata API available in both Cabloy Basic and Cabloy Start. It returns a schema transformer that writes column options under `rest.table`; it does **not** register a frontend column or render a cell by itself.
147
+
148
+ ```ts
149
+ @Api.field(
150
+ v.title($locale('Name')),
151
+ ZovaRender.column({
152
+ align: 'left',
153
+ width: 240,
154
+ fixed: 'left',
155
+ enableSorting: true,
156
+ sortDescFirst: true,
157
+ }),
158
+ v.string(),
159
+ )
160
+ name: string;
161
+ ```
162
+
163
+ The current options are:
164
+
165
+ | Option | Meaning in the table runtime |
166
+ | --------------- | ------------------------------------------------------------------------------------------------------------------------------ |
167
+ | `order` | Orders this property among the effective table properties. For shared field order, `ZovaRender.order(...)` is usually clearer. |
168
+ | `align` | Applies `left`, `center`, or `right` text alignment to the header and cells. |
169
+ | `width` | Supplies the TanStack column size and current renderers apply it as pixel width and minimum width. |
170
+ | `fixed` | Pins the column to the `left` or `right`; the table runtime calculates pinning and renderers apply sticky offsets. |
171
+ | `enableSorting` | Requests a sortable header, subject to the order-schema capability described below. |
172
+ | `sortDescFirst` | Makes the first header toggle descend; it does not change backend order syntax. |
173
+
174
+ `ZovaRender.column(...)` configures the physical column. `ZovaRender.cell(...)` independently selects the cell renderer and its options. For example, a virtual Operations field can be right-pinned and centered while its content comes from a reusable `tableCell` resource:
175
+
176
+ ```ts
177
+ @Api.field(
178
+ v.title($locale('Operations')),
179
+ ZovaRender.order(1, 'max'),
180
+ ZovaRender.column({ align: 'center', width: 360, fixed: 'right' }),
181
+ ZovaRender.cell('basic-table:actionOperationsRow', { actions }),
182
+ )
183
+ _operationsRow?: unknown;
184
+ ```
185
+
186
+ The `basic-table:*` renderer key in this example is Cabloy Basic-specific. The `ZovaRender.column(...)` contract itself is shared; use the active edition's renderer keys when selecting a cell resource.
187
+
188
+ ### Enable server-backed sorting
189
+
190
+ `enableSorting: true` is necessary but not sufficient. The same field, canonical key, or schema alias must also exist in `schemaOrder`; otherwise the table deliberately creates a non-sortable column.
191
+
192
+ A direct `ZTable` consumer owns and passes `schemaOrder`, controlled `sorting`, and `onSortingChange`. In the standard Cabloy Basic resource-page path, `basic-page:blockTable` passes those values from `basic-page:blockPage`; the page translates one manual table sort into backend `orders` and reloads the resource query. The loaded page is not client-side sorted by TanStack.
193
+
194
+ For relation-order rewriting, see [Existing Resource Field Update](/backend/resource-field-update#filter-and-sort-a-relation-by-its-display-field). For the resource-page bridge, see [Table + Resource CRUD Cookbook](/frontend/table-resource-crud-cookbook).
195
+
149
196
  A practical expression example is a schema-driven cell display that formats the current row value through CEL:
150
197
 
151
198
  ```text
@@ -156,7 +203,7 @@ In the shared table CEL scope, `getValue(name)` reads the current row value and
156
203
 
157
204
  For the schema side of that contract, also see [API Schema Guide](/frontend/api-schema-guide).
158
205
 
159
- ## Step 4: Use built-in or custom `tableCell` render resources
206
+ ## Step 5: Use built-in or custom `tableCell` render resources
160
207
 
161
208
  A column render is usually chosen through schema metadata such as:
162
209
 
@@ -184,7 +231,7 @@ A practical rule is:
184
231
  - use built-in `tableCell` resources first
185
232
  - add a custom `tableCell` bean only when the business UI really needs module-owned rendering behavior
186
233
 
187
- ## Step 5: Create a custom `tableCell` bean
234
+ ## Step 6: Create a custom `tableCell` bean
188
235
 
189
236
  When a built-in renderer is not enough, use the existing CLI-backed scene workflow.
190
237
 
@@ -230,7 +277,7 @@ That scaffold is useful because it starts from the correct Zova scene contract:
230
277
 
231
278
  For the broader scene system behind this decorator, see [Bean Scene Authoring](/frontend/bean-scene-authoring).
232
279
 
233
- ## Step 6: Add page-local custom columns with `getColumns(...)`
280
+ ## Step 7: Add page-local custom columns with `getColumns(...)`
234
281
 
235
282
  When most columns should stay schema-driven but one column should be page-local, use `getColumns(...)`.
236
283
 
@@ -275,7 +322,7 @@ A practical caveat is:
275
322
 
276
323
  So a mixed table does **not** mean bypassing Zova. It means extending the existing runtime through its own hooks.
277
324
 
278
- ## Step 7: Use resource-driven page blocks for CRUD list pages
325
+ ## Step 8: Use resource-driven page blocks for CRUD list pages
279
326
 
280
327
  For standard resource pages, the more common public surface is not direct `ZTable` usage. It is the block-based page runtime:
281
328
 
@@ -299,7 +346,7 @@ This gives you a cohesive CRUD path where:
299
346
 
300
347
  For the resource ownership side of that page shape, see [Model Resource Owner Pattern](/frontend/model-resource-owner-pattern).
301
348
 
302
- ## Step 8: Know when to use `BeanControllerPageTableBase`
349
+ ## Step 9: Know when to use `BeanControllerPageTableBase`
303
350
 
304
351
  If your table belongs directly to a page controller rather than a reusable component controller, the page-oriented base class already exists:
305
352
 
@@ -322,15 +369,19 @@ TanStack Table is still important, but the business-facing runtime is controller
322
369
 
323
370
  For many resource pages, the faster path is to let `rest.table` metadata describe the default columns first.
324
371
 
325
- ### Mistake 3: Bypassing `tableCell` beans for reusable cell behavior
372
+ ### Mistake 3: Treating `ZovaRender.column(...)` as a cell renderer
373
+
374
+ `ZovaRender.column(...)` supplies column layout and sorting metadata. Use `ZovaRender.cell(...)` to name the cell render target, and use a `tableCell` bean when that reusable renderer needs module-owned behavior.
375
+
376
+ ### Mistake 4: Bypassing `tableCell` beans for reusable cell behavior
326
377
 
327
378
  If a renderer should be shared across pages or modules, prefer a `tableCell` resource over repeated page-local callbacks.
328
379
 
329
- ### Mistake 4: Treating `controllerRef` like a generic Vue DOM ref
380
+ ### Mistake 5: Treating `controllerRef` like a generic Vue DOM ref
330
381
 
331
382
  `controllerRef` gives you the table controller instance, not a plain DOM element.
332
383
 
333
- ### Mistake 5: Rebuilding CRUD list wiring manually when `basic-page` already owns it
384
+ ### Mistake 6: Rebuilding CRUD list wiring manually when `basic-page` already owns it
334
385
 
335
386
  For standard resource pages, prefer the existing block/page runtime before hand-building a custom table flow.
336
387
 
@@ -340,12 +391,12 @@ If you want the shortest accurate path to a real business table, use this order:
340
391
 
341
392
  1. make sure the backend row schema is already the right contract truth
342
393
  2. start with a resource page or direct `ZTable`
343
- 3. let schema metadata drive the default columns
344
- 4. reuse built-in `tableCell` renderers first
394
+ 3. let schema metadata drive the default columns; add `ZovaRender.column(...)` only where layout, pinning, or sorting metadata is needed
395
+ 4. use `ZovaRender.cell(...)` and built-in `tableCell` renderers for cell content first
345
396
  5. add page-local `getColumns(...)` only where needed
346
397
  6. create custom `tableCell` beans only where the UI becomes business-specific
347
398
  7. continue with [TableCell Authoring Cookbook](/frontend/table-cell-cookbook) when you want concrete patterns for custom cell beans and row actions
348
- 8. continue with [Table + Resource CRUD Cookbook](/frontend/table-resource-crud-cookbook) when you want the standard resource-page integration path for filter, bulk actions, table, and pager
399
+ 8. continue with [Table + Resource CRUD Cookbook](/frontend/table-resource-crud-cookbook) when you want the standard resource-page integration path for filter, bulk actions, table, pager, and server sorting
349
400
  9. continue with [Zova Table Under the Hood](/frontend/zova-table-under-the-hood) when you want the runtime explanation behind the public authoring surface
350
401
  10. continue with [Zova Table Source Reading Map](/frontend/zova-table-source-reading-map) when you need framework-level source details and targeted file-order guidance
351
402
 
@@ -359,9 +410,11 @@ When documenting or changing a table workflow, verify in this order:
359
410
  npm run zova :create:bean --help
360
411
  ```
361
412
 
362
- 2. confirm the `tableCell` scene metadata still exists in the current `a-table` module
363
- 3. confirm the runtime claims against the current `a-table`, `basic-table`, and `basic-page` source
364
- 4. if you changed docs, build the docs site:
413
+ 2. confirm the `ZovaRender.column(...)` option contract and table-scene metadata merge still match current source
414
+ 3. confirm the `tableCell` scene metadata still exists in the current `a-table` module
415
+ 4. for sortable resource columns, verify header state, fixed-column layout/overflow where relevant, and the backend `orders` request
416
+ 5. confirm the runtime claims against the current `a-table`, `basic-table`, and `basic-page` source
417
+ 6. if you changed docs, build the docs site:
365
418
 
366
419
  ```bash
367
420
  npm run docs:build
@@ -269,6 +269,8 @@ Its bridge role is very clear:
269
269
  - render `ZTable`
270
270
  - pass page data into `data`
271
271
  - pass row schema into `schema`
272
+ - pass the resource order schema into `schemaOrder`
273
+ - pass controlled `sorting` and `onSortingChange`
272
274
  - pass page CEL scope into `tableScope`
273
275
  - capture the table controller through `controllerRef`
274
276
 
@@ -329,10 +331,13 @@ _operationsRow?: unknown;
329
331
 
330
332
  This is the practical reverse-sharing model:
331
333
 
332
- - the backend row contract chooses the table cell resource identity
334
+ - `ZovaRender.column(...)` configures physical-column behavior such as alignment, width, pinning, and sorting eligibility
335
+ - `ZovaRender.cell(...)` chooses the table-cell resource identity and its options
333
336
  - the frontend table runtime resolves that resource
334
337
  - the page block and table block do not need page-local hard-coded row-action wiring
335
338
 
339
+ For example, an Operations field can add `ZovaRender.column({ align: 'center', width: 360, fixed: 'right' })` beside the existing `ZovaRender.cell(...)`. `ZovaRender.column(...)` is a shared Basic/Start capability; the `basic-table:*` cell key remains a Cabloy Basic choice.
340
+
336
341
  For the built-in metadata-sharing teaching path, see [Tutorial 3: Frontend Metadata Sharing](/fullstack/tutorial-3-frontend-metadata-sharing).
337
342
 
338
343
  For the forward-chain row-action teaching path, see [Tutorial 5: Backend Contract Sharing](/fullstack/tutorial-5-backend-contract-sharing).
@@ -346,10 +351,24 @@ For standard CRUD list pages, most customization should happen in one of these p
346
351
  Use this when:
347
352
 
348
353
  - a field should change order
354
+ - a column should change alignment, width, or left/right pinning
355
+ - a server-authorized field should expose a sortable header
349
356
  - a column should be visible or hidden
350
357
  - a field should use a different built-in or custom `tableCell`
351
358
 
352
- This is usually the first and best extension point.
359
+ This is usually the first and best extension point. Use `ZovaRender.column(...)` for the column metadata and `ZovaRender.cell(...)` for the cell renderer; do not treat either helper as a replacement for the other.
360
+
361
+ ### Enable standard resource-page sorting
362
+
363
+ For a normal Cabloy Basic resource list, backend field metadata can request a sortable column with:
364
+
365
+ ```ts
366
+ ZovaRender.column({ enableSorting: true });
367
+ ```
368
+
369
+ The field must also be present in the resource `schemaOrder`; that order schema remains the authority for whether the header is actually sortable. `blockTable` forwards the order schema plus controlled sorting state into `ZTable`, and `blockPage` turns one header selection into backend `orders` before reloading the resource query. This is server sorting, not local reordering of the current page.
370
+
371
+ For the full shared `ZovaRender.column(...)` contract, see [Table Guide](/frontend/table-guide#configure-physical-columns-with-zovarendercolumn). For relation-backed order rewriting, see [Existing Resource Field Update](/backend/resource-field-update#filter-and-sort-a-relation-by-its-display-field).
353
372
 
354
373
  ### Extension point B: backend block composition metadata
355
374
 
@@ -505,8 +524,8 @@ If you want the shortest path to a real CRUD list page, use this order:
505
524
  1. generate or confirm the backend CRUD contract thread
506
525
  2. confirm the resource-owner model already exposes the required schemas and permissions
507
526
  3. keep the standard `blockPage -> blockFilter -> blockToolbarBulk -> blockTable -> blockPager` chain
508
- 4. refine backend row metadata for visible columns and row actions
509
- 5. reuse built-in `tableCell` resources first
527
+ 4. refine backend row metadata for visible columns, layout/pinning/sort capability through `ZovaRender.column(...)`, and row actions
528
+ 5. reuse `ZovaRender.cell(...)` with built-in `tableCell` resources first
510
529
  6. add custom `tableCell` resources only where the UI becomes business-specific
511
530
  7. change block controllers only when the existing runtime is structurally insufficient
512
531
  8. continue with [Table Guide](/frontend/table-guide) for the public `ZTable` surface
@@ -533,6 +552,8 @@ When authoring or documenting a resource CRUD list page, verify in this order:
533
552
  - filter works
534
553
  - bulk actions render correctly
535
554
  - table columns and row actions match metadata
555
+ - fixed columns, widths, and overflow behave as intended when configured
556
+ - sortable headers expose the expected state and reload with the intended backend `orders`
536
557
  - pager updates the list correctly
537
558
  7. if you changed docs, build the docs site:
538
559
 
@@ -233,7 +233,7 @@ Use this path when you are asking questions like:
233
233
  - where does `formLayout` come from in a resource DTO?
234
234
  - how are fields, embedded blocks, sections, groups, and tabs normalized before rendering?
235
235
  - how do canonical keys, preserved schema aliases, and unique relation-prefix shorthand resolve to one field?
236
- - why are omitted visible fields appended or duplicate fields removed?
236
+ - why do omitted fields not render, or duplicate fields get removed?
237
237
  - where does Cabloy Basic render responsive grids and tab error badges?
238
238
 
239
239
  ### Read the docs first
@@ -266,7 +266,7 @@ Use this path when you are asking questions like:
266
266
  - the resolver filters visible fields, resolves exact canonical keys before unique aliases and unique prefixes, and records duplicate identity and tab paths by canonical key
267
267
  - the Basic block controller renders sections, groups, tabs, field spans, and embedded blocks while delegating canonical field names to `$$form.renderField(...)`
268
268
  - `blockFilterActions` shows how a block rendered inside Form Layout reuses the inherited form CEL scope to invoke `$$filter`
269
- - the OpenAPI and Form Layout unit tests verify canonicalization, alias precedence, ambiguity, duplicates, visibility, root append, and tab paths
269
+ - the OpenAPI and Form Layout unit tests verify canonicalization, explicit field inclusion, alias precedence, ambiguity, duplicates, visibility, and tab paths
270
270
  - the Student test verifies emitted metadata nesting, columns, spans, embedded action blocks, and optional IDs; it is not a browser rendering test
271
271
 
272
272
  ## 8. Resource-driven CRUD page integration
@@ -454,7 +454,7 @@ That means automatic schema-driven rendering is not happening magically in the w
454
454
 
455
455
  When `ZForm` receives a nonempty block list, the render bean delegates body rendering to those blocks instead of iterating schema fields directly. For Cabloy Basic structural forms, `basic-form:blockFormLayout` resolves `formLayout` against the form's current schema properties and calls `$$form.renderField(...)` for each surviving layout field.
456
456
 
457
- For a field declaration, the shared resolver first accepts an exact canonical key, then a uniquely mapped preserved schema alias, and then a unique relation-prefix shorthand. It rewrites an alias or shorthand to the canonical key before calling `$$form.renderField(...)`. Exact canonical keys win over colliding aliases; ambiguous aliases or prefixes are reported as `unknownField`. Duplicate tracking and tab-path bookkeeping use the canonical key, while visible canonical fields not placed by a surviving declaration are appended afterward. See [Form Layout Guide](/frontend/form-layout-guide#how-the-resolver-handles-the-declared-tree) for the full DTO authoring rules.
457
+ For a field declaration, the shared resolver first accepts an exact canonical key, then a uniquely mapped preserved schema alias, and then a unique relation-prefix shorthand. It rewrites an alias or shorthand to the canonical key before calling `$$form.renderField(...)`. Exact canonical keys win over colliding aliases; ambiguous aliases or prefixes are reported as `unknownField`. A configured Form Layout is an explicit allow-list: only fields represented by surviving declarations render. Duplicate tracking and tab-path bookkeeping use the canonical key. See [Form Layout Guide](/frontend/form-layout-guide#how-the-resolver-handles-the-declared-tree) for the full DTO authoring rules.
458
458
 
459
459
  Form Layout also supports a leaf `block` node. It wraps an existing resource block descriptor and the Basic renderer invokes it with the inherited `IJsxRenderContextForm`, including the same JSX runtime and CEL scope. The node has no schema property or field value; for example, a filter can place `basic-page:blockFilterActions` inside a flow section while that action block continues to read `$$filter` from the filter-owned form scope.
460
460
 
@@ -151,6 +151,62 @@ This is one of the most important source-level facts for understanding Zova.
151
151
 
152
152
  Zova does not require the business author to write `reactive({ ... })` around controller state, because the framework already treats the controller bean as the reactive object.
153
153
 
154
+ #### Construction-time `this` and the exposed reactive bean
155
+
156
+ There is one lifecycle boundary that matters when a callback closes over `this`:
157
+
158
+ 1. the container first constructs the raw class instance with `new BeanClass(...)`;
159
+ 2. it then exposes the framework-managed reactive/proxied bean; and
160
+ 3. it invokes `__init__()` only after that preparation completes.
161
+
162
+ That distinction is normally invisible when a controller uses ordinary methods and render-time `this.member` access. It matters for a class-field arrow callback that mutates controller state, because the arrow is created during construction and lexically captures the construction-time raw `this`:
163
+
164
+ ```typescript
165
+ class ControllerPageCounter {
166
+ count = 0;
167
+
168
+ // Avoid for a state-mutating controller callback.
169
+ onIncrement = () => {
170
+ this.count++;
171
+ };
172
+ }
173
+ ```
174
+
175
+ The field can change on the raw instance, but the mutation bypasses the reactive/proxied bean through which render dependencies were collected. Vue therefore receives no invalidation notification. A later update to another dependency can rerender the component and reveal the changed underlying value, which can make this look intermittent.
176
+
177
+ Prefer an ordinary controller method for a normal TSX action:
178
+
179
+ ```typescript
180
+ class ControllerPageCounter {
181
+ count = 0;
182
+
183
+ increment() {
184
+ this.count++;
185
+ }
186
+
187
+ protected render() {
188
+ return <button onClick={() => this.increment()}>Increment</button>;
189
+ }
190
+ }
191
+ ```
192
+
193
+ If an external API requires a stable stored callback, create that closure in `__init__()` instead. At that point, its lexical `this` is the framework-exposed reactive/proxied bean:
194
+
195
+ ```typescript
196
+ class ControllerPageCounter {
197
+ count = 0;
198
+ onIncrement: () => void;
199
+
200
+ protected async __init__() {
201
+ this.onIncrement = () => {
202
+ this.count++;
203
+ };
204
+ }
205
+ }
206
+ ```
207
+
208
+ This is not a rule that arrow functions are generally non-reactive. The narrow hazard is a **class-field arrow callback that captures construction-time `this` and mutates bean state**.
209
+
154
210
  ### 4. `$computed()` is an instance-scoped wrapper around Vue `computed(...)`
155
211
 
156
212
  In:
@@ -238,21 +294,21 @@ A practical reading takeaway is:
238
294
 
239
295
  ### 7. Field mutation becomes normal reactive invalidation and rerender
240
296
 
241
- Once the controller bean is reactive and render has read its fields, changes such as:
297
+ Once render has read fields through the framework-exposed reactive/proxied controller, a mutation through that same identity, such as:
242
298
 
243
299
  ```typescript
244
300
  this.count++;
245
301
  ```
246
302
 
247
- behave the way a Vue reader would expect at the reactive-engine level:
303
+ behaves the way a Vue reader would expect at the reactive-engine level:
248
304
 
249
305
  - the field change invalidates dependencies
250
306
  - computed values depending on that field are recomputed
251
307
  - the next render sees the updated values
252
308
 
253
- So the runtime behavior is still recognizably Vue-like.
309
+ The construction-time class-field arrow case described above is the exception: its raw `this` can change the underlying field without triggering the dependency that render collected through the exposed bean.
254
310
 
255
- The architectural surface is what changed.
311
+ So the runtime behavior is still recognizably Vue-like. The architectural surface, including the bean lifecycle and identity boundary, is what changed.
256
312
 
257
313
  ## A compact call-flow sketch
258
314
 
@@ -260,13 +316,13 @@ The architectural surface is what changed.
260
316
  useControllerPage(...)
261
317
  -> _useController(...)
262
318
  -> ctx.bean._newBeanInner(..., markReactive = true)
263
- -> BeanContainer creates controller bean
264
- -> BeanContainer applies reactive(...)
265
- -> controller __init__ wires $computed / $watch helpers
319
+ -> BeanContainer constructs the raw controller bean
320
+ -> BeanContainer exposes the reactive/proxied controller bean
321
+ -> controller __init__ wires $computed / $watch helpers and any stored callbacks
266
322
  -> component render is patched toward controller/render bean
267
323
  -> render-time controller data update refreshes page route data
268
- -> render reads controller fields
269
- -> field mutation invalidates dependencies
324
+ -> render reads fields through the exposed bean
325
+ -> mutation through that same identity invalidates dependencies
270
326
  -> rerender produces updated UI
271
327
  ```
272
328
 
@@ -120,7 +120,8 @@ Use this path when you are asking questions like:
120
120
  Use this path when you are asking questions like:
121
121
 
122
122
  - how does a row schema become visible columns?
123
- - where do `visible`, `order`, `render`, and `columnProps` come from?
123
+ - where do `order`, `align`, `width`, `fixed`, `enableSorting`, and `sortDescFirst` come from?
124
+ - where do `visible`, `render`, and `columnProps` come from?
124
125
  - how does the table scene differ from form or filter scenes?
125
126
 
126
127
  ### Read the docs first
@@ -130,17 +131,51 @@ Use this path when you are asking questions like:
130
131
 
131
132
  ### Then read source in this order
132
133
 
134
+ 1. `zova/packages-cli/cli-set-front/cli/templates/rest/rest.ts`
135
+ 2. `zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/rest.ts`
136
+ 3. `zova/src/suite-vendor/a-zova/modules/a-openapi/src/lib/schema.ts`
137
+ 4. `zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/controller.tsx`
138
+ 5. `zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/render.tsx`
139
+
140
+ ### What each file clarifies
141
+
142
+ - `cli/templates/rest/rest.ts` shows that `ZovaRender.column(...)` writes `ITableColumnOptions` under `rest.table`
143
+ - `types/rest.ts` defines `order`, `align`, `width`, `fixed`, `enableSorting`, and `sortDescFirst`
144
+ - `schema.ts` shows `loadSchemaProperties(...)`, `$ref` resolution, and the merge of shared `rest` metadata with table-scene overlays
145
+ - `controller.tsx` shows `_createProperties()`, `_createTableMeta()`, `_createColumnsMiddle()`, order-schema sorting eligibility, and pinning
146
+ - `render.tsx` shows how effective metadata becomes alignment, dimensions, fixed-column styles, and sortable header DOM
147
+
148
+ ## 4. Column metadata, sortable headers, and resource orders
149
+
150
+ Use this path when you are asking questions like:
151
+
152
+ - why did `ZovaRender.column({ enableSorting: true })` not make a header sortable?
153
+ - how do fixed columns become sticky left/right columns?
154
+ - how does one header click become a resource request `orders` value?
155
+
156
+ ### Read the docs first
157
+
158
+ - [Table Guide](/frontend/table-guide)
159
+ - [Table + Resource CRUD Cookbook](/frontend/table-resource-crud-cookbook)
160
+ - [Existing Resource Field Update](/backend/resource-field-update#filter-and-sort-a-relation-by-its-display-field)
161
+
162
+ ### Then read source in this order
163
+
133
164
  1. `zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/controller.tsx`
134
- 2. `zova/src/suite-vendor/a-zova/modules/a-openapi/src/lib/schema.ts`
135
- 3. `zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/rest.ts`
165
+ 2. `zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/render.tsx`
166
+ 3. `zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockTable/controller.tsx`
167
+ 4. `zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx`
136
168
 
137
169
  ### What each file clarifies
138
170
 
139
- - `controller.tsx` shows `_createProperties()`, `_createTableMeta()`, and `_createColumnsMiddle()`
140
- - `schema.ts` shows `loadSchemaProperties(...)`, `$ref` resolution, and scene-specific `rest` overlays
141
- - `rest.ts` shows the schema extension contract, including `rest.table` and table-related render types
171
+ - `controller.tsx` shows that `enableSorting` also requires the property key or aliases to exist in `schemaOrder`, and shows left/right pinning construction
172
+ - `render.tsx` shows the header toggle, `aria-sort`, indicators, and sticky styles
173
+ - `blockTable/controller.tsx` shows the handoff of `schemaOrder`, controlled sorting, and `onSortingChange`
174
+ - `blockPage/controller.tsx` shows conversion of the single current sort into backend `orders` and query reload
175
+
176
+ The result is a controlled, manual server-sorting path: the standard resource page does not client-side sort rows that have already been fetched. The `basic-page` files are Cabloy Basic specimens; the metadata API and base Zova Table behavior are shared with Cabloy Start, whose page module paths must be resolved in that edition.
142
177
 
143
- ## 4. `tableCell` bean-scene contract and decorator surface
178
+ ## 5. `tableCell` bean-scene contract and decorator surface
144
179
 
145
180
  Use this path when you are asking questions like:
146
181
 
@@ -168,7 +203,7 @@ Use this path when you are asking questions like:
168
203
  - `package.json` shows `zovaModule.onions.tableCell` and boilerplate metadata
169
204
  - the boilerplate files show the intended scaffold shape for normal cells and row-action cells
170
205
 
171
- ## 5. Cell render pipeline and CEL/JSX scope
206
+ ## 6. Cell render pipeline and CEL/JSX scope
172
207
 
173
208
  Use this path when you are asking questions like:
174
209
 
@@ -193,7 +228,7 @@ Use this path when you are asking questions like:
193
228
  - `tableColumn.ts` shows column scope, cell scope, and table column render types
194
229
  - `tableCell.ts` shows the render-context contract received by a `tableCell` bean
195
230
 
196
- ## 6. Representative built-in cell renderers
231
+ ## 7. Representative built-in cell renderers
197
232
 
198
233
  Use this path when you are asking questions like:
199
234
 
@@ -220,7 +255,7 @@ Use this path when you are asking questions like:
220
255
  - `tableCell.select.tsx` shows value-to-item mapping
221
256
  - `actionOperationsRow.tsx` shows advanced visibility checks, nested action rendering, and permission-aware orchestration
222
257
 
223
- ## 7. Custom columns through `getColumns(...)`
258
+ ## 8. Custom columns through `getColumns(...)`
224
259
 
225
260
  Use this path when you are asking questions like:
226
261
 
@@ -243,15 +278,15 @@ Use this path when you are asking questions like:
243
278
 
244
279
  - `types/table.ts` shows `TypeTableGetColumns` and `TypeTableCreateColumnRender`
245
280
  - `controller.tsx` shows how custom columns are given `next(...)`, `createColumnRender(...)`, and the table controller itself
246
- - `basic-table/render.tsx` shows a module-level consumer wrapping `ZTable` rather than replacing its runtime
281
+ - `basic-table/render.tsx` is a Cabloy Basic forwarding wrapper around `ZTable`; it preserves the custom-column hook rather than serving as an executable custom-column specimen
247
282
 
248
- ## 8. Resource-page integration
283
+ ## 9. Resource-page integration
249
284
 
250
285
  Use this path when you are asking questions like:
251
286
 
252
287
  - how does a resource list page feed schema and data into `ZTable`?
253
- - where do `data`, `schemaRow`, and `tableScope` come from?
254
- - where should permission-sensitive table refresh be debugged?
288
+ - where do `data`, `schemaRow`, `schemaOrder`, and `tableScope` come from?
289
+ - where should permission-sensitive table refresh or resource sorting be debugged?
255
290
 
256
291
  ### Read the docs first
257
292
 
@@ -266,11 +301,11 @@ Use this path when you are asking questions like:
266
301
 
267
302
  ### What each file clarifies
268
303
 
269
- - `blockPage/controller.tsx` shows resource ownership, query state, page CEL scope, permissions, and table refresh integration
270
- - `blockTable/controller.tsx` shows the direct bridge from page block to `ZTable`
304
+ - `blockPage/controller.tsx` shows resource ownership, query state, page CEL scope, permissions, table refresh integration, and `orders` mapping
305
+ - `blockTable/controller.tsx` shows the direct bridge from page block to `ZTable`, including order schema and controlled sorting props
271
306
  - the Vona DTO specimen shows the block-based page composition consumed from backend-owned metadata
272
307
 
273
- ## 9. Representative specimens to read before editing the framework
308
+ ## 10. Representative specimens to read before editing the framework
274
309
 
275
310
  Use this section when you want one small example before reading framework internals.
276
311
 
@@ -288,7 +323,7 @@ Use this section when you want one small example before reading framework intern
288
323
 
289
324
  Together they give you the public integration, the advanced cell path, and the minimal cell path before you descend into the whole table runtime.
290
325
 
291
- ## 10. A compact reading strategy
326
+ ## 11. A compact reading strategy
292
327
 
293
328
  When in doubt, use this order:
294
329
 
@@ -301,7 +336,7 @@ When in doubt, use this order:
301
336
 
302
337
  That order usually gets you to the answer faster than starting from the deepest runtime files first.
303
338
 
304
- ## 11. Final takeaway
339
+ ## 12. Final takeaway
305
340
 
306
341
  The fastest way to read Zova Table accurately is not to memorize every file in `a-table`.
307
342