@vc-shell/vc-app-skill 2.0.0-alpha.33 → 2.0.0-alpha.34

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 (195) hide show
  1. package/CHANGELOG.md +78 -54
  2. package/README.md +42 -12
  3. package/package.json +4 -4
  4. package/runtime/VERSION +1 -1
  5. package/runtime/agents/api-analyzer.md +31 -16
  6. package/runtime/agents/blade-enhancer.md +15 -9
  7. package/runtime/agents/details-blade-generator.md +47 -31
  8. package/runtime/agents/list-blade-generator.md +21 -37
  9. package/runtime/agents/locales-generator.md +3 -0
  10. package/runtime/agents/migration-agent.md +18 -7
  11. package/runtime/agents/module-analyzer.md +2 -0
  12. package/runtime/agents/module-assembler.md +15 -0
  13. package/runtime/agents/promote-agent.md +15 -4
  14. package/runtime/agents/type-checker.md +11 -0
  15. package/runtime/knowledge/docs/_BUILD_HASH.md +1 -1
  16. package/runtime/knowledge/docs/core/api/platform.docs.md +30 -30
  17. package/runtime/knowledge/docs/core/blade-navigation/blade-nav-composables.docs.md +41 -41
  18. package/runtime/knowledge/docs/core/composables/bladeContext/index.docs.md +12 -10
  19. package/runtime/knowledge/docs/core/composables/useApiClient/useApiClient.docs.md +11 -14
  20. package/runtime/knowledge/docs/core/composables/useAppBarMobileButtons/useAppBarMobileButtons.docs.md +35 -35
  21. package/runtime/knowledge/docs/core/composables/useAppBarWidget/useAppBarWidget.docs.md +35 -35
  22. package/runtime/knowledge/docs/core/composables/useAppInsights/useAppInsights.docs.md +15 -15
  23. package/runtime/knowledge/docs/core/composables/useAssets/useAssets.docs.md +21 -18
  24. package/runtime/knowledge/docs/core/composables/useAssetsManager/useAssetsManager.docs.md +28 -24
  25. package/runtime/knowledge/docs/core/composables/useAsync/useAsync.docs.md +90 -61
  26. package/runtime/knowledge/docs/core/composables/useBeforeUnload/useBeforeUnload.docs.md +19 -18
  27. package/runtime/knowledge/docs/core/composables/useBlade/useBlade.docs.md +89 -68
  28. package/runtime/knowledge/docs/core/composables/useBladeForm/useBladeForm.docs.md +27 -25
  29. package/runtime/knowledge/docs/core/composables/useBladeRegistry/useBladeRegistry.docs.md +15 -15
  30. package/runtime/knowledge/docs/core/composables/useBladeWidgets/index.docs.md +43 -47
  31. package/runtime/knowledge/docs/core/composables/useBreadcrumbs/useBreadcrumbs.docs.md +11 -11
  32. package/runtime/knowledge/docs/core/composables/useConnectionStatus/useConnectionStatus.docs.md +27 -15
  33. package/runtime/knowledge/docs/core/composables/useDashboard/useDashboard.docs.md +30 -30
  34. package/runtime/knowledge/docs/core/composables/useDynamicProperties/useDynamicProperties.docs.md +34 -36
  35. package/runtime/knowledge/docs/core/composables/useErrorHandler/useErrorHandler.docs.md +44 -23
  36. package/runtime/knowledge/docs/core/composables/useFunctions/useFunctions.docs.md +14 -11
  37. package/runtime/knowledge/docs/core/composables/useKeyboardNavigation/useKeyboardNavigation.docs.md +47 -38
  38. package/runtime/knowledge/docs/core/composables/useLanguages/useLanguages.docs.md +37 -28
  39. package/runtime/knowledge/docs/core/composables/useLoading/useLoading.docs.md +23 -17
  40. package/runtime/knowledge/docs/core/composables/useMenuExpanded/index.docs.md +9 -9
  41. package/runtime/knowledge/docs/core/composables/useMenuService/useMenuService.docs.md +42 -42
  42. package/runtime/knowledge/docs/core/composables/useModificationTracker/useModificationTracker.docs.md +22 -12
  43. package/runtime/knowledge/docs/core/composables/useNotifications/useNotifications.docs.md +33 -41
  44. package/runtime/knowledge/docs/core/composables/usePermissions/usePermissions.docs.md +16 -16
  45. package/runtime/knowledge/docs/core/composables/usePlatformLocaleSync/usePlatformLocaleSync.docs.md +28 -0
  46. package/runtime/knowledge/docs/core/composables/usePopup/usePopup.docs.md +32 -24
  47. package/runtime/knowledge/docs/core/composables/useResponsive/useResponsive.docs.md +32 -11
  48. package/runtime/knowledge/docs/core/composables/useSettings/useSettings.docs.md +22 -13
  49. package/runtime/knowledge/docs/core/composables/useSettingsMenu/useSettingsMenu.docs.md +7 -7
  50. package/runtime/knowledge/docs/core/composables/useSidebarState/useSidebarState.docs.md +32 -24
  51. package/runtime/knowledge/docs/core/composables/useSlowNetworkDetection/useSlowNetworkDetection.docs.md +21 -17
  52. package/runtime/knowledge/docs/core/composables/useTheme/useTheme.docs.md +24 -24
  53. package/runtime/knowledge/docs/core/composables/useToolbar/useToolbar.docs.md +28 -31
  54. package/runtime/knowledge/docs/core/composables/useUser/useUser.docs.md +43 -24
  55. package/runtime/knowledge/docs/core/composables/useUserManagement/useUserManagement.docs.md +68 -48
  56. package/runtime/knowledge/docs/core/composables/useWebVitals/useWebVitals.docs.md +19 -19
  57. package/runtime/knowledge/docs/core/composables/useWidgets/useWidgets.docs.md +42 -47
  58. package/runtime/knowledge/docs/core/directives/autofocus/autofocus.docs.md +10 -4
  59. package/runtime/knowledge/docs/core/directives/loading/loading.docs.md +35 -20
  60. package/runtime/knowledge/docs/core/notifications/notifications.docs.md +36 -35
  61. package/runtime/knowledge/docs/core/plugins/ai-agent/ai-agent.docs.md +38 -38
  62. package/runtime/knowledge/docs/core/plugins/extension-points/extension-points.docs.md +79 -62
  63. package/runtime/knowledge/docs/core/plugins/global-error-handler/global-error-handler.docs.md +10 -10
  64. package/runtime/knowledge/docs/core/plugins/i18n/i18n.docs.md +21 -23
  65. package/runtime/knowledge/docs/core/plugins/modularity/modularity.docs.md +91 -83
  66. package/runtime/knowledge/docs/core/plugins/permissions/permissions.docs.md +10 -16
  67. package/runtime/knowledge/docs/core/plugins/signalR/signalR.docs.md +9 -9
  68. package/runtime/knowledge/docs/core/plugins/validation/validation.docs.md +65 -22
  69. package/runtime/knowledge/docs/core/services/services.docs.md +19 -22
  70. package/runtime/knowledge/docs/core/types/types.docs.md +40 -40
  71. package/runtime/knowledge/docs/core/utilities/date/date-utilities.docs.md +27 -27
  72. package/runtime/knowledge/docs/core/utilities/shared-utilities.docs.md +23 -23
  73. package/runtime/knowledge/docs/core/utilities/thumbnail/thumbnail.docs.md +22 -25
  74. package/runtime/knowledge/docs/core/utilities/utilities.docs.md +64 -64
  75. package/runtime/knowledge/docs/injection-keys.docs.md +52 -51
  76. package/runtime/knowledge/docs/modules/assets-manager/assets-manager.docs.md +9 -9
  77. package/runtime/knowledge/docs/shell/_internal/popup/common/popup-common.docs.md +23 -43
  78. package/runtime/knowledge/docs/shell/auth/ChangePasswordPage/change-password-page.docs.md +5 -5
  79. package/runtime/knowledge/docs/shell/auth/ForgotPasswordPage/forgot-password-page.docs.md +5 -5
  80. package/runtime/knowledge/docs/shell/auth/InvitePage/invite-page.docs.md +8 -7
  81. package/runtime/knowledge/docs/shell/auth/LoginPage/login-page.docs.md +7 -7
  82. package/runtime/knowledge/docs/shell/auth/ResetPasswordPage/reset-password-page.docs.md +8 -7
  83. package/runtime/knowledge/docs/shell/auth/sign-in/sign-in.docs.md +29 -13
  84. package/runtime/knowledge/docs/shell/components/change-password/change-password.docs.md +13 -16
  85. package/runtime/knowledge/docs/shell/components/change-password-button/change-password-button.docs.md +1 -7
  86. package/runtime/knowledge/docs/shell/components/error-interceptor/error-interceptor.docs.md +5 -5
  87. package/runtime/knowledge/docs/shell/components/language-selector/language-selector.docs.md +1 -1
  88. package/runtime/knowledge/docs/shell/components/logout-button/logout-button.docs.md +1 -1
  89. package/runtime/knowledge/docs/shell/components/notification-template/notification-template.docs.md +17 -9
  90. package/runtime/knowledge/docs/shell/components/settings-menu/settings-menu.docs.md +11 -17
  91. package/runtime/knowledge/docs/shell/components/settings-menu-item/settings-menu-item.docs.md +34 -65
  92. package/runtime/knowledge/docs/shell/components/sidebar/sidebar.docs.md +16 -26
  93. package/runtime/knowledge/docs/shell/components/theme-selector/theme-selector.docs.md +2 -2
  94. package/runtime/knowledge/docs/shell/components/user-dropdown-button/user-dropdown-button.docs.md +7 -9
  95. package/runtime/knowledge/docs/shell/dashboard/dashboard-charts/dashboard-charts.docs.md +30 -40
  96. package/runtime/knowledge/docs/shell/dashboard/dashboard-widget-card/dashboard-widget-card.docs.md +26 -19
  97. package/runtime/knowledge/docs/shell/dashboard/draggable-dashboard/draggable-dashboard.docs.md +15 -12
  98. package/runtime/knowledge/docs/ui/components/atoms/vc-badge/vc-badge.docs.md +15 -26
  99. package/runtime/knowledge/docs/ui/components/atoms/vc-banner/vc-banner.docs.md +21 -19
  100. package/runtime/knowledge/docs/ui/components/atoms/vc-button/vc-button.docs.md +83 -67
  101. package/runtime/knowledge/docs/ui/components/atoms/vc-card/vc-card.docs.md +100 -59
  102. package/runtime/knowledge/docs/ui/components/atoms/vc-col/vc-col.docs.md +28 -11
  103. package/runtime/knowledge/docs/ui/components/atoms/vc-container/vc-container.docs.md +20 -17
  104. package/runtime/knowledge/docs/ui/components/atoms/vc-hint/vc-hint.docs.md +26 -17
  105. package/runtime/knowledge/docs/ui/components/atoms/vc-icon/vc-icon.docs.md +30 -32
  106. package/runtime/knowledge/docs/ui/components/atoms/vc-image/vc-image.docs.md +25 -48
  107. package/runtime/knowledge/docs/ui/components/atoms/vc-label/vc-label.docs.md +29 -24
  108. package/runtime/knowledge/docs/ui/components/atoms/vc-link/vc-link.docs.md +23 -15
  109. package/runtime/knowledge/docs/ui/components/atoms/vc-loading/vc-loading.docs.md +22 -13
  110. package/runtime/knowledge/docs/ui/components/atoms/vc-progress/vc-progress.docs.md +33 -18
  111. package/runtime/knowledge/docs/ui/components/atoms/vc-row/vc-row.docs.md +56 -15
  112. package/runtime/knowledge/docs/ui/components/atoms/vc-scrollable-container/vc-scrollable-container.docs.md +28 -15
  113. package/runtime/knowledge/docs/ui/components/atoms/vc-skeleton/vc-skeleton.docs.md +40 -20
  114. package/runtime/knowledge/docs/ui/components/atoms/vc-status/vc-status.docs.md +25 -14
  115. package/runtime/knowledge/docs/ui/components/atoms/vc-status-icon/vc-status-icon.docs.md +40 -14
  116. package/runtime/knowledge/docs/ui/components/atoms/vc-tooltip/vc-tooltip.docs.md +54 -42
  117. package/runtime/knowledge/docs/ui/components/atoms/vc-video/vc-video.docs.md +17 -17
  118. package/runtime/knowledge/docs/ui/components/atoms/vc-widget/vc-widget.docs.md +21 -21
  119. package/runtime/knowledge/docs/ui/components/molecules/multilanguage-selector/multilanguage-selector.docs.md +23 -10
  120. package/runtime/knowledge/docs/ui/components/molecules/vc-accordion/vc-accordion.docs.md +55 -44
  121. package/runtime/knowledge/docs/ui/components/molecules/vc-breadcrumbs/vc-breadcrumbs.docs.md +23 -20
  122. package/runtime/knowledge/docs/ui/components/molecules/vc-checkbox/vc-checkbox.docs.md +92 -65
  123. package/runtime/knowledge/docs/ui/components/molecules/vc-checkbox-group/vc-checkbox-group.docs.md +22 -36
  124. package/runtime/knowledge/docs/ui/components/molecules/vc-color-input/vc-color-input.docs.md +65 -23
  125. package/runtime/knowledge/docs/ui/components/molecules/vc-date-picker/vc-date-picker.docs.md +52 -73
  126. package/runtime/knowledge/docs/ui/components/molecules/vc-dropdown/vc-dropdown.docs.md +91 -85
  127. package/runtime/knowledge/docs/ui/components/molecules/vc-dropdown-panel/vc-dropdown-panel.docs.md +38 -42
  128. package/runtime/knowledge/docs/ui/components/molecules/vc-editor/vc-editor.docs.md +56 -73
  129. package/runtime/knowledge/docs/ui/components/molecules/vc-field/vc-field.docs.md +61 -27
  130. package/runtime/knowledge/docs/ui/components/molecules/vc-file-upload/vc-file-upload.docs.md +42 -50
  131. package/runtime/knowledge/docs/ui/components/molecules/vc-form/vc-form.docs.md +35 -64
  132. package/runtime/knowledge/docs/ui/components/molecules/vc-image-tile/vc-image-tile.docs.md +38 -41
  133. package/runtime/knowledge/docs/ui/components/molecules/vc-input/vc-input.docs.md +109 -131
  134. package/runtime/knowledge/docs/ui/components/molecules/vc-input-currency/vc-input-currency.docs.md +47 -88
  135. package/runtime/knowledge/docs/ui/components/molecules/vc-input-dropdown/vc-input-dropdown.docs.md +50 -64
  136. package/runtime/knowledge/docs/ui/components/molecules/vc-input-group/vc-input-group.docs.md +29 -24
  137. package/runtime/knowledge/docs/ui/components/molecules/vc-menu/vc-menu.docs.md +32 -28
  138. package/runtime/knowledge/docs/ui/components/molecules/vc-multivalue/vc-multivalue.docs.md +57 -65
  139. package/runtime/knowledge/docs/ui/components/molecules/vc-pagination/vc-pagination.docs.md +28 -26
  140. package/runtime/knowledge/docs/ui/components/molecules/vc-radio-button/vc-radio-button.docs.md +55 -20
  141. package/runtime/knowledge/docs/ui/components/molecules/vc-radio-group/vc-radio-group.docs.md +21 -35
  142. package/runtime/knowledge/docs/ui/components/molecules/vc-rating/vc-rating.docs.md +38 -33
  143. package/runtime/knowledge/docs/ui/components/molecules/vc-select/vc-select.docs.md +72 -83
  144. package/runtime/knowledge/docs/ui/components/molecules/vc-slider/vc-slider.docs.md +21 -16
  145. package/runtime/knowledge/docs/ui/components/molecules/vc-switch/vc-switch.docs.md +55 -64
  146. package/runtime/knowledge/docs/ui/components/molecules/vc-textarea/vc-textarea.docs.md +51 -70
  147. package/runtime/knowledge/docs/ui/components/molecules/vc-toast/vc-toast.docs.md +58 -57
  148. package/runtime/knowledge/docs/ui/components/organisms/vc-app/vc-app.docs.md +49 -26
  149. package/runtime/knowledge/docs/ui/components/organisms/vc-auth-layout/vc-auth-layout.docs.md +82 -28
  150. package/runtime/knowledge/docs/ui/components/organisms/vc-blade/vc-blade.docs.md +90 -75
  151. package/runtime/knowledge/docs/ui/components/organisms/vc-data-table/composables/table-composables.docs.md +99 -48
  152. package/runtime/knowledge/docs/ui/components/organisms/vc-data-table/vc-data-table.docs.md +548 -367
  153. package/runtime/knowledge/docs/ui/components/organisms/vc-dynamic-property/vc-dynamic-property.docs.md +35 -52
  154. package/runtime/knowledge/docs/ui/components/organisms/vc-gallery/vc-gallery.docs.md +33 -62
  155. package/runtime/knowledge/docs/ui/components/organisms/vc-image-upload/vc-image-upload.docs.md +17 -23
  156. package/runtime/knowledge/docs/ui/components/organisms/vc-popup/vc-popup.docs.md +109 -68
  157. package/runtime/knowledge/docs/ui/components/organisms/vc-sidebar/vc-sidebar.docs.md +82 -44
  158. package/runtime/knowledge/docs/ui/composables/ui-composables.docs.md +8 -8
  159. package/runtime/knowledge/docs/ui/composables/useDataTablePagination.docs.md +164 -0
  160. package/runtime/knowledge/docs/ui/composables/useDataTableSort.docs.md +34 -26
  161. package/runtime/knowledge/docs/ui/composables/useTableSelection.docs.md +48 -40
  162. package/runtime/knowledge/docs/ui/composables/useTableSort.docs.md +30 -17
  163. package/runtime/knowledge/docs/ui/types/ui-types.docs.md +40 -29
  164. package/runtime/knowledge/examples/offers-module.md +15 -13
  165. package/runtime/knowledge/examples/team-module.md +82 -119
  166. package/runtime/knowledge/examples/videos-module.md +44 -17
  167. package/runtime/knowledge/index.md +22 -0
  168. package/runtime/knowledge/migration-prompts/blade-form-migration.md +17 -8
  169. package/runtime/knowledge/migration-prompts/blade-props-migration.md +1 -2
  170. package/runtime/knowledge/migration-prompts/datatable-migration.md +801 -0
  171. package/runtime/knowledge/migration-prompts/icon-migration.md +97 -0
  172. package/runtime/knowledge/migration-prompts/manual-migration-audit.md +117 -0
  173. package/runtime/knowledge/migration-prompts/notifications-migration.md +8 -3
  174. package/runtime/knowledge/migration-prompts/nswag-migration.md +25 -29
  175. package/runtime/knowledge/migration-prompts/use-assets-migration.md +164 -0
  176. package/runtime/knowledge/migration-prompts/use-data-table-pagination-migration.md +176 -0
  177. package/runtime/knowledge/migration-prompts/widgets-migration.md +48 -27
  178. package/runtime/knowledge/patterns/assets-management.md +20 -20
  179. package/runtime/knowledge/patterns/blade-navigation.md +7 -14
  180. package/runtime/knowledge/patterns/blade-widget.md +19 -17
  181. package/runtime/knowledge/patterns/child-blade-flow.md +19 -7
  182. package/runtime/knowledge/patterns/composable-details.md +20 -50
  183. package/runtime/knowledge/patterns/composable-list.md +43 -31
  184. package/runtime/knowledge/patterns/dashboard-widget.md +14 -16
  185. package/runtime/knowledge/patterns/datatable-pattern.md +521 -0
  186. package/runtime/knowledge/patterns/details-blade-pattern.md +78 -116
  187. package/runtime/knowledge/patterns/extension-points-usage.md +53 -44
  188. package/runtime/knowledge/patterns/form-validation.md +28 -64
  189. package/runtime/knowledge/patterns/list-blade-pattern.md +33 -21
  190. package/runtime/knowledge/patterns/module-structure.md +7 -1
  191. package/runtime/knowledge/patterns/multilanguage-fields.md +8 -14
  192. package/runtime/knowledge/patterns/notification-template.md +21 -14
  193. package/runtime/knowledge/patterns/signalr-notifications.md +30 -32
  194. package/runtime/knowledge/patterns/toolbar-pattern.md +18 -20
  195. package/runtime/vc-app.md +241 -62
