@vc-shell/vc-app-skill 2.0.0-alpha.23 → 2.0.0-alpha.25

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 (45) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/knowledge-stats.cjs +135 -0
  3. package/bin/sync-docs.cjs +62 -0
  4. package/package.json +5 -1
  5. package/runtime/VERSION +1 -1
  6. package/runtime/agents/details-blade-generator.md +75 -14
  7. package/runtime/knowledge/docs/_BUILD_HASH.md +1 -1
  8. package/runtime/knowledge/docs/core/api/platform.docs.md +14 -7
  9. package/runtime/knowledge/docs/core/blade-navigation/blade-nav-composables.docs.md +6 -0
  10. package/runtime/knowledge/docs/core/composables/useApiClient/useApiClient.docs.md +6 -0
  11. package/runtime/knowledge/docs/core/composables/useAssetsManager/useAssetsManager.docs.md +149 -0
  12. package/runtime/knowledge/docs/core/composables/useAsync/useAsync.docs.md +7 -0
  13. package/runtime/knowledge/docs/core/composables/useBlade/useBlade.docs.md +7 -0
  14. package/runtime/knowledge/docs/core/composables/useBladeWidgets.docs.md +164 -9
  15. package/runtime/knowledge/docs/core/composables/useDashboard/useDashboard.docs.md +6 -0
  16. package/runtime/knowledge/docs/core/composables/useMenuService/useMenuService.docs.md +6 -0
  17. package/runtime/knowledge/docs/core/composables/usePermissions/usePermissions.docs.md +6 -0
  18. package/runtime/knowledge/docs/core/composables/useToolbar/useToolbar.docs.md +6 -0
  19. package/runtime/knowledge/docs/core/composables/useWidgets/useWidgets.docs.md +2 -2
  20. package/runtime/knowledge/docs/core/plugins/ai-agent/ai-agent.docs.md +6 -0
  21. package/runtime/knowledge/docs/core/plugins/extension-points/extension-points.docs.md +6 -0
  22. package/runtime/knowledge/docs/core/plugins/global-error-handler/global-error-handler.docs.md +6 -0
  23. package/runtime/knowledge/docs/core/plugins/i18n/i18n.docs.md +74 -0
  24. package/runtime/knowledge/docs/core/plugins/modularity/modularity.docs.md +6 -0
  25. package/runtime/knowledge/docs/core/plugins/permissions/permissions.docs.md +6 -0
  26. package/runtime/knowledge/docs/core/plugins/signalR/signalR.docs.md +6 -0
  27. package/runtime/knowledge/docs/core/plugins/validation/validation.docs.md +6 -0
  28. package/runtime/knowledge/docs/injection-keys.docs.md +1 -1
  29. package/runtime/knowledge/docs/modules/assets-manager/assets-manager.docs.md +29 -33
  30. package/runtime/knowledge/docs/shell/components/logout-button/logout-button.docs.md +3 -3
  31. package/runtime/knowledge/docs/ui/components/atoms/vc-button/vc-button.docs.md +11 -0
  32. package/runtime/knowledge/docs/ui/components/atoms/vc-card/vc-card.docs.md +11 -0
  33. package/runtime/knowledge/docs/ui/components/organisms/vc-blade/vc-blade.docs.md +11 -0
  34. package/runtime/knowledge/docs/ui/components/organisms/vc-table/vc-data-table.docs.md +12 -0
  35. package/runtime/knowledge/index.md +60 -0
  36. package/runtime/knowledge/patterns/assets-management.md +213 -0
  37. package/runtime/knowledge/patterns/child-blade-flow.md +277 -0
  38. package/runtime/knowledge/patterns/details-blade-pattern.md +350 -3
  39. package/runtime/knowledge/patterns/extension-points-usage.md +308 -0
  40. package/runtime/knowledge/patterns/form-validation.md +377 -0
  41. package/runtime/knowledge/patterns/multilanguage-fields.md +239 -0
  42. package/runtime/knowledge/patterns/signalr-notifications.md +237 -0
  43. package/runtime/vc-app.md +44 -5
  44. package/runtime/knowledge/docs/core/composables/useWidget/useWidget.docs.md +0 -159
  45. package/runtime/knowledge/docs/shell/components/app-switcher/app-switcher.docs.md +0 -104
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ # [2.0.0-alpha.25](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.0-alpha.24...v2.0.0-alpha.25) (2026-03-25)
2
+
3
+ **Note:** Version bump only for package @vc-shell/vc-app-skill
4
+
5
+ # [2.0.0-alpha.24](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.0-alpha.23...v2.0.0-alpha.24) (2026-03-25)
6
+
7
+
8
+ ### Features
9
+
10
+ * **vc-app-skill:** add knowledge-stats script for knowledge base audit ([7a568fc](https://github.com/VirtoCommerce/vc-shell/commit/7a568fc57335d51349a644721c904586efd49273))
11
+ * **vc-app-skill:** enrich knowledge base with When to Use sections, patterns, and sync-docs script ([c82ed63](https://github.com/VirtoCommerce/vc-shell/commit/c82ed639d748bdc8fd2d9c39435ee37baedd0563))
12
+ * **vc-app:** add sidebar search bar for menu filtering ([72f17fc](https://github.com/VirtoCommerce/vc-shell/commit/72f17fc5b0e77e4e87457c5a29262345da50317d)), closes [#menu](https://github.com/VirtoCommerce/vc-shell/issues/menu) [#menu](https://github.com/VirtoCommerce/vc-shell/issues/menu)
1
13
  # [2.0.0-alpha.23](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.0-alpha.22...v2.0.0-alpha.23) (2026-03-23)
2
14
 
3
15
 
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const KNOWLEDGE_DIR = path.join(__dirname, '..', 'runtime', 'knowledge');
8
+
9
+ const green = (s) => `\x1b[32m${s}\x1b[0m`;
10
+ const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
11
+ const red = (s) => `\x1b[31m${s}\x1b[0m`;
12
+ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
13
+ const bold = (s) => `\x1b[1m${s}\x1b[0m`;
14
+
15
+ function findFiles(dir, ext, base) {
16
+ const results = [];
17
+ if (!fs.existsSync(dir)) return results;
18
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
19
+ const full = path.join(dir, entry.name);
20
+ if (entry.isDirectory()) {
21
+ results.push(...findFiles(full, ext, base));
22
+ } else if (entry.name.endsWith(ext)) {
23
+ results.push(path.relative(base, full));
24
+ }
25
+ }
26
+ return results.sort();
27
+ }
28
+
29
+ function analyzeDoc(filePath) {
30
+ const content = fs.readFileSync(filePath, 'utf-8');
31
+ const lines = content.split('\n');
32
+ const title = (lines.find(l => l.startsWith('# ')) || '').replace('# ', '');
33
+ const hasWhenToUse = /^## When to Use/m.test(content);
34
+ const hasSlots = /^## Slots/m.test(content);
35
+ const hasQuickStart = /^## Quick Start/m.test(content);
36
+ const codeBlocks = (content.match(/```/g) || []).length / 2;
37
+ return { title, lines: lines.length, hasWhenToUse, hasSlots, hasQuickStart, codeBlocks: Math.floor(codeBlocks) };
38
+ }
39
+
40
+ function analyzePattern(filePath) {
41
+ const content = fs.readFileSync(filePath, 'utf-8');
42
+ const lines = content.split('\n');
43
+ const title = (lines.find(l => l.startsWith('# ')) || '').replace('# ', '');
44
+ const sections = lines.filter(l => l.startsWith('## ')).map(l => l.replace('## ', ''));
45
+ const codeBlocks = (content.match(/```/g) || []).length / 2;
46
+ return { title, lines: lines.length, sections, codeBlocks: Math.floor(codeBlocks) };
47
+ }
48
+
49
+ // --- Docs ---
50
+ const docsDir = path.join(KNOWLEDGE_DIR, 'docs');
51
+ const docFiles = findFiles(docsDir, '.docs.md', docsDir);
52
+
53
+ console.log(bold('\n📚 DOCS') + dim(` (${docsDir})`));
54
+ console.log(dim('─'.repeat(80)));
55
+
56
+ const docCategories = {};
57
+ for (const f of docFiles) {
58
+ const cat = f.split(path.sep)[0]; // core, ui, shell, modules
59
+ if (!docCategories[cat]) docCategories[cat] = [];
60
+ docCategories[cat].push(f);
61
+ }
62
+
63
+ let totalDocs = 0;
64
+ let withWhenToUse = 0;
65
+ let withSlots = 0;
66
+ let withQuickStart = 0;
67
+
68
+ for (const [cat, files] of Object.entries(docCategories)) {
69
+ console.log(`\n ${bold(cat.toUpperCase())} ${dim(`(${files.length} files)`)}`);
70
+ for (const f of files) {
71
+ const info = analyzeDoc(path.join(docsDir, f));
72
+ totalDocs++;
73
+ if (info.hasWhenToUse) withWhenToUse++;
74
+ if (info.hasSlots) withSlots++;
75
+ if (info.hasQuickStart) withQuickStart++;
76
+
77
+ const flags = [
78
+ info.hasWhenToUse ? green('WtU') : red('WtU'),
79
+ info.hasSlots ? green('Slt') : dim('Slt'),
80
+ info.hasQuickStart ? green('QS') : dim('QS'),
81
+ ].join(' ');
82
+
83
+ const name = info.title || path.basename(f, '.docs.md');
84
+ console.log(` ${flags} ${dim(`${String(info.lines).padStart(4)}L ${String(info.codeBlocks).padStart(2)}ex`)} ${name}`);
85
+ }
86
+ }
87
+
88
+ console.log(dim('\n─'.repeat(80)));
89
+ console.log(` Total: ${bold(totalDocs)} docs | When to Use: ${withWhenToUse}/${totalDocs} | Slots: ${withSlots}/${totalDocs} | Quick Start: ${withQuickStart}/${totalDocs}`);
90
+
91
+ // --- Patterns ---
92
+ const patternsDir = path.join(KNOWLEDGE_DIR, 'patterns');
93
+ const patternFiles = findFiles(patternsDir, '.md', patternsDir).filter(f => f !== '.gitkeep');
94
+
95
+ console.log(bold('\n🧩 PATTERNS') + dim(` (${patternsDir})`));
96
+ console.log(dim('─'.repeat(80)));
97
+
98
+ for (const f of patternFiles) {
99
+ const info = analyzePattern(path.join(patternsDir, f));
100
+ console.log(` ${dim(`${String(info.lines).padStart(4)}L ${String(info.codeBlocks).padStart(2)}ex`)} ${bold(info.title || f)}`);
101
+ console.log(` ${dim(info.sections.join(' → '))}`);
102
+ }
103
+
104
+ console.log(dim('\n─'.repeat(80)));
105
+ console.log(` Total: ${bold(patternFiles.length)} patterns`);
106
+
107
+ // --- Examples ---
108
+ const examplesDir = path.join(KNOWLEDGE_DIR, 'examples');
109
+ const exampleFiles = findFiles(examplesDir, '.md', examplesDir);
110
+
111
+ console.log(bold('\n📝 EXAMPLES') + dim(` (${examplesDir})`));
112
+ console.log(dim('─'.repeat(80)));
113
+
114
+ for (const f of exampleFiles) {
115
+ const content = fs.readFileSync(path.join(examplesDir, f), 'utf-8');
116
+ const lines = content.split('\n');
117
+ const title = (lines.find(l => l.startsWith('# ')) || '').replace('# ', '');
118
+ console.log(` ${dim(`${String(lines.length).padStart(4)}L`)} ${title || f}`);
119
+ }
120
+
121
+ console.log(dim('\n─'.repeat(80)));
122
+ console.log(` Total: ${bold(exampleFiles.length)} examples`);
123
+
124
+ // --- Summary ---
125
+ const totalKB = Math.round(docFiles.concat(patternFiles, exampleFiles)
126
+ .reduce((sum, f) => {
127
+ const dir = f.endsWith('.docs.md') ? docsDir : patternFiles.includes(f) ? patternsDir : examplesDir;
128
+ try { return sum + fs.statSync(path.join(dir, f)).size; } catch { return sum; }
129
+ }, 0) / 1024);
130
+
131
+ console.log(bold('\n📊 SUMMARY'));
132
+ console.log(dim('─'.repeat(80)));
133
+ console.log(` ${bold(totalDocs)} docs + ${bold(patternFiles.length)} patterns + ${bold(exampleFiles.length)} examples = ${bold(totalDocs + patternFiles.length + exampleFiles.length)} knowledge files (${totalKB} KB)`);
134
+ console.log(` Coverage: When to Use ${bold(Math.round(withWhenToUse/totalDocs*100)+'%')} | Slots ${bold(Math.round(withSlots/totalDocs*100)+'%')} | Quick Start ${bold(Math.round(withQuickStart/totalDocs*100)+'%')}`);
135
+ console.log('');
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const ROOT = path.resolve(__dirname, '..', '..', '..');
8
+ const FRAMEWORK_DIR = path.join(ROOT, 'framework');
9
+ const DOCS_DIR = path.join(__dirname, '..', 'runtime', 'knowledge', 'docs');
10
+
11
+ function findDocsFiles(dir, base) {
12
+ const results = [];
13
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
14
+ const fullPath = path.join(dir, entry.name);
15
+ const relPath = path.relative(base, fullPath);
16
+ if (entry.isDirectory()) {
17
+ results.push(...findDocsFiles(fullPath, base));
18
+ } else if (entry.name.endsWith('.docs.md')) {
19
+ results.push(relPath);
20
+ }
21
+ }
22
+ return results;
23
+ }
24
+
25
+ // Collect framework docs
26
+ const frameworkDocs = findDocsFiles(FRAMEWORK_DIR, FRAMEWORK_DIR);
27
+
28
+ let copied = 0;
29
+ let skipped = 0;
30
+
31
+ for (const relPath of frameworkDocs) {
32
+ const src = path.join(FRAMEWORK_DIR, relPath);
33
+ const dest = path.join(DOCS_DIR, relPath);
34
+
35
+ // Skip if dest is identical
36
+ if (fs.existsSync(dest)) {
37
+ const srcContent = fs.readFileSync(src);
38
+ const destContent = fs.readFileSync(dest);
39
+ if (srcContent.equals(destContent)) {
40
+ skipped++;
41
+ continue;
42
+ }
43
+ }
44
+
45
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
46
+ fs.copyFileSync(src, dest);
47
+ console.log(` updated: ${relPath}`);
48
+ copied++;
49
+ }
50
+
51
+ // Remove docs that no longer exist in framework
52
+ const skillDocs = findDocsFiles(DOCS_DIR, DOCS_DIR);
53
+ let removed = 0;
54
+ for (const relPath of skillDocs) {
55
+ if (!fs.existsSync(path.join(FRAMEWORK_DIR, relPath))) {
56
+ fs.unlinkSync(path.join(DOCS_DIR, relPath));
57
+ console.log(` removed: ${relPath}`);
58
+ removed++;
59
+ }
60
+ }
61
+
62
+ console.log(`\nSync complete: ${copied} updated, ${removed} removed, ${skipped} unchanged`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vc-shell/vc-app-skill",
3
- "version": "2.0.0-alpha.23",
3
+ "version": "2.0.0-alpha.25",
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": [
@@ -18,6 +18,10 @@
18
18
  "scaffolding",
19
19
  "vue3"
20
20
  ],
21
+ "scripts": {
22
+ "sync-docs": "node bin/sync-docs.cjs",
23
+ "knowledge-stats": "node bin/knowledge-stats.cjs"
24
+ },
21
25
  "publishConfig": {
22
26
  "access": "public",
23
27
  "registry": "https://registry.npmjs.org/"
package/runtime/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.0-alpha.23
1
+ 2.0.0-alpha.25
@@ -21,9 +21,10 @@ description: Generates a details blade Vue component and its singular composable
21
21
  "fields": [
22
22
  {
23
23
  "name": "string — camelCase field name",
24
- "type": "string — 'string' | 'boolean' | 'number' | 'Date' | 'enum' | 'array'",
24
+ "type": "string — 'string' | 'text' | 'rich-text' | 'boolean' | 'number' | 'currency' | 'date-time' | 'Date' | 'enum' | 'multi-select' | 'image' | 'gallery' | 'file' | 'rating' | 'range' | 'color' | 'array'",
25
25
  "required": "boolean?",
26
- "label": "string? — display label override"
26
+ "label": "string? — display label override",
27
+ "component": "string? — explicit component override (e.g., 'VcTextarea', 'VcEditor', 'VcInputCurrency'). If provided, use this instead of type-based mapping."
27
28
  }
28
29
  ],
29
30
  "isStandalone": "boolean — true if this is a workspace blade with its own URL (settings/profile page)",
@@ -54,7 +55,10 @@ Before generating, Read these files in order:
54
55
  2. `{knowledgeBase}/patterns/composable-details.md` — singular composable skeleton and rules
55
56
  3. `{knowledgeBase}/index.md` — to find relevant component docs
56
57
 
57
- From `index.md`, identify docs for `VcInput`, `VcSelect`, `VcSwitch`, `VcBlade`, `VcForm` and Read them from `{docsRoot}/`.
58
+ From `index.md`, identify and Read docs from `{docsRoot}/` for:
59
+ - **Always:** `VcBlade`, `VcForm`, `VcInput`, `VcSelect`, `VcSwitch`
60
+ - **Based on field types present:** Read docs for each component that appears in the field type mapping below (e.g., if any field has type `text` → read `VcTextarea` docs; if `rich-text` → read `VcEditor` docs; if `currency` → read `VcInputCurrency` docs; etc.)
61
+ - **Layout:** If 5+ fields → read `VcCard` docs; if 8+ fields → read `VcAccordion` docs
58
62
 
59
63
  ## Generation Rules
60
64
 
@@ -87,17 +91,42 @@ When `linkTo` is provided:
87
91
 
88
92
  ### Step 2: Map fields to components
89
93
 
90
- For each field in `fields`, determine the component and rules:
94
+ For each field in `fields`, determine the component using this table. If the field already has a `component` property (from `/vc-app design`), use that directly. Otherwise, map from `type`:
91
95
 
92
96
  | Field type | Component | Validation rules | Notes |
93
97
  |------------|-----------|------------------|-------|
94
- | `string` | `VcInput` | `required` (if required) | Default input |
95
- | `boolean` | `VcSwitch` | none | No `Field` wrapper |
96
- | `number` | `VcInput type="number"` | `required\|bigint\|min_value:0` | |
97
- | `Date` | `VcInput type="datetime-local"` | `required` (if required) | |
98
- | `enum` | `VcSelect` | `required` (if required) | Needs `statusOptions` computed |
98
+ | `string` | `VcInput` | `required` (if required) | Default for short text. Use `rules="email"` if name contains "email", `type="tel"` if phone, `type="url"` if url/website |
99
+ | `text` | `VcTextarea` | `required` (if required) | Long text: description, notes, comments, bio, summary |
100
+ | `rich-text` | `VcEditor` | `required` (if required) | HTML/WYSIWYG: body, content, article, template |
101
+ | `boolean` | `VcSwitch` | none | No `Field` wrapper. Default for toggles (isActive, isEnabled) |
102
+ | `boolean` | `VcCheckbox` | none | No `Field` wrapper. Use for consent/accept semantics (agreeTerms) |
103
+ | `number` | `VcInput type="number"` | `required\|bigint\|min_value:0` | Plain numbers: quantity, count, age |
104
+ | `currency` | `VcInputCurrency` | `required\|min_value:0` | Money: price, cost, amount, salary. Set `currency="USD"` (or infer from context) |
105
+ | `date-time` | `VcDatePicker` | `required` (if required) | Preferred over `VcInput type="datetime-local"`. Calendar picker widget |
106
+ | `Date` | `VcDatePicker` | `required` (if required) | Legacy alias — same as `date-time` |
107
+ | `enum` | `VcSelect` | `required` (if required) | For 6+ options or dynamic options. Needs `{field}Options` computed |
108
+ | `enum` (2-5 options) | `VcRadioGroup` | `required` (if required) | More visual for small static sets. Needs `{field}Options` array |
109
+ | `multi-select` | `VcMultivalue` | none | Tags/multi-pick: tags, categories, roles. No `Field` wrapper needed |
110
+ | `multi-select` (static) | `VcCheckboxGroup` | none | Few static checkboxes. Needs `{field}Options` array |
111
+ | `rating` | `VcRating` | none | Star rating 1-5. No `Field` wrapper needed |
112
+ | `range` | `VcSlider` | none | Numeric slider: discount, percentage. Set `:min` / `:max` |
113
+ | `color` | `VcColorInput` | none | Color picker with hex. No `Field` wrapper needed |
114
+ | `image` | `VcImageUpload` | none | Single image upload with preview |
115
+ | `gallery` | `VcGallery` | none | Multi-image management grid |
116
+ | `file` | `VcFileUpload` | none | File attachment. Set `:accept` filter for allowed extensions |
99
117
  | `array` | `VcDataTable` (inline) | none | Read-only nested table |
100
118
 
119
+ **Field name heuristics:** When field type is plain `"string"`, upgrade based on the field name:
120
+ - `description`, `notes`, `comment`, `bio`, `summary`, `about` → `text` (VcTextarea)
121
+ - `body`, `content`, `html`, `article`, `template` → `rich-text` (VcEditor)
122
+ - `price`, `cost`, `amount`, `total`, `salary`, `budget`, `fee` → `currency` (VcInputCurrency)
123
+ - `avatar`, `logo`, `photo`, `thumbnail`, `banner` → `image` (VcImageUpload)
124
+ - `tags`, `labels`, `categories`, `roles`, `permissions` → `multi-select` (VcMultivalue)
125
+ - `rating`, `score`, `stars` → `rating` (VcRating)
126
+ - `color`, `colour`, `brandColor` → `color` (VcColorInput)
127
+
128
+ Refer to `details-blade-pattern.md` → "Field Type → Component Mapping" section for full template examples of each component.
129
+
101
130
  ### Step 3: Generate the singular composable
102
131
 
103
132
  Write to: `{targetDir}/composables/use{EntityName}/index.ts`
@@ -175,12 +204,35 @@ Follow `details-blade-pattern.md` template exactly:
175
204
 
176
205
  **Template:**
177
206
  - `<VcBlade :loading="loading" :modified="modified" :title="title" :toolbar-items="bladeToolbar" width="50%">`
178
- - `<VcContainer><VcForm><VcRow>`
179
- - For each non-array, non-boolean field: wrap in `<Field>` with `v-slot`, `name`, `:model-value`, `rules`
180
- - For boolean fields: `<VcSwitch v-model="entity.{name}">` without Field wrapper
207
+ - `<VcContainer>`
208
+
209
+ **Banners (contextual alerts):**
210
+ - Add `<VcBanner>` at the top of the form (before `<VcForm>`) for state-dependent alerts. See `details-blade-pattern.md` → "Contextual Banners Pattern".
211
+ - Use `variant="info"` for creation hints, `variant="danger"` for errors, `variant="warning"` for missing data.
212
+ - Example: `<VcBanner v-if="!entity.id" variant="info" icon="lucide-lightbulb" icon-size="l">{{ $t("...CREATE_HINT") }}</VcBanner>`
213
+
214
+ **Form fields:**
215
+ - `<VcForm>` wraps all editable fields
216
+ - Use the component determined in Step 2 for each field. Refer to `details-blade-pattern.md` for full template examples of each component type.
217
+ - Components that need `<Field>` wrapper (for validation): `VcInput`, `VcTextarea`, `VcEditor`, `VcDatePicker`, `VcSelect`, `VcRadioGroup`, `VcInputCurrency`, `VcFileUpload`
218
+ - Components that do NOT need `<Field>` wrapper: `VcSwitch`, `VcCheckbox`, `VcMultivalue`, `VcRating`, `VcSlider`, `VcColorInput`, `VcImageUpload`, `VcGallery`
181
219
  - For array fields: separate `<VcRow>` with inline `<VcDataTable>`
182
220
  - Use i18n keys: `{i18nPrefix}.FIELDS.{FIELD_NAME_UPPER}.LABEL` and `.PLACEHOLDER`
183
221
 
222
+ **Read-only fields (VcField):**
223
+ - For fields that should display data without editing (computed values, IDs, timestamps on existing entities), use `<VcField>` instead of `<VcInput>`:
224
+ ```vue
225
+ <VcField :label="$t('...')" :model-value="entity.number" orientation="horizontal" :aspect-ratio="[1, 2]" copyable type="text" />
226
+ ```
227
+ - See `details-blade-pattern.md` → "Read-Only Details Pattern".
228
+ - Use `VcField` when: the blade is view-only, or for specific non-editable fields within an editable form (order number, creation date).
229
+
230
+ **Layout — ALWAYS use VcCard sections:**
231
+ - Group related fields in `<VcCard :header="$t('...')">` sections. This is how real vc-shell apps are built — flat forms without cards look unfinished.
232
+ - If 3+ fields → at minimum 1 card. If 5+ fields → 2+ cards grouped by topic.
233
+ - If 8+ fields → consider `<VcAccordion>` for secondary/advanced fields.
234
+ - **Grid:** Use `<VcRow><VcCol>` to arrange short fields in 2 columns. Wide fields (`VcTextarea`, `VcEditor`, `VcGallery`, `VcDataTable`) span full width.
235
+
184
236
  **Close guard:**
185
237
  ```ts
186
238
  onBeforeClose(async () => {
@@ -217,9 +269,18 @@ Before completing, verify:
217
269
  - [ ] `onBeforeClose` guard is present if `modified` is used
218
270
  - [ ] After save/delete: `callParent("reload")` before `closeSelf()`
219
271
  - [ ] `disabled` on toolbar items is a `computed(...)`, not a plain boolean
220
- - [ ] `boolean` fields use `VcSwitch` without a `Field` wrapper
221
- - [ ] `enum` fields use `VcSelect` with `option-value` and `option-label` props
272
+ - [ ] `boolean` fields use `VcSwitch` (or `VcCheckbox` for consent) without a `Field` wrapper
273
+ - [ ] `enum` fields use `VcSelect` (6+ options) or `VcRadioGroup` (2-5 options) with `option-value` and `option-label` props
274
+ - [ ] `text` fields use `VcTextarea`, NOT `VcInput`
275
+ - [ ] `rich-text` fields use `VcEditor`, NOT `VcTextarea`
276
+ - [ ] `currency` fields use `VcInputCurrency`, NOT `VcInput type="number"`
277
+ - [ ] `date-time` fields use `VcDatePicker`, NOT `VcInput type="datetime-local"`
278
+ - [ ] `multi-select` fields use `VcMultivalue` or `VcCheckboxGroup`
279
+ - [ ] `image` fields use `VcImageUpload`, `gallery` uses `VcGallery`, `file` uses `VcFileUpload`
280
+ - [ ] `rating` fields use `VcRating`, `range` uses `VcSlider`, `color` uses `VcColorInput`
222
281
  - [ ] `array` fields render as inline `VcDataTable` in a separate `VcRow`
282
+ - [ ] Components without `Field` wrapper: `VcSwitch`, `VcCheckbox`, `VcMultivalue`, `VcRating`, `VcSlider`, `VcColorInput`, `VcImageUpload`, `VcGallery`
283
+ - [ ] Wide components (`VcTextarea`, `VcEditor`, `VcGallery`, `VcDataTable`) span full width, not in 2-col grid
223
284
  - [ ] `useModificationTracker` is used in composable (not blade)
224
285
  - [ ] `resetModificationState()` called after fetch AND after create/update
225
286
  - [ ] No `useI18n` import in the composable file
@@ -1 +1 @@
1
- Synced from framework at commit 785583032 on 2026-03-23T13:30:02.724Z
1
+ Synced from framework at commit 99a2f022b on 2026-03-25T11:57:14.169Z
@@ -6,10 +6,17 @@ Auto-generated TypeScript API client for the VirtoCommerce Platform REST API. Ge
6
6
 
7
7
  Communicating with the VirtoCommerce platform backend requires typed HTTP client classes that match the platform's REST endpoints. Rather than manually writing fetch calls or Axios requests, the framework provides auto-generated client classes that handle URL construction, request serialization, response deserialization, and authentication token injection.
8
8
 
9
- Each API client class extends `AuthApiBase`, which automatically attaches the Bearer token to every request. The clients are generated by NSwag from the platform's Swagger/OpenAPI specification and should not be edited manually -- any manual changes will be overwritten during regeneration.
9
+ Each API client class extends `AuthApiBase`, which automatically attaches the Bearer token to every request. Data model types (DTOs, Commands, Queries) are generated as **interfaces** (not classes), so they cannot be instantiated with `new` -- use object literals with type annotations instead. The clients are generated by NSwag from the platform's Swagger/OpenAPI specification and should not be edited manually -- any manual changes will be overwritten during regeneration.
10
10
 
11
11
  **File:** `platform.ts` (auto-generated, do not edit manually)
12
12
 
13
+ ## When to Use
14
+
15
+ - Call platform-level REST endpoints (security, settings, notifications, modules, dynamic properties) from typed TypeScript clients
16
+ - Access platform DTOs (`ApplicationUser`, `PushNotification`, `Role`, `Permission`, etc.) as typed interfaces
17
+ - Combine with `useApiClient` for automatic token management and caching
18
+ - When NOT to use: for domain-module APIs (orders, catalog, inventory) -- use their own generated clients instead; for third-party APIs -- use `fetch` or Axios directly
19
+
13
20
  ## API Clients
14
21
 
15
22
  All clients extend `AuthApiBase` and accept an optional `baseUrl` and `http` fetch implementation.
@@ -30,10 +37,10 @@ All clients extend `AuthApiBase` and accept an optional `baseUrl` and `http` fet
30
37
  | `SecurityClient` | User management, roles, permissions, password operations |
31
38
  | `SettingClient` | Platform settings CRUD |
32
39
 
33
- ## Key DTOs
40
+ ## Key DTOs (Interfaces)
34
41
 
35
- | Class | Description |
36
- |-------|-------------|
42
+ | Interface | Description |
43
+ |-----------|-------------|
37
44
  | `PushNotification` | Push notification payload: `id`, `title`, `notifyType`, `isNew`, `finished`, etc. |
38
45
  | `PushNotificationSearchCriteria` | Search criteria for notification queries |
39
46
  | `ApplicationUser` | Platform user with roles, permissions, logins |
@@ -68,7 +75,7 @@ The `getBaseUrl` method always returns an empty string, meaning API calls are re
68
75
  import { PushNotificationClient, PushNotificationSearchCriteria } from "@vc-shell/framework";
69
76
 
70
77
  const client = new PushNotificationClient();
71
- const criteria = new PushNotificationSearchCriteria({ take: 20, skip: 0 });
78
+ const criteria: PushNotificationSearchCriteria = { take: 20, skip: 0 };
72
79
  const result = await client.searchPushNotification(criteria);
73
80
 
74
81
  console.log(`Found ${result.totalCount} notifications`);
@@ -131,10 +138,10 @@ const client = new SettingClient();
131
138
  const settings = await client.getValues(["VirtoCommerce.Notifications.SendGrid.ApiKey"]);
132
139
 
133
140
  // Update a setting
134
- const entry = new ObjectSettingEntry({
141
+ const entry: ObjectSettingEntry = {
135
142
  name: "MyModule.SomeSetting",
136
143
  value: "new-value",
137
- });
144
+ };
138
145
  await client.update([entry]);
139
146
  ```
140
147
 
@@ -10,6 +10,12 @@ Blade navigation manages an ordered stack of blade descriptors (plain data objec
10
10
  2. **useBladeMessaging** -- inter-blade communication: parent-child method calls.
11
11
  3. **useBladeNavigation** (adapter) -- legacy API adapter that maps the old index-based API onto the new ID-based stack and messaging systems.
12
12
 
13
+ ## When to Use
14
+
15
+ - Build or extend the blade navigation infrastructure itself (plugin authors, framework internals)
16
+ - Need direct access to the blade stack state machine (`useBladeStack`) or inter-blade messaging (`useBladeMessaging`)
17
+ - When NOT to use: for everyday blade operations in modules -- prefer `useBlade()` from `core/composables/useBlade/`, which wraps these low-level composables with a cleaner, context-aware API
18
+
13
19
  ## Exports
14
20
 
15
21
  ```typescript
@@ -2,6 +2,12 @@
2
2
 
3
3
  Creates a typed API client instance for communicating with VirtoCommerce platform APIs. The composable accepts a generated client class constructor and returns an async factory function that produces a configured, authenticated client. Base URL resolution and authentication token injection are handled automatically.
4
4
 
5
+ ## When to Use
6
+
7
+ - Instantiate a generated VirtoCommerce API client with automatic authentication and base-URL resolution
8
+ - Pair with `useAsync` for loading/error state on every API call in a blade or composable
9
+ - When NOT to use: for third-party or non-platform APIs that do not extend `AuthApiBase` -- use `fetch` or Axios directly
10
+
5
11
  ## Quick Start
6
12
 
7
13
  ```typescript
@@ -0,0 +1,149 @@
1
+ # useAssetsManager
2
+
3
+ High-level composable for managing asset arrays (images, documents, files). Wraps upload, remove, reorder, and metadata editing operations, mutating a reactive ref directly — no manual wiring needed.
4
+
5
+ Replaces the low-level `useAssets()` composable which required building handler objects manually.
6
+
7
+ ## When to Use
8
+
9
+ - Manage a list of images/assets on an entity (product, offer, user profile, seller)
10
+ - Need upload, remove, reorder, and edit-metadata on an asset array
11
+ - Want to bind directly to `VcGallery` events without boilerplate
12
+
13
+ ## API
14
+
15
+ ```typescript
16
+ import { useAssetsManager } from "@vc-shell/framework";
17
+
18
+ const assets = useAssetsManager(ref, options);
19
+ ```
20
+
21
+ ### Parameters
22
+
23
+ | Parameter | Type | Description |
24
+ |---|---|---|
25
+ | `source` | `Ref<AssetLike[] \| undefined \| null>` | Reactive ref to the asset array. Accepts `ref()`, `toRef()`, or `computed({ get, set })`. `undefined`/`null` values are treated as empty array. The composable holds an internal copy and syncs both ways. |
26
+ | `options` | `UseAssetsManagerOptions` | Configuration (see below) |
27
+
28
+ ### Options
29
+
30
+ | Option | Type | Required | Description |
31
+ |---|---|---|---|
32
+ | `uploadPath` | `() => string` | Yes | Upload destination path (function — evaluated at upload time) |
33
+ | `confirmRemove` | `() => Promise<boolean> \| boolean` | No | Called before remove. Return `false` to cancel. Omit for silent remove. |
34
+ | `assetKey` | `string` | No | Key for matching items (default: `"url"`) |
35
+ | `concurrency` | `number` | No | Max concurrent uploads (default: 4) |
36
+
37
+ ### Return (`UseAssetsManagerReturn`)
38
+
39
+ | Property | Type | Description |
40
+ |---|---|---|
41
+ | `items` | `Ref<AssetLike[]>` | Reactive asset array (internal ref, safe to bind to templates) |
42
+ | `upload` | `(files: FileList, startingSortOrder?: number) => Promise<void>` | Upload files, append to array |
43
+ | `remove` | `(item: AssetLike) => Promise<void>` | Remove single item (with confirmation if configured) |
44
+ | `removeMany` | `(items: AssetLike[]) => Promise<void>` | Remove multiple items (one confirmation for batch) |
45
+ | `reorder` | `(items: AssetLike[]) => void` | Replace array with new order |
46
+ | `updateItem` | `(item: AssetLike) => void` | Update single item metadata by `assetKey` |
47
+ | `loading` | `ComputedRef<boolean>` | True during upload |
48
+
49
+ ## Usage Examples
50
+
51
+ ### Basic — gallery with confirmation
52
+
53
+ ```typescript
54
+ import { useAssetsManager } from "@vc-shell/framework";
55
+ import { toRef } from "vue";
56
+
57
+ const assets = useAssetsManager(
58
+ computed({
59
+ get: () => offer.value.images ?? [],
60
+ set: (val) => { offer.value.images = val; },
61
+ }),
62
+ {
63
+ uploadPath: () => `offers/${offer.value?.id ?? "new"}`,
64
+ confirmRemove: () => showConfirmation(t("OFFERS.ALERTS.IMAGE_DELETE")),
65
+ },
66
+ );
67
+ ```
68
+
69
+ ```html
70
+ <VcGallery
71
+ :images="assets.items.value"
72
+ @upload="assets.upload"
73
+ @sort="assets.reorder"
74
+ @remove="assets.remove"
75
+ @edit="onGalleryItemEdit"
76
+ />
77
+ ```
78
+
79
+ ### Single image (photo/logo)
80
+
81
+ Wrap a single value in a computed array:
82
+
83
+ ```typescript
84
+ const photoAssets = computed({
85
+ get: () => user.value?.iconUrl ? [{ url: user.value.iconUrl }] : [],
86
+ set: (val) => { user.value!.iconUrl = val[0]?.url; },
87
+ });
88
+
89
+ const photo = useAssetsManager(photoAssets, {
90
+ uploadPath: () => `users/${user.value?.id}`,
91
+ });
92
+ ```
93
+
94
+ ### Passing to AssetsManager blade
95
+
96
+ When passing `useAssetsManager` through blade options, **always wrap with `markRaw()`**:
97
+
98
+ ```typescript
99
+ import { markRaw } from "vue";
100
+ import { useBlade, useAssetsManager } from "@vc-shell/framework";
101
+
102
+ const { openBlade } = useBlade();
103
+
104
+ const assets = useAssetsManager(productAssetsRef, {
105
+ uploadPath: () => `/catalog/${product.value?.id}`,
106
+ confirmRemove: () => showConfirmation("Delete selected assets?"),
107
+ });
108
+
109
+ openBlade({
110
+ name: "AssetsManager",
111
+ options: {
112
+ manager: markRaw(assets), // IMPORTANT: prevents reactive proxy unwrap
113
+ disabled: !canEdit.value,
114
+ },
115
+ });
116
+ ```
117
+
118
+ **Why `markRaw`?** Blade descriptors are stored in `ref<BladeDescriptor[]>()`, which creates a deep reactive proxy. Vue auto-unwraps `Ref` values inside reactive objects — so `manager.items` would become a plain array instead of `Ref<AssetLike[]>`, breaking `.value` access. `markRaw()` tells Vue to skip this object, keeping Refs intact.
119
+
120
+ ## Reactivity Model
121
+
122
+ The composable holds an **internal ref** (`_items`) that is the source of truth for the UI:
123
+
124
+ 1. **Source → internal**: A `watch` syncs changes from the source ref (e.g., when parent reloads data)
125
+ 2. **Mutations → internal → source**: Every operation (`upload`, `remove`, `reorder`, `updateItem`) updates `_items` first, then writes back to the source via `_sync()`
126
+
127
+ This two-way sync avoids reactivity issues when the source is a `WritableComputed` wrapping deeply nested properties (e.g., `item.value.productData.assets`).
128
+
129
+ ## Types
130
+
131
+ ### `AssetLike`
132
+
133
+ Minimal structural contract compatible with any domain image type:
134
+
135
+ ```typescript
136
+ interface AssetLike {
137
+ url?: string;
138
+ name?: string;
139
+ sortOrder?: number;
140
+ [key: string]: any; // compatible with Image, ProductImage, Asset, etc.
141
+ }
142
+ ```
143
+
144
+ ## Related
145
+
146
+ - `framework/modules/assets-manager/` — AssetsManager blade (table UI for managing assets)
147
+ - `framework/modules/assets/` — AssetsDetails blade (single asset editing)
148
+ - `framework/ui/components/organisms/vc-gallery/` — VcGallery component
149
+ - `framework/core/composables/useAssets/` — deprecated low-level composable
@@ -8,6 +8,13 @@ When the returned `action` is called, `useAsync` automatically:
8
8
  3. On failure: parses the error into a `DisplayableError`, stores it in `error`, logs it, optionally shows a toast notification, and re-throws
9
9
  4. Sets `loading` to `false` (success or failure)
10
10
 
11
+ ## When to Use
12
+
13
+ - Wrap any async operation (API calls, saves, deletes) with reactive `loading` and `error` state
14
+ - Get automatic error parsing, toast notifications, and re-throw behavior for free
15
+ - Combine with `useLoading` when a blade has multiple independent async operations
16
+ - When NOT to use: for synchronous logic -- just use a regular function; for fire-and-forget operations where you do not need loading/error tracking
17
+
11
18
  ## Quick Start
12
19
 
13
20
  ```typescript
@@ -4,6 +4,13 @@ Unified composable for blade navigation, identity, communication, guards, and er
4
4
 
5
5
  `useBlade()` works **everywhere**: inside blades it provides the full API (identity, navigation, communication, guards, errors); outside blades (dashboard cards, notification templates, composables) it provides navigation only (`openBlade`). Blade-specific methods throw a descriptive runtime error if called outside blade context.
6
6
 
7
+ ## When to Use
8
+
9
+ - Open, close, replace, or cover blades from any component (blade, dashboard widget, toolbar handler)
10
+ - Read blade identity (`param`, `options`, `id`) or register close guards inside a blade
11
+ - Communicate between parent and child blades via `callParent` / `exposeToChildren`
12
+ - When NOT to use: for low-level stack manipulation or custom navigation plugins -- use the blade-navigation composables (`useBladeStack`, `useBladeMessaging`) directly
13
+
7
14
  ## Quick Start
8
15
 
9
16
  ```vue