@vc-shell/vc-app-skill 2.1.0-pr248.6401a93 → 2.1.0-pr249.229549
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vc-shell/vc-app-skill",
|
|
3
|
-
"version": "2.1.0-
|
|
3
|
+
"version": "2.1.0-pr249.0229549",
|
|
4
4
|
"description": "AI coding skill for scaffolding and generating VirtoCommerce Shell applications. Works with Claude Code, OpenCode, Gemini, Codex, Cursor.",
|
|
5
5
|
"bin": "./bin/install.cjs",
|
|
6
6
|
"files": [
|
|
@@ -37,8 +37,10 @@ Process topics in this order (dependencies first):
|
|
|
37
37
|
5. `use-blade-form` — depends on correct types from nswag
|
|
38
38
|
6. `blade-props-simplification` — final cleanup of reusable components
|
|
39
39
|
7. `vctable-audit` — VcTable → VcDataTable component and API rewrite
|
|
40
|
-
8. `
|
|
41
|
-
9. `
|
|
40
|
+
8. `use-data-table-pagination-audit` — manual pagination boilerplate → `useDataTablePagination()`
|
|
41
|
+
9. `table-url-state-audit` — opt a VcDataTable list into URL-query state (sort/search/page via `stateKey`); run after the table is on VcDataTable and pagination uses the composable
|
|
42
|
+
10. `icon-audit` — replace all non-lucide icons with lucide equivalents (last among mechanical manual edits)
|
|
43
|
+
11. `manual-migration-audit` — catch-all manual refactors (run last to avoid overlap)
|
|
42
44
|
|
|
43
45
|
Skip topics not present in the `topics` input.
|
|
44
46
|
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: table-url-state-migration
|
|
3
|
+
description: AI transformation rules for opting an existing VcDataTable list into URL-query state persistence (sort, search, page) via the composables' stateKey option.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Table URL State: persist sort / search / page in the blade URL
|
|
7
|
+
|
|
8
|
+
This retrofit applies to list blades that **already use `VcDataTable`** with the state
|
|
9
|
+
composables (`useDataTableSort`, `useDataTablePagination`, and search) but do **not** yet
|
|
10
|
+
persist the view to the URL. After it, sort, search keyword, and current page survive a
|
|
11
|
+
reload and travel in a shareable link.
|
|
12
|
+
|
|
13
|
+
It is independent of the VcTable→VcDataTable swap (`datatable-migration`) and of adopting
|
|
14
|
+
the pagination composable (`use-data-table-pagination-migration`). Run those first if the
|
|
15
|
+
file still uses `<VcTable>` or hand-rolled `pages`/`currentPage`.
|
|
16
|
+
|
|
17
|
+
## Concept
|
|
18
|
+
|
|
19
|
+
Each state composable accepts an optional `stateKey`. With it set, the composable reads its
|
|
20
|
+
initial value from the blade URL query on creation and writes changes back:
|
|
21
|
+
|
|
22
|
+
- `useDataTableSort({ stateKey })` → `<stateKey>_sort` (e.g. `createdDate:DESC`)
|
|
23
|
+
- `useTableSearch({ stateKey })` → `<stateKey>_search`
|
|
24
|
+
- `useDataTablePagination({ stateKey })` → `<stateKey>_page` (page 1 is encoded as absent)
|
|
25
|
+
|
|
26
|
+
The `state-key` **prop** on `<VcDataTable>` is unrelated — it persists column layout to
|
|
27
|
+
`localStorage`. Leave it as-is. The composable `stateKey` is the URL query store.
|
|
28
|
+
|
|
29
|
+
## Choosing the key
|
|
30
|
+
|
|
31
|
+
- One key per table, **the same string** across all three composables on that blade.
|
|
32
|
+
- Use snake_case, conventionally `<module>_list` (e.g. `orders_list`). It may equal the
|
|
33
|
+
table `state-key` prop — they are separate stores.
|
|
34
|
+
- The key must be unique among tables that can be visible simultaneously (a list plus a
|
|
35
|
+
child list in the same stack), so their query params do not collide.
|
|
36
|
+
|
|
37
|
+
## Precondition: the blade must be URL-addressable
|
|
38
|
+
|
|
39
|
+
`stateKey` only does something when the blade has a `url` in `defineBlade`. For a
|
|
40
|
+
non-routable or nested blade (no `url`) the query service is a no-op — skip that file and
|
|
41
|
+
note it in the report rather than adding dead options.
|
|
42
|
+
|
|
43
|
+
## RULE 1: Add `stateKey` to `useDataTableSort`
|
|
44
|
+
|
|
45
|
+
**BEFORE:**
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
const { sortField, sortOrder, sortExpression } = useDataTableSort({
|
|
49
|
+
initialField: "createdDate",
|
|
50
|
+
initialDirection: "DESC",
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**AFTER:**
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const { sortField, sortOrder, sortExpression } = useDataTableSort({
|
|
58
|
+
stateKey: "orders_list",
|
|
59
|
+
initialField: "createdDate",
|
|
60
|
+
initialDirection: "DESC",
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## RULE 2: Own the search keyword with `useTableSearch({ stateKey })`
|
|
65
|
+
|
|
66
|
+
The keyword must live in a ref that `useTableSearch` controls, bound to
|
|
67
|
+
`v-model:search-value`. Three starting shapes:
|
|
68
|
+
|
|
69
|
+
**2a — bare `ref("")` in the blade:** replace it.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// BEFORE
|
|
73
|
+
const searchValue = ref("");
|
|
74
|
+
// AFTER
|
|
75
|
+
const { searchValue } = useTableSearch({ stateKey: "orders_list" });
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**2b — event-driven `@search` with no ref:** the table emits `@search` and a handler loads
|
|
79
|
+
directly. Convert to `v-model:search-value` + a watcher.
|
|
80
|
+
|
|
81
|
+
```vue
|
|
82
|
+
<!-- BEFORE -->
|
|
83
|
+
<VcDataTable :searchable="true" @search="onSearchChange" />
|
|
84
|
+
<!-- AFTER -->
|
|
85
|
+
<VcDataTable :searchable="true" v-model:search-value="searchValue" />
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
// BEFORE
|
|
90
|
+
async function onSearchChange(keyword: string | undefined) {
|
|
91
|
+
await loadItems({ ...searchQuery.value, keyword, skip: 0 });
|
|
92
|
+
}
|
|
93
|
+
// AFTER — remove onSearchChange; drive load from the watcher (see RULE 5)
|
|
94
|
+
const { searchValue } = useTableSearch({ stateKey: "orders_list" });
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**2c — the deprecated `useTableQueryState().read()` preview form:** delete the manual
|
|
98
|
+
restore block entirely (see RULE 6); the three composables now seed themselves.
|
|
99
|
+
|
|
100
|
+
In all cases add `v-model:search-value="searchValue"` to the `<VcDataTable>` and keep
|
|
101
|
+
`:searchable="true"`.
|
|
102
|
+
|
|
103
|
+
## RULE 3: Add `stateKey` to `useDataTablePagination`
|
|
104
|
+
|
|
105
|
+
If pagination is created in the blade, add the option directly:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const pagination = useDataTablePagination({
|
|
109
|
+
stateKey: "orders_list",
|
|
110
|
+
pageSize: 20,
|
|
111
|
+
totalCount,
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
If pagination is created **inside the list composable** (common when `onPageChange` drives
|
|
116
|
+
the load), thread `stateKey` through as a composable option — the blade still owns the key:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
// composable
|
|
120
|
+
export function useOrdersList(options?: { pageSize?: number; stateKey?: string }) {
|
|
121
|
+
// ...
|
|
122
|
+
const pagination = useDataTablePagination({
|
|
123
|
+
stateKey: options?.stateKey,
|
|
124
|
+
pageSize: options?.pageSize ?? 20,
|
|
125
|
+
totalCount: computed(() => searchResult.value?.totalCount ?? 0),
|
|
126
|
+
onPageChange: ({ skip }) => loadItems({ ...searchQuery.value, skip }),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
// blade
|
|
133
|
+
const { items, pagination, loadItems } = useOrdersList({ stateKey: "orders_list" });
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Add the `stateKey?: string` field to the composable's options interface.
|
|
137
|
+
|
|
138
|
+
## RULE 4: Seed the initial load from the restored values
|
|
139
|
+
|
|
140
|
+
The first load on mount must read the restored sort, keyword, and page — otherwise the URL
|
|
141
|
+
is restored into the refs but the first fetch ignores them and shows page 1 unfiltered.
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
// BEFORE
|
|
145
|
+
onMounted(() => loadItems({ take: 20, sort: sortExpression.value }));
|
|
146
|
+
// AFTER
|
|
147
|
+
onMounted(() =>
|
|
148
|
+
loadItems({
|
|
149
|
+
take: 20,
|
|
150
|
+
sort: sortExpression.value,
|
|
151
|
+
keyword: searchValue.value || undefined,
|
|
152
|
+
skip: pagination.skip,
|
|
153
|
+
}),
|
|
154
|
+
);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
For a `reload()` helper, use `skip: pagination.skip` (not a recomputed skip).
|
|
158
|
+
|
|
159
|
+
## RULE 5: Reset to page 1 when the search changes — REQUIRED
|
|
160
|
+
|
|
161
|
+
This is not optional. If the user is on page 3 and types a query that returns fewer pages,
|
|
162
|
+
leaving `_page=3` in the URL makes the next reload request page 3 of the filtered set:
|
|
163
|
+
`skip` overshoots and the table shows an empty "nothing found" state on reload. Resetting to
|
|
164
|
+
page 1 drops `_page` from the URL and keeps the `(search, page)` pair consistent.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
watch(searchValue, () => pagination.setPage(1));
|
|
168
|
+
// then the reload watcher (debounced) picks up the change:
|
|
169
|
+
watch([sortExpression, searchValue, () => pagination.skip], debounce(load, 300));
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
If search loads through a separate handler (event-driven apps), call `pagination.setPage(1)`
|
|
173
|
+
at the top of that handler before loading with `skip: 0`. Apply the same reset when a global
|
|
174
|
+
filter changes.
|
|
175
|
+
|
|
176
|
+
## RULE 6: Remove the deprecated preview API
|
|
177
|
+
|
|
178
|
+
Delete any `useTableQueryState().read()` or `useTableQueryPersistence` usage and the manual
|
|
179
|
+
ref-seeding it drove — the `stateKey` option replaces it.
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
// DELETE this whole block:
|
|
183
|
+
const restored = useTableQueryState("orders_list").read();
|
|
184
|
+
if (restored.sort) {
|
|
185
|
+
/* ... */
|
|
186
|
+
}
|
|
187
|
+
if (restored.search) searchValue.value = restored.search;
|
|
188
|
+
if (restored.page) pagination.setPage(restored.page);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Remove the now-unused `useTableQueryState` / `useTableQueryPersistence` import.
|
|
192
|
+
|
|
193
|
+
## Advanced: a shared list base with two views
|
|
194
|
+
|
|
195
|
+
Some apps render two datasets (e.g. flat list vs. category tree) through one
|
|
196
|
+
`VcDataTable`-wrapping base component, each with its own `useDataTablePagination`. Pass the
|
|
197
|
+
same `stateKey` to **both** view paginations (only one view is active at a time, so they
|
|
198
|
+
never write the page param simultaneously) and reset the page on search via the unified
|
|
199
|
+
pagination object the base holds (`props.pagination.setPage(1)`).
|
|
200
|
+
|
|
201
|
+
## What to add
|
|
202
|
+
|
|
203
|
+
- `stateKey` on `useDataTableSort`, `useDataTablePagination`, and `useTableSearch` (same key)
|
|
204
|
+
- `useTableSearch({ stateKey })` import + `v-model:search-value="searchValue"` binding
|
|
205
|
+
- `watch(searchValue, () => pagination.setPage(1))`
|
|
206
|
+
- `skip: pagination.skip` and `keyword: searchValue.value || undefined` in the initial load
|
|
207
|
+
- `stateKey?: string` on the list composable's options interface (when pagination is there)
|
|
208
|
+
|
|
209
|
+
## What to remove
|
|
210
|
+
|
|
211
|
+
- `useTableQueryState().read()` / `useTableQueryPersistence` calls and their imports
|
|
212
|
+
- Event-driven `@search` handlers that only set a keyword and reload (replaced by RULE 2/5)
|
|
213
|
+
- Any manual `currentPage`/`skip` math used purely to restore a page from the URL
|
|
214
|
+
|
|
215
|
+
## Verification
|
|
216
|
+
|
|
217
|
+
1. `npx vue-tsc --noEmit` passes.
|
|
218
|
+
2. Sort a column, search, go to page 2 — the URL gains `<key>_sort`, `<key>_search`,
|
|
219
|
+
`<key>_page`. Reload: the table restores the same view and shows results.
|
|
220
|
+
3. On page 2, type a search — the URL drops `_page` (reset to page 1) and results show.
|
|
221
|
+
4. No `useTableQueryState` / `useTableQueryPersistence` references remain.
|
package/runtime/vc-app.md
CHANGED
|
@@ -1613,6 +1613,7 @@ Map topic headings to migration prompt files and pattern files:
|
|
|
1613
1613
|
| Assets API / useAssets / useAssetsManager / use-assets-migration | `use-assets-migration` | `{KNOWLEDGE_BASE}/migration-prompts/use-assets-migration.md` | `{KNOWLEDGE_BASE}/patterns/assets-management.md` |
|
|
1614
1614
|
| Manual Migration Audit / useExternalWidgets / moment / useFunctions / resolveBladeByName / onParentCall / manual-migration-audit | `manual-migration-audit` | `{KNOWLEDGE_BASE}/migration-prompts/manual-migration-audit.md` | — |
|
|
1615
1615
|
| Pagination / useDataTablePagination / use-data-table-pagination-audit | `use-data-table-pagination-audit` | `{KNOWLEDGE_BASE}/migration-prompts/use-data-table-pagination-migration.md` | — |
|
|
1616
|
+
| Table URL State / stateKey / useTableQueryState / table-url-state-audit | `table-url-state-audit` | `{KNOWLEDGE_BASE}/migration-prompts/table-url-state-migration.md` | `{KNOWLEDGE_BASE}/patterns/list-blade-pattern.md` |
|
|
1616
1617
|
|
|
1617
1618
|
Build the `topics` array for the migration-agent using the canonical topic names above.
|
|
1618
1619
|
|