@morya-ui/setup 0.3.3 → 0.3.4
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/README.md +103 -103
- package/bin/morya-ui-setup.js +14 -14
- package/catalog/skills.json +46 -46
- package/package.json +5 -3
- package/src/cli.mjs +335 -335
- package/src/copy-template.mjs +78 -78
- package/src/fs-utils.mjs +24 -24
- package/src/install.mjs +63 -63
- package/src/mcp.mjs +50 -50
- package/src/package-json.mjs +30 -30
- package/src/skills.mjs +223 -223
- package/src/styles.mjs +119 -119
- package/template/.agents/skills/morya-ui-pages/SKILL.md +184 -164
- package/template/.agents/skills/morya-ui-pages/evals/evals.json +89 -89
- package/template/.agents/skills/morya-ui-pages/references/component-index.md +99 -99
- package/template/.agents/skills/morya-ui-pages/references/decision-recipes.md +32 -0
- package/template/.agents/skills/morya-ui-pages/references/design-system.md +101 -100
- package/template/.agents/skills/morya-ui-pages/references/feedback.md +68 -68
- package/template/.agents/skills/morya-ui-pages/references/optional-companions.md +107 -34
- package/template/.agents/skills/morya-ui-pages/references/page-layouts.md +141 -119
- package/template/.agents/skills/morya-ui-pages/references/review-checklist.md +64 -61
- package/template/.agents/skills/morya-ui-pages/references/style-presets.md +73 -0
- package/template/.agents/skills/morya-ui-pages/references/surfaces.md +91 -89
- package/template/.agents/skills/morya-ui-pages/references/visual-craft.md +221 -196
- package/template/.cursor/rules/coding-style.mdc +41 -41
- package/template/.cursor/rules/component-usage.mdc +41 -41
- package/template/.cursor/rules/design-system.mdc +17 -17
- package/template/.cursor/rules/page-layout.mdc +97 -78
- package/template/DESIGN.md +59 -59
- package/template/scripts/check-raw-colors.mjs +74 -74
- package/LICENSE +0 -21
package/src/styles.mjs
CHANGED
|
@@ -1,119 +1,119 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
-
import { join, resolve } from 'node:path'
|
|
3
|
-
|
|
4
|
-
const STYLE_IMPORT = "import 'morya-ui/styles.css'"
|
|
5
|
-
const STYLE_MARKER = 'morya-ui/styles.css'
|
|
6
|
-
|
|
7
|
-
const CANDIDATES = [
|
|
8
|
-
'src/main.ts',
|
|
9
|
-
'src/main.js',
|
|
10
|
-
'src/main.tsx',
|
|
11
|
-
'src/main.jsx',
|
|
12
|
-
'main.ts',
|
|
13
|
-
'main.js',
|
|
14
|
-
'src/app.ts',
|
|
15
|
-
'src/app.js',
|
|
16
|
-
]
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @param {string} cwd
|
|
20
|
-
* @returns {string | null} absolute path
|
|
21
|
-
*/
|
|
22
|
-
export function findEntryFile(cwd) {
|
|
23
|
-
for (const rel of CANDIDATES) {
|
|
24
|
-
const full = join(cwd, rel)
|
|
25
|
-
if (existsSync(full)) return full
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const indexHtml = join(cwd, 'index.html')
|
|
29
|
-
if (existsSync(indexHtml)) {
|
|
30
|
-
const html = readFileSync(indexHtml, 'utf8')
|
|
31
|
-
const match = html.match(/<script[^>]*type=["']module["'][^>]*src=["']([^"']+)["']/i)
|
|
32
|
-
|| html.match(/<script[^>]*src=["']([^"']+)["'][^>]*type=["']module["']/i)
|
|
33
|
-
if (match?.[1]) {
|
|
34
|
-
const src = match[1].replace(/^\//, '')
|
|
35
|
-
const full = resolve(cwd, src)
|
|
36
|
-
if (existsSync(full)) return full
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return null
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Insert an import after the last leading import, or at top.
|
|
45
|
-
* @param {string} source
|
|
46
|
-
* @param {string} importLine
|
|
47
|
-
* @param {string} marker substring that means "already present"
|
|
48
|
-
*/
|
|
49
|
-
export function injectImportLine(source, importLine, marker) {
|
|
50
|
-
if (source.includes(marker)) {
|
|
51
|
-
return { source, changed: false, reason: 'already-present' }
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const lines = source.split(/\r?\n/)
|
|
55
|
-
let lastImportIndex = -1
|
|
56
|
-
for (let i = 0; i < lines.length; i++) {
|
|
57
|
-
const line = lines[i].trim()
|
|
58
|
-
if (!line) {
|
|
59
|
-
if (lastImportIndex >= 0) continue
|
|
60
|
-
continue
|
|
61
|
-
}
|
|
62
|
-
if (line.startsWith('//') || line.startsWith('/*') || line.startsWith('*')) {
|
|
63
|
-
if (lastImportIndex < 0) continue
|
|
64
|
-
break
|
|
65
|
-
}
|
|
66
|
-
if (/^import\s/.test(line) || /^import["']/.test(line)) {
|
|
67
|
-
lastImportIndex = i
|
|
68
|
-
continue
|
|
69
|
-
}
|
|
70
|
-
break
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (lastImportIndex >= 0) {
|
|
74
|
-
lines.splice(lastImportIndex + 1, 0, importLine)
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
let insertAt = 0
|
|
78
|
-
if (lines[0]?.startsWith('#!')) insertAt = 1
|
|
79
|
-
lines.splice(insertAt, 0, importLine, '')
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return { source: lines.join('\n'), changed: true }
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* @param {string} source
|
|
87
|
-
* @deprecated use injectImportLine
|
|
88
|
-
*/
|
|
89
|
-
export function injectStyleImport(source) {
|
|
90
|
-
return injectImportLine(source, STYLE_IMPORT, STYLE_MARKER)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* @returns {{
|
|
95
|
-
* action: 'injected' | 'skipped' | 'missing-entry',
|
|
96
|
-
* path?: string,
|
|
97
|
-
* reason?: string,
|
|
98
|
-
* dryRun?: boolean
|
|
99
|
-
* }}
|
|
100
|
-
*/
|
|
101
|
-
export function ensureStylesImport(cwd, { dryRun = false } = {}) {
|
|
102
|
-
const entry = findEntryFile(cwd)
|
|
103
|
-
if (!entry) {
|
|
104
|
-
return { action: 'missing-entry', reason: 'no-entry-found' }
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const original = readFileSync(entry, 'utf8')
|
|
108
|
-
const { source, changed, reason } = injectImportLine(original, STYLE_IMPORT, STYLE_MARKER)
|
|
109
|
-
|
|
110
|
-
if (!changed) {
|
|
111
|
-
return { action: 'skipped', path: entry, reason: reason || 'already-present' }
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (!dryRun) {
|
|
115
|
-
writeFileSync(entry, source, 'utf8')
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return { action: 'injected', path: entry, dryRun }
|
|
119
|
-
}
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { join, resolve } from 'node:path'
|
|
3
|
+
|
|
4
|
+
const STYLE_IMPORT = "import 'morya-ui/styles.css'"
|
|
5
|
+
const STYLE_MARKER = 'morya-ui/styles.css'
|
|
6
|
+
|
|
7
|
+
const CANDIDATES = [
|
|
8
|
+
'src/main.ts',
|
|
9
|
+
'src/main.js',
|
|
10
|
+
'src/main.tsx',
|
|
11
|
+
'src/main.jsx',
|
|
12
|
+
'main.ts',
|
|
13
|
+
'main.js',
|
|
14
|
+
'src/app.ts',
|
|
15
|
+
'src/app.js',
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} cwd
|
|
20
|
+
* @returns {string | null} absolute path
|
|
21
|
+
*/
|
|
22
|
+
export function findEntryFile(cwd) {
|
|
23
|
+
for (const rel of CANDIDATES) {
|
|
24
|
+
const full = join(cwd, rel)
|
|
25
|
+
if (existsSync(full)) return full
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const indexHtml = join(cwd, 'index.html')
|
|
29
|
+
if (existsSync(indexHtml)) {
|
|
30
|
+
const html = readFileSync(indexHtml, 'utf8')
|
|
31
|
+
const match = html.match(/<script[^>]*type=["']module["'][^>]*src=["']([^"']+)["']/i)
|
|
32
|
+
|| html.match(/<script[^>]*src=["']([^"']+)["'][^>]*type=["']module["']/i)
|
|
33
|
+
if (match?.[1]) {
|
|
34
|
+
const src = match[1].replace(/^\//, '')
|
|
35
|
+
const full = resolve(cwd, src)
|
|
36
|
+
if (existsSync(full)) return full
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Insert an import after the last leading import, or at top.
|
|
45
|
+
* @param {string} source
|
|
46
|
+
* @param {string} importLine
|
|
47
|
+
* @param {string} marker substring that means "already present"
|
|
48
|
+
*/
|
|
49
|
+
export function injectImportLine(source, importLine, marker) {
|
|
50
|
+
if (source.includes(marker)) {
|
|
51
|
+
return { source, changed: false, reason: 'already-present' }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const lines = source.split(/\r?\n/)
|
|
55
|
+
let lastImportIndex = -1
|
|
56
|
+
for (let i = 0; i < lines.length; i++) {
|
|
57
|
+
const line = lines[i].trim()
|
|
58
|
+
if (!line) {
|
|
59
|
+
if (lastImportIndex >= 0) continue
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
if (line.startsWith('//') || line.startsWith('/*') || line.startsWith('*')) {
|
|
63
|
+
if (lastImportIndex < 0) continue
|
|
64
|
+
break
|
|
65
|
+
}
|
|
66
|
+
if (/^import\s/.test(line) || /^import["']/.test(line)) {
|
|
67
|
+
lastImportIndex = i
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
break
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (lastImportIndex >= 0) {
|
|
74
|
+
lines.splice(lastImportIndex + 1, 0, importLine)
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
let insertAt = 0
|
|
78
|
+
if (lines[0]?.startsWith('#!')) insertAt = 1
|
|
79
|
+
lines.splice(insertAt, 0, importLine, '')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { source: lines.join('\n'), changed: true }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {string} source
|
|
87
|
+
* @deprecated use injectImportLine
|
|
88
|
+
*/
|
|
89
|
+
export function injectStyleImport(source) {
|
|
90
|
+
return injectImportLine(source, STYLE_IMPORT, STYLE_MARKER)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @returns {{
|
|
95
|
+
* action: 'injected' | 'skipped' | 'missing-entry',
|
|
96
|
+
* path?: string,
|
|
97
|
+
* reason?: string,
|
|
98
|
+
* dryRun?: boolean
|
|
99
|
+
* }}
|
|
100
|
+
*/
|
|
101
|
+
export function ensureStylesImport(cwd, { dryRun = false } = {}) {
|
|
102
|
+
const entry = findEntryFile(cwd)
|
|
103
|
+
if (!entry) {
|
|
104
|
+
return { action: 'missing-entry', reason: 'no-entry-found' }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const original = readFileSync(entry, 'utf8')
|
|
108
|
+
const { source, changed, reason } = injectImportLine(original, STYLE_IMPORT, STYLE_MARKER)
|
|
109
|
+
|
|
110
|
+
if (!changed) {
|
|
111
|
+
return { action: 'skipped', path: entry, reason: reason || 'already-present' }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!dryRun) {
|
|
115
|
+
writeFileSync(entry, source, 'utf8')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { action: 'injected', path: entry, dryRun }
|
|
119
|
+
}
|
|
@@ -1,164 +1,184 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: morya-ui-pages
|
|
3
|
-
description: >
|
|
4
|
-
Build, redesign, critique, or polish any Vue 3 UI surface that should use the
|
|
5
|
-
morya-ui component library — admin CRUD (list, form, dashboard, detail,
|
|
6
|
-
settings), auth and onboarding, empty and error states, wizards, overlays,
|
|
7
|
-
marketing/landing and pricing pages, docs chrome, and hybrid product UI.
|
|
8
|
-
Trigger on: morya-ui, M* components, --m-* tokens, golden pages, 后台页,
|
|
9
|
-
列表页, 表单页, 仪表盘, 登录页, 注册, 空状态, 向导, 落地页, 官网, landing,
|
|
10
|
-
login, dashboard, settings, onboarding, or “用组件库做页面”. Prefer this
|
|
11
|
-
skill over generic frontend-design, impeccable, or ui-ux-pro-max when the
|
|
12
|
-
implementation stack is morya-ui; those companions may
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
|
32
|
-
|
|
|
33
|
-
| **
|
|
34
|
-
| **
|
|
35
|
-
| **
|
|
36
|
-
| **
|
|
37
|
-
| **
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
- **
|
|
57
|
-
- **
|
|
58
|
-
- **
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
- **
|
|
105
|
-
- **
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
**
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
|
155
|
-
|
|
|
156
|
-
|
|
|
157
|
-
|
|
|
158
|
-
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
1
|
+
---
|
|
2
|
+
name: morya-ui-pages
|
|
3
|
+
description: >
|
|
4
|
+
Build, redesign, critique, or polish any Vue 3 UI surface that should use the
|
|
5
|
+
morya-ui component library — admin CRUD (list, form, dashboard, detail,
|
|
6
|
+
settings), auth and onboarding, empty and error states, wizards, overlays,
|
|
7
|
+
marketing/landing and pricing pages, docs chrome, and hybrid product UI.
|
|
8
|
+
Trigger on: morya-ui, M* components, --m-* tokens, golden pages, 后台页,
|
|
9
|
+
列表页, 表单页, 仪表盘, 登录页, 注册, 空状态, 向导, 落地页, 官网, landing,
|
|
10
|
+
login, dashboard, settings, onboarding, or “用组件库做页面”. Prefer this
|
|
11
|
+
skill over generic frontend-design, impeccable, or ui-ux-pro-max when the
|
|
12
|
+
implementation stack is morya-ui; those companions may deepen taste and polish
|
|
13
|
+
after structure/contract are fixed (see references/optional-companions.md).
|
|
14
|
+
Do not use for backend-only work or for authoring new components inside the
|
|
15
|
+
morya-ui library source itself.
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Morya UI Pages
|
|
19
|
+
|
|
20
|
+
Guide agents that **consume morya-ui** across the full product surface — not only admin CRUD.
|
|
21
|
+
|
|
22
|
+
Two layers always apply:
|
|
23
|
+
|
|
24
|
+
1. **Contract** — only `M*` controls, `--m-*` tokens, real APIs (MCP/docs). Never invent props or mix UI kits.
|
|
25
|
+
2. **Craft** — pick the right surface pattern, then apply intentional visual direction (distilled from Frontend Design / Impeccable / UI-UX-Pro-Max ideas). Admin golden pages stay disciplined; expressive surfaces (landing, auth brand moments, empty states) may take a justified aesthetic risk — still on-token and on-component.
|
|
26
|
+
|
|
27
|
+
When companions conflict with this skill or project `DESIGN.md`, **this skill wins**.
|
|
28
|
+
|
|
29
|
+
## Surface map (pick one first)
|
|
30
|
+
|
|
31
|
+
| Lane | Surfaces | Primary references |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| **Ops** | list, form, dashboard, detail, settings, filter drawer, CRUD dialog | [page-layouts.md](references/page-layouts.md), golden pages |
|
|
34
|
+
| **Account** | login, register, invite, forgot/reset password, profile | [surfaces.md](references/surfaces.md) § Account |
|
|
35
|
+
| **Flow** | onboarding, empty state, wizard/stepper, success/result | [surfaces.md](references/surfaces.md) § Flow |
|
|
36
|
+
| **System** | 404 / error, permission denied, maintenance | [surfaces.md](references/surfaces.md) § System |
|
|
37
|
+
| **Express** | marketing landing, pricing, feature showcase, docs marketing chrome | [surfaces.md](references/surfaces.md) § Express + [visual-craft.md](references/visual-craft.md) |
|
|
38
|
+
| **Overlay** | dialog, drawer, popover, command menu as the main UI | [surfaces.md](references/surfaces.md) § Overlay |
|
|
39
|
+
|
|
40
|
+
Unclear brief → ask **one** short question, or default: Ops → closest golden page; public marketing → Express.
|
|
41
|
+
|
|
42
|
+
Full taxonomy: [references/surfaces.md](references/surfaces.md).
|
|
43
|
+
|
|
44
|
+
## Prerequisites
|
|
45
|
+
|
|
46
|
+
1. `morya-ui` installed; `morya-ui/styles.css` imported.
|
|
47
|
+
2. Prefer `@morya-ui/mcp` — never invent prop / event / slot names.
|
|
48
|
+
3. Golden pages, component APIs, and feedback rules come from `@morya-ui/mcp`. Without MCP, use this skill's `references/`. Project `DESIGN.md` overrides generic taste when the AI pack is merged.
|
|
49
|
+
|
|
50
|
+
## Workflow
|
|
51
|
+
|
|
52
|
+
### 1. Pin subject, audience, surface, job, **style**
|
|
53
|
+
|
|
54
|
+
State explicitly (even briefly in thinking):
|
|
55
|
+
|
|
56
|
+
- **Subject** — product / domain vernacular (not generic “SaaS”)
|
|
57
|
+
- **Audience** — who uses this screen
|
|
58
|
+
- **Surface** — from the map above
|
|
59
|
+
- **Single job** — what the first viewport must accomplish
|
|
60
|
+
- **Style direction** — resolve in this order (see [style-presets.md](references/style-presets.md)):
|
|
61
|
+
1. User **reference** (screenshot / mock / existing page / “像 XX”) → extract cues, map to `--m-*` + `M*`
|
|
62
|
+
2. User **named preset** (`soft` / 柔和留白 / …) → apply it
|
|
63
|
+
3. **Prompt cues** (行业/气质) → infer a preset and name it
|
|
64
|
+
4. Still unclear → ask **one** question with 3–4 presets; if “直接写” → domain heuristic (**not** always `quiet`)
|
|
65
|
+
|
|
66
|
+
Golden pages lock **structure/API**, not the only aesthetic. Blindly cloning golden visuals makes pages feel stiff.
|
|
67
|
+
|
|
68
|
+
For Express / branded Account moments, also draft a tiny **design plan** (see [visual-craft.md](references/visual-craft.md)): palette roles mapped to `--m-*` (extend only if the project already customizes theme), type roles, layout concept, one signature element. For Ops, a named preset + Ops polish is enough unless the user asks for a redesign.
|
|
69
|
+
|
|
70
|
+
### 2. Load the smallest useful references
|
|
71
|
+
|
|
72
|
+
| Need | Prefer (MCP) | Else read |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| Ops pattern | `recommend_page` → **`get_golden_page`** (mirror structure; craft from style direction) | [page-layouts.md](references/page-layouts.md) |
|
|
75
|
+
| Style direction | `recommend_page({ style })` / **`list_style_presets`** / **`get_style_preset`** | [style-presets.md](references/style-presets.md) |
|
|
76
|
+
| Account / Express / empty / result | `recommend_page` → `get_golden_page` (`login-page` / `landing-page` / `empty-state` / `result-page`) | [surfaces.md](references/surfaces.md) |
|
|
77
|
+
| Visual direction | — | [visual-craft.md](references/visual-craft.md) + [style-presets.md](references/style-presets.md) |
|
|
78
|
+
| Components / **API truth** | `search` / **`get_component`** / `get_example` / **`recommend_component`** (includes L2 recipes) | [decision-recipes.md](references/decision-recipes.md) + [component-index.md](references/component-index.md) |
|
|
79
|
+
| Tokens / rules | `get_design_rules` | [design-system.md](references/design-system.md) |
|
|
80
|
+
| Snippet | `get_page_snippet` | golden / surface excerpt |
|
|
81
|
+
| Feedback API | — | [feedback.md](references/feedback.md) |
|
|
82
|
+
| **Required checks** | **`validate_usage`** (every `M*` you used) + `validate_page` | [review-checklist.md](references/review-checklist.md) |
|
|
83
|
+
|
|
84
|
+
### 3. Compose
|
|
85
|
+
|
|
86
|
+
**Ops:** mirror golden-page **block order**; apply the resolved **style preset** (or reference cues) for density/chrome/copy; prefer `MPage*` over custom chrome. Do not freeze every Ops page into identical quiet chrome. List craft variants: `list-page` (soft structure), `list-page-dense`, `list-page-rail` — `recommend_page({ style })` routes them.
|
|
87
|
+
|
|
88
|
+
**Account / Flow / System:** centered or split shells with `MCard` / `MForm` / `MEmpty` / `MResult` (see surfaces); keep controls as `M*`. Persistent form errors use field `errorMessage` or a token-styled `role="alert"` — `<MMessage>` is the `message` host, not an inline alert.
|
|
89
|
+
|
|
90
|
+
**Express:** hero + sections with intentional hierarchy; interactive bits still `MButton` / `MTag` / etc.; atmosphere via layout, motion, and tokens — not a second component library.
|
|
91
|
+
|
|
92
|
+
**Overlay:** build the host page lightly; put the real job inside `MDialog` / `MDrawer` / `MCommandMenu`.
|
|
93
|
+
|
|
94
|
+
### 4. Wire real API usage
|
|
95
|
+
|
|
96
|
+
- Import from `morya-ui` (or documented subpath + style).
|
|
97
|
+
- **Selection + key props:** call MCP **`recommend_component`** (by query or `decision` id) and apply the returned **recipe** (`props` / `slots` / `events`) and **antiPatterns**. Without MCP, read [decision-recipes.md](references/decision-recipes.md). Then confirm full API with `get_component` / `get_example`.
|
|
98
|
+
- Forms: `MForm` + fields; `@submit` + `type="submit"` (or documented footer button pattern on `form-in-dialog`).
|
|
99
|
+
- Tables: `columns` + `rows` + `row-key`; `#cell-{key}`. There is no `data` prop. For full-viewport admin lists whose main job is one table, consider `MPageContent fill` + `MTable fill paginator`; skip `fill` for embedded/short/whole-page-scroll cases (see [page-layouts.md](references/page-layouts.md)).
|
|
100
|
+
- Enums → `MSelect` / `MTreeSelect`; action menus → `MDropdown`.
|
|
101
|
+
- Destructive → `MConfirmDialog` / `MConfirmPopup` (`confirm-choice`).
|
|
102
|
+
- Feedback → default **`message`**; `toast` only for summary+detail / async; persistent form errors → `errorMessage` / `role="alert"` (`feedback-choice`). See [feedback.md](references/feedback.md).
|
|
103
|
+
- Motion → intensity with `useMotion` (`full` / `reduced` / `none`); overlay enter/exit with `transition` prop or `createMoryaUI({ motion: { transitions } })` — do not invent a second animation stack. Prefer MCP / docs `motion` guide.
|
|
104
|
+
- **Before craft:** for each unfamiliar or newly written `M*` usage, call MCP **`get_component` / `get_example`**, then **`validate_usage`**. Fix every `unknown-prop` / `unknown-event` before delivery.
|
|
105
|
+
- `recommend_page(includeScaffold: true)` returns the **golden page source** when one exists — remap copy/data only; never treat generated fallback as the visual target.
|
|
106
|
+
|
|
107
|
+
### 5. Craft pass (always — lane-aware + companions)
|
|
108
|
+
|
|
109
|
+
Run **before** delivery. Do not stop at a structurally correct shell.
|
|
110
|
+
|
|
111
|
+
1. Resolve **style direction** ([style-presets.md](references/style-presets.md)) — reference → preset → cues → ask.
|
|
112
|
+
2. Apply lane craft from [visual-craft.md](references/visual-craft.md):
|
|
113
|
+
- **Ops / Operate:** style preset + § Ops polish (one primary, menu icons, `MStatus`, designed empty, no decorative cards).
|
|
114
|
+
- **Account / Flow:** one calm brand or empty-state cue from § Atmosphere; form errors via `errorMessage` / token `role="alert"`.
|
|
115
|
+
- **Express / Persuade:** short design plan + one signature; avoid AI-default looks; optional 1–2 token-only motions via `useMotion`.
|
|
116
|
+
3. **If companions are already installed** (see [optional-companions.md](references/optional-companions.md)):
|
|
117
|
+
- Express / brand → may load **`frontend-design`** for POV after contract is fixed
|
|
118
|
+
- User asks 更大胆/更克制/polish/audit → may load **`impeccable`** command (`bolder` / `quieter` / `polish` / …)
|
|
119
|
+
- Mood/industry keywords only → optional **`ui-ux-pro-max`** search, then map to tokens/preset
|
|
120
|
+
- a11y pass → optional **`fixing-accessibility`** after visual
|
|
121
|
+
- Max **one** visual companion per task; always remediate with `M*` + `--m-*`
|
|
122
|
+
4. If companions are **absent**, use distilled visual-craft / style-presets — do **not** block or ask to install mid-task.
|
|
123
|
+
5. **All lanes:** responsive, focus visible, domain-real copy. User **reference** overrides companion taste within the morya contract.
|
|
124
|
+
|
|
125
|
+
Named polish modes (`quieter` | `bolder` | `clarify` | `audit` | …): extra pass when the user asks to improve an existing screen.
|
|
126
|
+
|
|
127
|
+
### 6. Review
|
|
128
|
+
|
|
129
|
+
Use [review-checklist.md](references/review-checklist.md) (contract + craft sections).
|
|
130
|
+
|
|
131
|
+
**Required when MCP is available:**
|
|
132
|
+
|
|
133
|
+
1. `validate_usage` on the page (or per component) — API accuracy gate
|
|
134
|
+
2. `validate_page` — layout / token / contract advisories
|
|
135
|
+
|
|
136
|
+
Do not deliver with unresolved `unknown-prop` / `unknown-event`.
|
|
137
|
+
|
|
138
|
+
## Hard boundaries
|
|
139
|
+
|
|
140
|
+
- No second UI kit on the same surface.
|
|
141
|
+
- No hand-rolled table/modal when `MTable` / `MDialog` / `MDrawer` fit.
|
|
142
|
+
- No invented props / events / slots.
|
|
143
|
+
- No defaulting every success to `toast`.
|
|
144
|
+
- No substituting a generated scaffold for `get_golden_page` when a golden sample exists.
|
|
145
|
+
- Ops surfaces follow golden layouts first — do not replace them with marketing heroes.
|
|
146
|
+
- Express surfaces still use `M*` for controls and `--m-*` for color/space; do not introduce shadcn/Element/etc. stacks suggested by generic design skills.
|
|
147
|
+
- Soft-load companions only; never require Impeccable / UI-UX-Pro-Max / Frontend Design to be installed.
|
|
148
|
+
|
|
149
|
+
## Soft companions
|
|
150
|
+
|
|
151
|
+
If already installed in the consumer project, **combine** them after structure + contract (do not replace this skill):
|
|
152
|
+
|
|
153
|
+
| Companion | Load when | Role |
|
|
154
|
+
| --- | --- | --- |
|
|
155
|
+
| `frontend-design` | Express / branded Account moments | Distinctive design plan + signature (taste) |
|
|
156
|
+
| `impeccable` | Polish / bolder / quieter / audit / delight asks | Named Operate/Persuade craft passes |
|
|
157
|
+
| `ui-ux-pro-max` | Mood / industry keyword search for Express | Keywords → map to `--m-*` + style preset |
|
|
158
|
+
| `fixing-accessibility` | a11y audit after visual | Names, keyboard, focus on top of `M*` |
|
|
159
|
+
|
|
160
|
+
Routing, conflict rules, and load budget: [optional-companions.md](references/optional-companions.md). Distilled craft in [visual-craft.md](references/visual-craft.md) + [style-presets.md](references/style-presets.md) keeps this skill **standalone**.
|
|
161
|
+
|
|
162
|
+
## Output expectations
|
|
163
|
+
|
|
164
|
+
- Vue 3 `<script setup lang="ts">`.
|
|
165
|
+
- PascalCase `M*` in templates.
|
|
166
|
+
- Domain-real copy and data shapes (not placeholder “示例 / Name / No data” when the brief names a product).
|
|
167
|
+
- Scoped CSS minimal; tokens only (`color-mix` / gradients from `--m-*` OK; control widths may be inline).
|
|
168
|
+
- Craft pass completed for the lane (see step 5).
|
|
169
|
+
- For multi-file asks: sensible `views/` / `components/` split; otherwise one SFC is fine.
|
|
170
|
+
|
|
171
|
+
## Bundled references
|
|
172
|
+
|
|
173
|
+
| File | Read when |
|
|
174
|
+
| --- | --- |
|
|
175
|
+
| [surfaces.md](references/surfaces.md) | Choosing / composing non-Ops (and hybrid) surfaces |
|
|
176
|
+
| [page-layouts.md](references/page-layouts.md) | Ops golden layouts |
|
|
177
|
+
| [style-presets.md](references/style-presets.md) | Style resolution + named presets users can pick |
|
|
178
|
+
| [visual-craft.md](references/visual-craft.md) | Ops polish, atmosphere recipes, anti-defaults, polish modes |
|
|
179
|
+
| [design-system.md](references/design-system.md) | Principles, tokens, bans |
|
|
180
|
+
| [component-index.md](references/component-index.md) | Catalog + decision-id index |
|
|
181
|
+
| [decision-recipes.md](references/decision-recipes.md) | Scenario → component → key props (generated; offline MCP mirror) |
|
|
182
|
+
| [feedback.md](references/feedback.md) | message / toast / MMessage |
|
|
183
|
+
| [review-checklist.md](references/review-checklist.md) | Pre-delivery checks |
|
|
184
|
+
| [optional-companions.md](references/optional-companions.md) | Combining with external design skills |
|