@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.
- package/CHANGELOG.md +12 -0
- package/bin/knowledge-stats.cjs +135 -0
- package/bin/sync-docs.cjs +62 -0
- package/package.json +5 -1
- package/runtime/VERSION +1 -1
- package/runtime/agents/details-blade-generator.md +75 -14
- package/runtime/knowledge/docs/_BUILD_HASH.md +1 -1
- package/runtime/knowledge/docs/core/api/platform.docs.md +14 -7
- package/runtime/knowledge/docs/core/blade-navigation/blade-nav-composables.docs.md +6 -0
- package/runtime/knowledge/docs/core/composables/useApiClient/useApiClient.docs.md +6 -0
- package/runtime/knowledge/docs/core/composables/useAssetsManager/useAssetsManager.docs.md +149 -0
- package/runtime/knowledge/docs/core/composables/useAsync/useAsync.docs.md +7 -0
- package/runtime/knowledge/docs/core/composables/useBlade/useBlade.docs.md +7 -0
- package/runtime/knowledge/docs/core/composables/useBladeWidgets.docs.md +164 -9
- package/runtime/knowledge/docs/core/composables/useDashboard/useDashboard.docs.md +6 -0
- package/runtime/knowledge/docs/core/composables/useMenuService/useMenuService.docs.md +6 -0
- package/runtime/knowledge/docs/core/composables/usePermissions/usePermissions.docs.md +6 -0
- package/runtime/knowledge/docs/core/composables/useToolbar/useToolbar.docs.md +6 -0
- package/runtime/knowledge/docs/core/composables/useWidgets/useWidgets.docs.md +2 -2
- package/runtime/knowledge/docs/core/plugins/ai-agent/ai-agent.docs.md +6 -0
- package/runtime/knowledge/docs/core/plugins/extension-points/extension-points.docs.md +6 -0
- package/runtime/knowledge/docs/core/plugins/global-error-handler/global-error-handler.docs.md +6 -0
- package/runtime/knowledge/docs/core/plugins/i18n/i18n.docs.md +74 -0
- package/runtime/knowledge/docs/core/plugins/modularity/modularity.docs.md +6 -0
- package/runtime/knowledge/docs/core/plugins/permissions/permissions.docs.md +6 -0
- package/runtime/knowledge/docs/core/plugins/signalR/signalR.docs.md +6 -0
- package/runtime/knowledge/docs/core/plugins/validation/validation.docs.md +6 -0
- package/runtime/knowledge/docs/injection-keys.docs.md +1 -1
- package/runtime/knowledge/docs/modules/assets-manager/assets-manager.docs.md +29 -33
- package/runtime/knowledge/docs/shell/components/logout-button/logout-button.docs.md +3 -3
- package/runtime/knowledge/docs/ui/components/atoms/vc-button/vc-button.docs.md +11 -0
- package/runtime/knowledge/docs/ui/components/atoms/vc-card/vc-card.docs.md +11 -0
- package/runtime/knowledge/docs/ui/components/organisms/vc-blade/vc-blade.docs.md +11 -0
- package/runtime/knowledge/docs/ui/components/organisms/vc-table/vc-data-table.docs.md +12 -0
- package/runtime/knowledge/index.md +60 -0
- package/runtime/knowledge/patterns/assets-management.md +213 -0
- package/runtime/knowledge/patterns/child-blade-flow.md +277 -0
- package/runtime/knowledge/patterns/details-blade-pattern.md +350 -3
- package/runtime/knowledge/patterns/extension-points-usage.md +308 -0
- package/runtime/knowledge/patterns/form-validation.md +377 -0
- package/runtime/knowledge/patterns/multilanguage-fields.md +239 -0
- package/runtime/knowledge/patterns/signalr-notifications.md +237 -0
- package/runtime/vc-app.md +44 -5
- package/runtime/knowledge/docs/core/composables/useWidget/useWidget.docs.md +0 -159
- 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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
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
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
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
|
-
|
|
|
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 =
|
|
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 =
|
|
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
|