@@ -0,0 +1,801 @@
1
+ ---
2
+ name: datatable-migration
3
+ description: AI transformation rules for VcTable → VcDataTable migration.
4
+ ---
5
+
6
+ # DataTable Migration: VcTable → VcDataTable
7
+
8
+ Replace the imperative `VcTable` component (columns array, manual sort handlers, filter slots) with the declarative `VcDataTable` component. The new API uses `VcColumn` child components, declarative `v-model` bindings for sort and selection, and a `global-filters` prop instead of a `#filters` template slot.
9
+
10
+ ## RULE 1: Component Swap and Props Rewrite
11
+
12
+ Full prop/event mapping:
13
+
14
+ | VcTable prop/event | VcDataTable equivalent |
15
+ | -------------------------- | ------------------------------------------------ |
16
+ | `:columns="columns"` | `<VcColumn>` child components (see RULE 2) |
17
+ | `:selected-item-id="id"` | `v-model:active-item-id="id"` |
18
+ | `:sort="sortExpression"` | `v-model:sort-field` + `v-model:sort-order` |
19
+ | `:multiselect="true"` | `:selection-mode="'multiple'"` |
20
+ | `@selection-changed="fn"` | `v-model:selection="ref"` |
21
+ | `:search-value="val"` | `:searchable="true"` (internal state) |
22
+ | `@search:change="fn"` | `@search="fn"` |
23
+ | `@item-click="fn"` | `@row-click="fn"` (different signature) |
24
+ | `@header-click="fn"` | Remove (sort is declarative via v-model) |
25
+ | `@scroll:ptr="fn"` | `:pull-to-refresh="true"` + `@pull-refresh="fn"` |
26
+ | `:pages` + `:current-page` | `:pagination="{ currentPage, pages }"` |
27
+ | `:active-filter-count` | Remove (managed by global-filters internally) |
28
+ | `<!--@vue-generic {T}-->` | Remove (no longer needed) |
29
+
30
+ **BEFORE:**
31
+
32
+ ```vue
33
+ <template>
34
+ <!--@vue-generic {OrderItem}-->
35
+ <VcTable
36
+ :columns="columns"
37
+ :items="items"
38
+ :loading="loading"
39
+ :total-count="totalCount"
40
+ :pages="pages"
41
+ :current-page="currentPage"
42
+ :search-value="searchValue"
43
+ :selected-item-id="selectedItemId"
44
+ :sort="sortExpression"
45
+ :multiselect="true"
46
+ :active-filter-count="activeFilterCount"
47
+ @item-click="onItemClick"
48
+ @header-click="onHeaderClick"
49
+ @selection-changed="onSelectionChanged"
50
+ @search:change="onSearchChange"
51
+ @scroll:ptr="onPullToRefresh"
52
+ @pagination-click="onPageChanged"
53
+ >
54
+ <template #item_name="{ item }">
55
+ <span class="font-bold">{{ item.name }}</span>
56
+ </template>
57
+ <template #item_status="{ item }">
58
+ <StatusBadge :status="item.status" />
59
+ </template>
60
+ <template #filters>
61
+ <!-- filter checkboxes -->
62
+ </template>
63
+ <template #empty>
64
+ <EmptyState @add="onAdd" />
65
+ </template>
66
+ <template #notfound>
67
+ <NotFound @reset="onReset" />
68
+ </template>
69
+ </VcTable>
70
+ </template>
71
+ ```
72
+
73
+ **AFTER:**
74
+
75
+ ```vue
76
+ <template>
77
+ <VcDataTable
78
+ :items="items"
79
+ :loading="loading"
80
+ :total-count="totalCount"
81
+ :pagination="{ currentPage, pages }"
82
+ :searchable="true"
83
+ :selection-mode="'multiple'"
84
+ :pull-to-refresh="true"
85
+ :global-filters="computedGlobalFilters"
86
+ :empty-state="{
87
+ icon: 'lucide-package-open',
88
+ title: $t('MY_MODULE.PAGES.LIST.EMPTY'),
89
+ actionLabel: $t('MY_MODULE.PAGES.LIST.ADD'),
90
+ actionHandler: onAdd,
91
+ }"
92
+ v-model:active-item-id="selectedItemId"
93
+ v-model:sort-field="sortField"
94
+ v-model:sort-order="sortOrder"
95
+ v-model:selection="localSelection"
96
+ @row-click="onItemClick"
97
+ @search="onSearchChange"
98
+ @pull-refresh="onPullToRefresh"
99
+ @pagination-click="onPageChanged"
100
+ @filter="onFilter"
101
+ >
102
+ <VcColumn
103
+ id="name"
104
+ :title="$t('MY_MODULE.PAGES.LIST.TABLE.NAME')"
105
+ :sortable="true"
106
+ :always-visible="true"
107
+ field="name"
108
+ >
109
+ <template #body="{ data }">
110
+ <span class="font-bold">{{ data.name }}</span>
111
+ </template>
112
+ </VcColumn>
113
+ <VcColumn
114
+ id="status"
115
+ :title="$t('MY_MODULE.PAGES.LIST.TABLE.STATUS')"
116
+ type="status"
117
+ field="status"
118
+ >
119
+ <template #body="{ data }">
120
+ <StatusBadge :status="data.status" />
121
+ </template>
122
+ </VcColumn>
123
+ </VcDataTable>
124
+ </template>
125
+ ```
126
+
127
+ Key points:
128
+
129
+ - `<!--@vue-generic {T}-->` comment is removed entirely.
130
+ - `@header-click` is removed — sort is now declarative via `v-model:sort-field` and `v-model:sort-order`.
131
+ - `:active-filter-count` is removed — the `global-filters` prop manages filter state and badge count internally.
132
+ - `#filters`, `#empty`, and `#notfound` template slots are removed in favor of props (see RULE 6 and RULE 7).
133
+
134
+ ## RULE 2: Columns Array → VcColumn Children
135
+
136
+ Column definition moves from a JavaScript array to `<VcColumn>` child components in the template.
137
+
138
+ Prop mapping:
139
+
140
+ | columns array key | VcColumn prop |
141
+ | ------------------------------------------------------------- | ----------------- |
142
+ | `id` | `id` |
143
+ | `title` | `:title` |
144
+ | `sortable` | `:sortable` |
145
+ | `alwaysVisible` | `:always-visible` |
146
+ | `type` | `type` |
147
+ | `width` | `width` |
148
+ | `visible` | `:visible` |
149
+ | `field` | `:field` |
150
+ | `mobilePosition` (value: `"top-left"`, `"bottom-left"`, etc.) | `mobile-position` |
151
+ | `mobilePosition` (value: `"image"`, `"status"`) | `mobile-role` |
152
+
153
+ **BEFORE:**
154
+
155
+ ```typescript
156
+ const columns = ref<ITableColumns[]>([
157
+ {
158
+ id: "imgSrc",
159
+ title: computed(() => t("MY_MODULE.PAGES.LIST.TABLE.IMAGE")),
160
+ width: 60,
161
+ alwaysVisible: true,
162
+ mobilePosition: "image",
163
+ },
164
+ {
165
+ id: "name",
166
+ title: computed(() => t("MY_MODULE.PAGES.LIST.TABLE.NAME")),
167
+ sortable: true,
168
+ alwaysVisible: true,
169
+ mobilePosition: "top-left",
170
+ },
171
+ {
172
+ id: "status",
173
+ title: computed(() => t("MY_MODULE.PAGES.LIST.TABLE.STATUS")),
174
+ type: "status",
175
+ width: 120,
176
+ mobilePosition: "status",
177
+ },
178
+ {
179
+ id: "createdDate",
180
+ title: computed(() => t("MY_MODULE.PAGES.LIST.TABLE.CREATED")),
181
+ sortable: true,
182
+ width: 160,
183
+ type: "date-ago",
184
+ mobilePosition: "bottom-right",
185
+ },
186
+ {
187
+ id: "modifiedDate",
188
+ title: computed(() => t("MY_MODULE.PAGES.LIST.TABLE.MODIFIED")),
189
+ sortable: true,
190
+ width: 160,
191
+ type: "date-ago",
192
+ visible: false,
193
+ },
194
+ ]);
195
+ ```
196
+
197
+ **AFTER:**
198
+
199
+ ```vue
200
+ <VcDataTable :items="items" :loading="loading" :total-count="totalCount">
201
+ <VcColumn
202
+ id="imgSrc"
203
+ :title="$t('MY_MODULE.PAGES.LIST.TABLE.IMAGE')"
204
+ width="60"
205
+ :always-visible="true"
206
+ mobile-role="image"
207
+ />
208
+ <VcColumn
209
+ id="name"
210
+ :title="$t('MY_MODULE.PAGES.LIST.TABLE.NAME')"
211
+ :sortable="true"
212
+ :always-visible="true"
213
+ mobile-position="top-left"
214
+ field="name"
215
+ />
216
+ <VcColumn
217
+ id="status"
218
+ :title="$t('MY_MODULE.PAGES.LIST.TABLE.STATUS')"
219
+ type="status"
220
+ width="120"
221
+ mobile-role="status"
222
+ field="status"
223
+ />
224
+ <VcColumn
225
+ id="createdDate"
226
+ :title="$t('MY_MODULE.PAGES.LIST.TABLE.CREATED')"
227
+ :sortable="true"
228
+ width="160"
229
+ type="date-ago"
230
+ mobile-position="bottom-right"
231
+ field="createdDate"
232
+ />
233
+ <VcColumn
234
+ id="modifiedDate"
235
+ :title="$t('MY_MODULE.PAGES.LIST.TABLE.MODIFIED')"
236
+ :sortable="true"
237
+ width="160"
238
+ type="date-ago"
239
+ :visible="false"
240
+ field="modifiedDate"
241
+ />
242
+ </VcDataTable>
243
+ ```
244
+
245
+ Custom slot transformation — the slot name changes from `#item_{columnId}` to `#body` inside the `<VcColumn>`, and `item` becomes `data`:
246
+
247
+ ```vue
248
+ <!-- BEFORE -->
249
+ <VcTable :columns="columns" :items="items">
250
+ <template #item_name="{ item }">
251
+ <MyRenderer :item="item" />
252
+ </template>
253
+ </VcTable>
254
+
255
+ <!-- AFTER -->
256
+ <VcDataTable :items="items">
257
+ <VcColumn id="name" :title="$t('MY_MODULE.PAGES.LIST.TABLE.NAME')">
258
+ <template #body="{ data }">
259
+ <MyRenderer :item="data" />
260
+ </template>
261
+ </VcColumn>
262
+ </VcDataTable>
263
+ ```
264
+
265
+ Key points:
266
+
267
+ - Remove the entire `columns` ref/reactive from the script section.
268
+ - Remove the `ITableColumns` import if it was only used for the columns array.
269
+ - The `mobilePosition` value `"image"` or `"status"` maps to `mobile-role`, not `mobile-position`. All other position values (`"top-left"`, `"bottom-left"`, `"top-right"`, `"bottom-right"`) map to `mobile-position`.
270
+ - The `field` prop tells VcColumn which property of the row object to display by default. If you have a custom `#body` slot, `field` is optional but still recommended for sort/filter binding.
271
+ - **VcDataTable does NOT have a `:columns` prop.** Never pass columns as a prop — always use `<VcColumn>` children.
272
+
273
+ ## RULE 2b: Dynamic Columns (received via props)
274
+
275
+ When a reusable component receives columns as a prop (e.g., `columns: ITableColumns[]`), use `v-for` on `<VcColumn>`:
276
+
277
+ **BEFORE:**
278
+
279
+ ```vue
280
+ <VcTable :columns="columns" :items="items" />
281
+ ```
282
+
283
+ ```typescript
284
+ export interface Props {
285
+ columns: ITableColumns[];
286
+ items: Item[];
287
+ }
288
+ ```
289
+
290
+ **AFTER:**
291
+
292
+ ```vue
293
+ <VcDataTable :items="items">
294
+ <VcColumn
295
+ v-for="col in columns"
296
+ :key="col.id"
297
+ :id="col.id"
298
+ :title="col.title"
299
+ :sortable="col.sortable"
300
+ :always-visible="col.alwaysVisible"
301
+ :type="col.type"
302
+ :width="col.width"
303
+ :visible="col.visible"
304
+ :field="col.field"
305
+ />
306
+ </VcDataTable>
307
+ ```
308
+
309
+ The Props interface keeps `columns` — the parent still passes column definitions. The difference is that they are rendered as `<VcColumn>` children via `v-for`, not as a `:columns` prop.
310
+
311
+ ## RULE 3: Sort Composable Replacement
312
+
313
+ Replace `useTableSort` with `useDataTableSort`. The new composable returns `sortField` and `sortOrder` as separate refs that bind directly to `v-model:sort-field` and `v-model:sort-order`. Remove the `onHeaderClick` handler entirely.
314
+
315
+ **BEFORE:**
316
+
317
+ ```typescript
318
+ import { useTableSort } from "@vc-shell/framework";
319
+ import type { ITableColumns } from "@vc-shell/framework";
320
+
321
+ const { sortExpression, handleSortChange } = useTableSort({
322
+ initialProperty: "createdDate",
323
+ initialDirection: "DESC",
324
+ });
325
+
326
+ function onHeaderClick(item: ITableColumns) {
327
+ handleSortChange(item.id);
328
+ }
329
+
330
+ // In the watcher that triggers data reload:
331
+ watch(sortExpression, async () => {
332
+ await load({ sort: sortExpression.value, skip: 0 });
333
+ });
334
+ ```
335
+
336
+ **AFTER:**
337
+
338
+ ```typescript
339
+ import { useDataTableSort } from "@vc-shell/framework";
340
+
341
+ const { sortField, sortOrder, sortExpression } = useDataTableSort({
342
+ initialField: "createdDate",
343
+ initialDirection: "DESC",
344
+ });
345
+
346
+ // Remove onHeaderClick entirely — sort is declarative via v-model.
347
+ // The watcher remains the same, watching the computed sortExpression:
348
+ watch(sortExpression, async () => {
349
+ await load({ sort: sortExpression.value, skip: 0 });
350
+ });
351
+ ```
352
+
353
+ Key points:
354
+
355
+ - `initialProperty` is renamed to `initialField`.
356
+ - `handleSortChange` is removed — the `v-model:sort-field` and `v-model:sort-order` bindings on VcDataTable handle sort changes automatically.
357
+ - `sortExpression` is still available as a computed string (e.g., `"createdDate:DESC"`) for the API call watcher.
358
+ - Remove `onHeaderClick` and its `@header-click` binding from the template.
359
+ - Remove the `ITableColumns` type import if it was only used for the `onHeaderClick` parameter.
360
+
361
+ ## RULE 4: Selection Binding
362
+
363
+ Replace the `@selection-changed` event handler with a `v-model:selection` binding. The selection ref holds the full item objects, not just IDs.
364
+
365
+ **BEFORE:**
366
+
367
+ ```typescript
368
+ const selectedIds = ref<string[]>([]);
369
+
370
+ function onSelectionChanged(items: OrderItem[]) {
371
+ selectedIds.value = items.map((i) => i.id!);
372
+ }
373
+ ```
374
+
375
+ ```vue
376
+ <VcTable :multiselect="true" @selection-changed="onSelectionChanged" />
377
+ ```
378
+
379
+ **AFTER:**
380
+
381
+ ```typescript
382
+ const localSelection = ref<OrderItem[]>([]);
383
+
384
+ // If you need IDs for an API call, derive them with a watcher or computed:
385
+ const selectedIds = computed(() => localSelection.value.map((i) => i.id!));
386
+
387
+ // Or use a watcher if you need side-effects:
388
+ watch(
389
+ localSelection,
390
+ (newSelection) => {
391
+ // e.g., enable/disable toolbar buttons based on selection
392
+ hasSelection.value = newSelection.length > 0;
393
+ },
394
+ { deep: true },
395
+ );
396
+ ```
397
+
398
+ ```vue
399
+ <VcDataTable :selection-mode="'multiple'" v-model:selection="localSelection" />
400
+ ```
401
+
402
+ Key points:
403
+
404
+ - The `localSelection` ref holds full item objects (typed to your entity), not string IDs.
405
+ - Remove the `onSelectionChanged` function entirely.
406
+ - `:multiselect="true"` becomes `:selection-mode="'multiple'"`.
407
+ - If you only need a boolean "has selection" check, derive it from `localSelection.value.length > 0`.
408
+
409
+ ## RULE 5: Row Click Event Signature
410
+
411
+ The `@row-click` event wraps the item in an `{ data }` object, unlike `@item-click` which passed the item directly.
412
+
413
+ **BEFORE:**
414
+
415
+ ```typescript
416
+ function onItemClick(item: OrderItem) {
417
+ openBlade({
418
+ name: "OrderDetails",
419
+ param: item.id,
420
+ options: { orderId: item.id },
421
+ });
422
+ }
423
+ ```
424
+
425
+ ```vue
426
+ <VcTable @item-click="onItemClick" />
427
+ ```
428
+
429
+ **AFTER:**
430
+
431
+ ```typescript
432
+ function onItemClick(event: { data: OrderItem }) {
433
+ const item = event.data;
434
+ openBlade({
435
+ name: "OrderDetails",
436
+ param: item.id,
437
+ options: { orderId: item.id },
438
+ });
439
+ }
440
+ ```
441
+
442
+ ```vue
443
+ <VcDataTable @row-click="onItemClick" />
444
+ ```
445
+
446
+ Key points:
447
+
448
+ - The parameter changes from `item: T` to `event: { data: T }`.
449
+ - Destructure `event.data` at the top of the handler to minimize changes in the rest of the function body.
450
+ - If the handler was a simple one-liner, you can also inline the destructure: `(event: { data: OrderItem }) => openBlade({ name: "OrderDetails", param: event.data.id })`.
451
+
452
+ ## RULE 6: Empty State and Not-Found
453
+
454
+ Replace `#empty` and `#notfound` template slots with the `:empty-state` prop.
455
+
456
+ **BEFORE:**
457
+
458
+ ```vue
459
+ <VcTable :columns="columns" :items="items">
460
+ <template #empty>
461
+ <div class="empty-state">
462
+ <VcIcon icon="lucide-package-open" size="xl" />
463
+ <p>{{ $t("MY_MODULE.PAGES.LIST.EMPTY_TITLE") }}</p>
464
+ <VcButton @click="onAdd">
465
+ {{ $t("MY_MODULE.PAGES.LIST.ADD_BUTTON") }}
466
+ </VcButton>
467
+ </div>
468
+ </template>
469
+ <template #notfound>
470
+ <div class="not-found-state">
471
+ <VcIcon icon="lucide-search-x" size="xl" />
472
+ <p>{{ $t("MY_MODULE.PAGES.LIST.NOT_FOUND") }}</p>
473
+ <VcButton @click="onResetSearch">
474
+ {{ $t("MY_MODULE.PAGES.LIST.RESET_SEARCH") }}
475
+ </VcButton>
476
+ </div>
477
+ </template>
478
+ </VcTable>
479
+ ```
480
+
481
+ **AFTER:**
482
+
483
+ ```vue
484
+ <VcDataTable
485
+ :items="items"
486
+ :empty-state="{
487
+ icon: 'lucide-package-open',
488
+ title: $t('MY_MODULE.PAGES.LIST.EMPTY_TITLE'),
489
+ actionLabel: $t('MY_MODULE.PAGES.LIST.ADD_BUTTON'),
490
+ actionHandler: onAdd,
491
+ }"
492
+ :not-found-state="{
493
+ icon: 'lucide-search-x',
494
+ title: $t('MY_MODULE.PAGES.LIST.NOT_FOUND'),
495
+ actionLabel: $t('MY_MODULE.PAGES.LIST.RESET_SEARCH'),
496
+ actionHandler: onResetSearch,
497
+ }"
498
+ />
499
+ ```
500
+
501
+ Key points:
502
+
503
+ - Remove `#empty` and `#notfound` template slots entirely.
504
+ - Remove dedicated empty/not-found `.vue` components if they only rendered an icon, title, and action button.
505
+ - `:empty-state` shows when there are no items at all (no search active). Props: `icon`, `title`, `actionLabel`, `actionHandler`.
506
+ - `:not-found-state` shows when a search is active but returned no results. Same props as `:empty-state`. If omitted, VcDataTable uses a default not-found message.
507
+ - Both are **separate props** — do NOT combine them into one.
508
+
509
+ ## RULE 7: Filters — Template Slot → global-filters Prop
510
+
511
+ This is the most complex transformation. Replace the `#filters` template slot (with staged filter state, apply/reset buttons, and checkbox loops) with a declarative `global-filters` prop and `@filter` event.
512
+
513
+ **DO NOT SKIP THIS RULE even if filter types look exotic.** Real apps often have date-range pickers, multi-select dropdowns, and enum selects alongside checkboxes. Each of these maps to a `GlobalFilterConfig` entry with an appropriate `filter` shape and `options`. If a single filter type does not fit any documented shape, map the other filters and flag the one holdout in your report — **do NOT delete the whole filter panel.** "Removed to get the build green" is not an acceptable resolution: the user loses UX. If in doubt, keep the existing `<template #filters>` slot unchanged and mark the file as needing manual migration in the report, rather than erasing user-visible functionality.
514
+
515
+ ### Filter shapes cheat-sheet
516
+
517
+ Use these exact shapes in `GlobalFilterConfig[]` (annotate the `computed<GlobalFilterConfig[]>(...)` to satisfy strict typing):
518
+
519
+ ```ts
520
+ // Single-select (checkbox group → dropdown)
521
+ { id: "type", label: t("..."), filter: { options: [{ value, label }, ...] } }
522
+
523
+ // Multi-select — same shape + multiple: true
524
+ { id: "tags", label: t("..."), filter: { options: [...], multiple: true } }
525
+
526
+ // Date range — TWO output keys in event.filters (not a nested object)
527
+ { id: "dueDate", label: t("..."), filter: { range: ["startDate", "endDate"] } }
528
+
529
+ // Free-text (column search already covers this, but for completeness)
530
+ { id: "keyword", label: t("..."), filter: true }
531
+ ```
532
+
533
+ ### `@filter` handler — no manual Date→string coercion
534
+
535
+ VcDataTable's `GlobalFiltersPanel` internally normalises date-picker values to `"YYYY-MM-DD"` (local components, no TZ shift). Handler receives `event.filters: Record<string, unknown>` with string values:
536
+
537
+ ```ts
538
+ async function onFilter(event: { filters: Record<string, unknown> }) {
539
+ currentFilters.value = {
540
+ type: event.filters.type as string | undefined,
541
+ priority: event.filters.priority as string | undefined,
542
+ startDate: event.filters.startDate as string | undefined, // already "YYYY-MM-DD"
543
+ endDate: event.filters.endDate as string | undefined,
544
+ };
545
+ await reload();
546
+ }
547
+ ```
548
+
549
+ Do NOT call `.toISOString()` on values from `event.filters` — the panel already hands back plain strings, and `.toISOString()` on a local-midnight Date would introduce an off-by-one-day TZ shift.
550
+
551
+ ### Apply / Reset buttons are built-in
552
+
553
+ The old `#filters` slot typically had staged/applied state plus Apply/Reset buttons. VcDataTable's built-in panel ships its own Apply/Clear footer — **delete** `stagedFilters` / `appliedFilters` / `activeFilterCount` / `toggleFilter` / `applyFilters` / `resetFilters`. Keep a single `currentFilters` ref (populated from `@filter`) used by `reload()` / search — that's it.
554
+
555
+ **BEFORE:**
556
+
557
+ ```typescript
558
+ // --- Staged filter state ---
559
+ const statuses = ref([
560
+ { value: "New", displayValue: "New" },
561
+ { value: "Processing", displayValue: "Processing" },
562
+ { value: "Completed", displayValue: "Completed" },
563
+ ]);
564
+
565
+ const stagedFilters = reactive({
566
+ status: [] as string[],
567
+ });
568
+ const appliedFilters = reactive({
569
+ status: [] as string[],
570
+ });
571
+
572
+ const hasFilterChanges = computed(() => JSON.stringify(stagedFilters) !== JSON.stringify(appliedFilters));
573
+ const hasFiltersApplied = computed(() => appliedFilters.status.length > 0);
574
+ const activeFilterCount = computed(() => (hasFiltersApplied.value ? appliedFilters.status.length : 0));
575
+
576
+ function toggleFilter(group: "status", value: string) {
577
+ const idx = stagedFilters[group].indexOf(value);
578
+ if (idx > -1) {
579
+ stagedFilters[group].splice(idx, 1);
580
+ } else {
581
+ stagedFilters[group].push(value);
582
+ }
583
+ }
584
+
585
+ async function applyFilters() {
586
+ Object.assign(appliedFilters, JSON.parse(JSON.stringify(stagedFilters)));
587
+ await load({ statuses: appliedFilters.status, skip: 0 });
588
+ }
589
+
590
+ function resetFilters() {
591
+ stagedFilters.status = [];
592
+ appliedFilters.status = [];
593
+ load({ statuses: [], skip: 0 });
594
+ }
595
+ ```
596
+
597
+ ```vue
598
+ <VcTable :columns="columns" :items="items" :active-filter-count="activeFilterCount">
599
+ <template #filters>
600
+ <div class="filters-panel">
601
+ <h4>{{ $t("MY_MODULE.PAGES.LIST.FILTERS.STATUS") }}</h4>
602
+ <VcCheckbox
603
+ v-for="status in statuses"
604
+ :key="status.value"
605
+ :model-value="stagedFilters.status.includes(status.value)"
606
+ @update:model-value="toggleFilter('status', status.value)"
607
+ >
608
+ {{ status.displayValue }}
609
+ </VcCheckbox>
610
+ <div class="filters-actions">
611
+ <VcButton
612
+ variant="primary"
613
+ :disabled="!hasFilterChanges"
614
+ @click="applyFilters"
615
+ >
616
+ {{ $t("MY_MODULE.PAGES.LIST.FILTERS.APPLY") }}
617
+ </VcButton>
618
+ <VcButton
619
+ variant="outline"
620
+ :disabled="!hasFiltersApplied"
621
+ @click="resetFilters"
622
+ >
623
+ {{ $t("MY_MODULE.PAGES.LIST.FILTERS.RESET") }}
624
+ </VcButton>
625
+ </div>
626
+ </div>
627
+ </template>
628
+ </VcTable>
629
+ ```
630
+
631
+ **AFTER:**
632
+
633
+ ```typescript
634
+ const statuses = ref([
635
+ { value: "New", displayValue: "New" },
636
+ { value: "Processing", displayValue: "Processing" },
637
+ { value: "Completed", displayValue: "Completed" },
638
+ ]);
639
+
640
+ const computedGlobalFilters = computed(() => [
641
+ {
642
+ id: "status",
643
+ label: t("MY_MODULE.PAGES.LIST.FILTERS.STATUS"),
644
+ filter: {
645
+ options: statuses.value.map((s) => ({
646
+ value: s.value ?? "",
647
+ label: s.displayValue ?? "",
648
+ })),
649
+ multiple: true,
650
+ },
651
+ },
652
+ ]);
653
+
654
+ async function onFilter(event: { filters: Record<string, unknown> }) {
655
+ const statusFilter = event.filters.status as string[] | undefined;
656
+ await load({ statuses: statusFilter ?? [], skip: 0 });
657
+ }
658
+ ```
659
+
660
+ ```vue
661
+ <VcDataTable :items="items" :global-filters="computedGlobalFilters" @filter="onFilter">
662
+ <!-- VcColumn children here -->
663
+ </VcDataTable>
664
+ ```
665
+
666
+ Remove all of the following explicitly:
667
+
668
+ - `stagedFilters` reactive object
669
+ - `appliedFilters` reactive object
670
+ - `hasFilterChanges` computed
671
+ - `hasFiltersApplied` computed
672
+ - `activeFilterCount` computed
673
+ - `toggleFilter` function
674
+ - `applyFilters` function
675
+ - `resetFilters` function
676
+ - `:active-filter-count` prop on the table component
677
+ - `#filters` template slot and all its contents (checkboxes, apply/reset buttons)
678
+ - Any filter-related CSS classes (`.filters-panel`, `.filters-actions`, etc.)
679
+
680
+ Key points:
681
+
682
+ - The `global-filters` prop accepts an array of filter descriptors. Each descriptor has `id`, `label`, and a `filter` object with `options` and `multiple`.
683
+ - VcDataTable renders the filter UI internally — no manual checkbox loops or apply/reset buttons.
684
+ - The `@filter` event fires when the user applies filters. The `event.filters` object is keyed by filter `id`, with values matching the selected option values.
685
+ - The active filter count badge is managed internally by VcDataTable — no need for a manual `activeFilterCount` computed.
686
+
687
+ ## RULE 8: Pagination — Manual Calculation → useDataTablePagination
688
+
689
+ Replace manual `pages`/`currentPage` computed properties and `@pagination-click` handlers with the `useDataTablePagination` composable. The composable manages skip/page math internally and returns a reactive pagination object that VcDataTable consumes directly.
690
+
691
+ **BEFORE (composable):**
692
+
693
+ ```typescript
694
+ import { computed, ref } from "vue";
695
+ import { useApiClient, useAsync, useLoading } from "@vc-shell/framework";
696
+
697
+ export function useMyList(options?: { pageSize?: number }) {
698
+ const pageSize = options?.pageSize || 20;
699
+ const searchQuery = ref({ take: pageSize, skip: 0, sort: "createdDate:desc" });
700
+ const searchResult = ref();
701
+
702
+ const { action: loadItems, loading } = useAsync(async (query) => {
703
+ searchQuery.value = { ...searchQuery.value, ...query };
704
+ searchResult.value = await (await getApiClient()).search(searchQuery.value);
705
+ });
706
+
707
+ return {
708
+ items: computed(() => searchResult.value?.results || []),
709
+ totalCount: computed(() => searchResult.value?.totalCount || 0),
710
+ pages: computed(() => Math.ceil((searchResult.value?.totalCount || 1) / pageSize)),
711
+ currentPage: computed(() => Math.ceil((searchQuery.value.skip || 0) / pageSize + 1)),
712
+ searchQuery,
713
+ loadItems,
714
+ loading,
715
+ };
716
+ }
717
+ ```
718
+
719
+ **AFTER (composable):**
720
+
721
+ ```typescript
722
+ import { computed, ref } from "vue";
723
+ import { useApiClient, useAsync, useLoading, useDataTablePagination, type UseDataTablePaginationReturn } from "@vc-shell/framework";
724
+
725
+ export function useMyList(options?: { pageSize?: number }) {
726
+ const pageSize = options?.pageSize || 20;
727
+ const searchQuery = ref({ take: pageSize, skip: 0, sort: "createdDate:desc" });
728
+ const searchResult = ref();
729
+
730
+ const { action: loadItems, loading } = useAsync(async (query) => {
731
+ searchQuery.value = { ...searchQuery.value, ...query };
732
+ searchResult.value = await (await getApiClient()).search(searchQuery.value);
733
+ });
734
+
735
+ const pagination = useDataTablePagination({
736
+ pageSize,
737
+ totalCount: computed(() => searchResult.value?.totalCount ?? 0),
738
+ onPageChange: ({ skip }) => loadItems({ ...searchQuery.value, skip }),
739
+ });
740
+
741
+ return {
742
+ items: computed(() => searchResult.value?.results || []),
743
+ pagination,
744
+ searchQuery,
745
+ loadItems,
746
+ loading,
747
+ };
748
+ }
749
+ ```
750
+
751
+ **BEFORE (blade page):**
752
+
753
+ ```vue
754
+ <VcDataTable :pagination="{ currentPage, pages }" @pagination-click="onPaginationClick" />
755
+ ```
756
+
757
+ ```typescript
758
+ const { items, totalCount, pages, currentPage, loadItems, loading } = useMyList();
759
+
760
+ const onPaginationClick = async (page: number) => {
761
+ await loadItems({ ...searchQuery.value, skip: (page - 1) * 20 });
762
+ };
763
+ ```
764
+
765
+ **AFTER (blade page):**
766
+
767
+ ```vue
768
+ <VcDataTable :pagination="pagination" :total-count="pagination.totalCount" @pagination-click="pagination.goToPage" />
769
+ ```
770
+
771
+ ```typescript
772
+ const { items, pagination, loadItems, loading } = useMyList();
773
+ // No manual onPaginationClick — pagination.goToPage handles it
774
+ ```
775
+
776
+ **What to remove:**
777
+
778
+ - `totalCount`, `pages`, `currentPage` computed from composable return
779
+ - Manual `onPaginationClick` function in blade pages
780
+ - Manual `skip` calculation `(page - 1) * pageSize`
781
+
782
+ **What to add:**
783
+
784
+ - `useDataTablePagination` import in composable
785
+ - `pagination` object in composable return
786
+ - `:pagination="pagination"` and `@pagination-click="pagination.goToPage"` in template
787
+
788
+ ## Verification
789
+
790
+ After migration:
791
+
792
+ 1. Run `vue-tsc --noEmit` (or `npx tsc --noEmit`) to verify no TypeScript errors
793
+ 2. Confirm the table renders with correct columns, data, and layout
794
+ 3. Confirm sorting works: click a sortable column header, verify the sort indicator toggles, verify the data reloads with the correct sort expression
795
+ 4. Confirm selection works: check rows, verify `localSelection` updates, verify toolbar buttons respond to selection state
796
+ 5. Confirm filters work: open the filter panel, select filter options, apply, verify the data reloads with correct filter parameters, verify the active filter badge shows
797
+ 6. Confirm the empty state shows when there are no items (correct icon, title, and action button)
798
+ 7. Confirm row click navigates correctly: click a row, verify the correct blade opens with the correct parameters (remember the `{ data }` wrapper)
799
+ 8. Confirm pull-to-refresh works if enabled
800
+ 9. Confirm pagination works: navigate between pages, verify data reloads
801
+ 10. Confirm no stale imports remain: `useTableSort`, `ITableColumns`, `columns` ref, staged/applied filter state, `@header-click`, `@selection-changed`, `@item-click`, `#filters`, `#empty`, `#notfound`