@morya-ui/setup 0.3.1 → 0.3.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Morya UI contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -9,7 +9,7 @@ Docs (zh/en):
9
9
  - [Agent Skill](https://morya-space.github.io/morya-ui/docs/agent-skill) — `morya-ui-pages` behavior
10
10
  - [Quick start](https://morya-space.github.io/morya-ui/docs/quick-start) — install and first component
11
11
 
12
- Installs the UI library, copies Agent skills / Cursor rules / design docs from `design-kit`, merges Cursor MCP for [`@morya-ui/mcp`](https://www.npmjs.com/package/@morya-ui/mcp), and injects `import 'morya-ui/styles.css'`.
12
+ Installs the UI library, copies first-party Agent skills / Cursor rules / design docs from `design-kit`, installs optional companion skills at **latest** via the [skills CLI](https://skills.sh/), merges Cursor MCP for [`@morya-ui/mcp`](https://www.npmjs.com/package/@morya-ui/mcp), and injects `import 'morya-ui/styles.css'`.
13
13
 
14
14
  ## Usage
15
15
 
@@ -32,7 +32,7 @@ npx @morya-ui/setup app
32
32
 
33
33
  # Non-interactive skill selection
34
34
  npx @morya-ui/setup ai --yes
35
- npx @morya-ui/setup ai --skills=morya-ui-pages,frontend-design
35
+ npx @morya-ui/setup ai --skills=morya-ui-pages,frontend-design,impeccable
36
36
  npx @morya-ui/setup ai --skills=all
37
37
  ```
38
38
 
@@ -47,7 +47,7 @@ npx @morya-ui/setup ai --skills=all
47
47
  | `--force` | Overwrite existing template files and the `morya-ui` MCP entry |
48
48
  | `--dry-run` | Print actions without writing or installing |
49
49
  | `--skip-install` | Do not install `morya-ui` |
50
- | `--skip-template` | Do not copy AI skill / rules / docs |
50
+ | `--skip-template` | Do not copy AI skill / rules / docs (also skips companion skill install) |
51
51
  | `--skip-mcp` | Do not write `.cursor/mcp.json` |
52
52
  | `--skip-styles` | Do not inject `styles.css` |
53
53
  | `--skip-scripts` | Do not add `check:colors` to `package.json` |
@@ -55,30 +55,36 @@ npx @morya-ui/setup ai --skills=all
55
55
 
56
56
  ### Skills
57
57
 
58
- | Id | Default | Role |
59
- | --- | --- | --- |
60
- | `morya-ui-pages` | required | Page generation with `M*` + golden layouts |
61
- | `frontend-design` | optional | Express / brand visual taste |
62
- | `fixing-accessibility` | optional | A11y audit and targeted fixes |
58
+ | Id | Default | Install | Role |
59
+ | --- | --- | --- | --- |
60
+ | `morya-ui-pages` | required | template (from this package) | Page generation with `M*` + golden layouts |
61
+ | `frontend-design` | optional | skills CLI → `anthropics/skills` (latest) | Express / brand visual taste |
62
+ | `fixing-accessibility` | optional | skills CLI → `ibelick/ui-skills` (latest) | A11y audit and targeted fixes |
63
+ | `impeccable` | optional | skills CLI → `pbakaus/impeccable` (latest) | Named polish / audit / redesign passes |
63
64
 
64
65
  Catalog: [`catalog/skills.json`](./catalog/skills.json).
65
66
 
66
67
  ### Conflict policy
67
68
 
68
69
  - Template files and `.cursor/rules/*`: **skip** if the destination exists (unless `--force`).
70
+ - Companion skills (`skills-cli`): always reinstall/update to **latest** when selected.
69
71
  - `.cursor/mcp.json`: merge other servers; skip existing `morya-ui` entry unless `--force`.
70
72
  - `package.json` `check:colors`: add only if missing (unless `--force`).
71
73
  - Styles: inject only when an entry file is found and the import is not already present.
72
74
 
73
- ### What gets copied
75
+ ### What gets copied / installed
74
76
 
75
77
  From the package `template/` (synced from repo `design-kit/`):
76
78
 
77
79
  - `DESIGN.md` — core design contract (principles, tokens, bans)
78
- - `.agents/skills/<selected>/` — at least `morya-ui-pages`
80
+ - `.agents/skills/morya-ui-pages/` — first-party page skill
79
81
  - `.cursor/rules/`
80
82
  - `scripts/check-raw-colors.mjs`
81
83
 
84
+ Via `npx skills add …` when optional companions are selected:
85
+
86
+ - `.agents/skills/frontend-design/`, `fixing-accessibility/`, and/or `impeccable/` (latest upstream)
87
+
82
88
  ### MCP (Cursor)
83
89
 
84
90
  Writes / merges:
@@ -6,6 +6,7 @@
6
6
  "description": "Build admin and product pages with M* components, golden layouts, and tokens.",
7
7
  "required": true,
8
8
  "default": true,
9
+ "install": "template",
9
10
  "path": ".agents/skills/morya-ui-pages"
10
11
  },
11
12
  {
@@ -14,6 +15,9 @@
14
15
  "description": "Distinctive visual direction for Express / brand surfaces (taste only; still remediate with M*).",
15
16
  "required": false,
16
17
  "default": false,
18
+ "install": "skills-cli",
19
+ "source": "anthropics/skills",
20
+ "skill": "frontend-design",
17
21
  "path": ".agents/skills/frontend-design"
18
22
  },
19
23
  {
@@ -22,7 +26,21 @@
22
26
  "description": "Audit and fix names, keyboard, focus, forms, and WCAG basics.",
23
27
  "required": false,
24
28
  "default": false,
29
+ "install": "skills-cli",
30
+ "source": "ibelick/ui-skills",
31
+ "skill": "fixing-accessibility",
25
32
  "path": ".agents/skills/fixing-accessibility"
33
+ },
34
+ {
35
+ "id": "impeccable",
36
+ "name": "Impeccable",
37
+ "description": "Named polish / audit / redesign passes for frontend UI (latest from pbakaus/impeccable).",
38
+ "required": false,
39
+ "default": false,
40
+ "install": "skills-cli",
41
+ "source": "pbakaus/impeccable",
42
+ "skill": "impeccable",
43
+ "path": ".agents/skills/impeccable"
26
44
  }
27
45
  ]
28
46
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@morya-ui/setup",
3
3
  "type": "module",
4
- "version": "0.3.1",
4
+ "version": "0.3.3",
5
5
  "description": "One-shot setup for morya-ui: install the library, Cursor MCP, Agent skills, and design config.",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/morya-space/morya-ui/tree/main/packages/setup#readme",
package/src/cli.mjs CHANGED
@@ -8,6 +8,7 @@ import { mergeMcpConfig } from './mcp.mjs'
8
8
  import { ensureCheckColorsScript } from './package-json.mjs'
9
9
  import {
10
10
  buildAiInclude,
11
+ installSkillsCli,
11
12
  loadSkillsCatalog,
12
13
  parseSkillsFlag,
13
14
  resolveSkillSelection,
@@ -39,7 +40,8 @@ export function printHelp() {
39
40
  const { skills } = loadSkillsCatalog()
40
41
  const skillList = skills.map((s) => {
41
42
  const tag = s.required ? 'required' : s.default ? 'default' : 'optional'
42
- return ` ${s.id.padEnd(24)} (${tag}) ${s.description}`
43
+ const via = s.install === 'skills-cli' ? ' [skills-cli latest]' : ''
44
+ return ` ${s.id.padEnd(24)} (${tag})${via} ${s.description}`
43
45
  }).join('\n')
44
46
 
45
47
  console.log(`Usage: morya-ui-setup [command] [options]
@@ -243,6 +245,10 @@ export async function runSetup(options) {
243
245
  include: aiInclude,
244
246
  })
245
247
 
248
+ const remoteSkills = skipTemplate || !selectedSkills.length
249
+ ? { installed: [], commands: [], skipped: true }
250
+ : installSkillsCli(cwd, selectedSkills, catalog.skills, { dryRun })
251
+
246
252
  const mcp = skipMcp
247
253
  ? { path: join(cwd, '.cursor', 'mcp.json'), action: 'skipped-flag' }
248
254
  : mergeMcpConfig(cwd, { force, dryRun })
@@ -277,6 +283,15 @@ export async function runSetup(options) {
277
283
  }
278
284
  }
279
285
 
286
+ if (remoteSkills.skipped) {
287
+ // no companions selected or template step skipped
288
+ } else if (remoteSkills.dryRun) {
289
+ console.log(`Skills CLI: dry-run — would install ${remoteSkills.installed.join(', ')}`)
290
+ for (const command of remoteSkills.commands) console.log(` $ ${command}`)
291
+ } else {
292
+ console.log(`Skills CLI: installed ${remoteSkills.installed.join(', ')} (latest)`)
293
+ }
294
+
280
295
  if (mcp.action === 'skipped-flag') {
281
296
  console.log('MCP: skipped (--skip-mcp or app mode)')
282
297
  } else {
@@ -316,5 +331,5 @@ export async function runSetup(options) {
316
331
  console.log(' 4. Optional: pnpm check:colors')
317
332
  }
318
333
 
319
- return { mode, install, template, mcp, styles, scripts, skills: selectedSkills }
334
+ return { mode, install, template, remoteSkills, mcp, styles, scripts, skills: selectedSkills }
320
335
  }
package/src/skills.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { execSync } from 'node:child_process'
1
2
  import { createInterface } from 'node:readline'
2
3
  import { dirname, join } from 'node:path'
3
4
  import { fileURLToPath } from 'node:url'
@@ -13,6 +14,9 @@ const CATALOG_PATH = join(__dirname, '..', 'catalog', 'skills.json')
13
14
  * description: string
14
15
  * required?: boolean
15
16
  * default?: boolean
17
+ * install?: 'template' | 'skills-cli'
18
+ * source?: string
19
+ * skill?: string
16
20
  * path: string
17
21
  * }} SkillEntry
18
22
  */
@@ -24,6 +28,20 @@ export function loadSkillsCatalog() {
24
28
  return readJson(CATALOG_PATH)
25
29
  }
26
30
 
31
+ /**
32
+ * @param {SkillEntry} skill
33
+ */
34
+ export function isTemplateSkill(skill) {
35
+ return !skill.install || skill.install === 'template'
36
+ }
37
+
38
+ /**
39
+ * @param {SkillEntry} skill
40
+ */
41
+ export function isSkillsCliSkill(skill) {
42
+ return skill.install === 'skills-cli'
43
+ }
44
+
27
45
  /**
28
46
  * @param {SkillEntry[]} skills
29
47
  */
@@ -63,21 +81,23 @@ export function parseSkillsFlag(raw, skills) {
63
81
  }
64
82
 
65
83
  /**
84
+ * Template-relative paths for skills that ship inside the package template.
66
85
  * @param {string[]} skillIds
67
86
  * @param {SkillEntry[]} skills
68
87
  * @returns {string[]}
69
88
  */
70
89
  export function skillPathsForIds(skillIds, skills) {
71
90
  const byId = new Map(skills.map((s) => [s.id, s]))
72
- return skillIds.map((id) => {
91
+ return skillIds.flatMap((id) => {
73
92
  const entry = byId.get(id)
74
93
  if (!entry) throw new Error(`Unknown skill id: ${id}`)
75
- return entry.path
94
+ if (!isTemplateSkill(entry)) return []
95
+ return [entry.path]
76
96
  })
77
97
  }
78
98
 
79
99
  /**
80
- * Build template include prefixes for the AI pack.
100
+ * Build template include prefixes for the AI pack (first-party files only).
81
101
  * @param {string[]} skillIds
82
102
  * @param {SkillEntry[]} skills
83
103
  */
@@ -90,6 +110,54 @@ export function buildAiInclude(skillIds, skills) {
90
110
  ]
91
111
  }
92
112
 
113
+ /**
114
+ * Install selected companion skills via the skills CLI (always latest from source).
115
+ * @param {string} cwd
116
+ * @param {string[]} skillIds
117
+ * @param {SkillEntry[]} skills
118
+ * @param {{ dryRun?: boolean }} [options]
119
+ * @returns {{ installed: string[], commands: string[], dryRun?: boolean, skipped?: boolean }}
120
+ */
121
+ export function installSkillsCli(cwd, skillIds, skills, { dryRun = false } = {}) {
122
+ const selected = skills.filter((s) => skillIds.includes(s.id) && isSkillsCliSkill(s))
123
+ if (!selected.length) {
124
+ return { installed: [], commands: [], skipped: true }
125
+ }
126
+
127
+ for (const entry of selected) {
128
+ if (!entry.source) {
129
+ throw new Error(`Skill "${entry.id}" is marked skills-cli but has no source`)
130
+ }
131
+ }
132
+
133
+ /** @type {Map<string, string[]>} */
134
+ const bySource = new Map()
135
+ for (const entry of selected) {
136
+ const name = entry.skill || entry.id
137
+ const list = bySource.get(entry.source) || []
138
+ list.push(name)
139
+ bySource.set(entry.source, list)
140
+ }
141
+
142
+ const commands = []
143
+ for (const [source, names] of bySource) {
144
+ const skillFlags = names.map((name) => `-s ${name}`).join(' ')
145
+ commands.push(`npx -y skills add ${source} ${skillFlags} -a cursor -y`)
146
+ }
147
+
148
+ if (dryRun) {
149
+ return { installed: selected.map((s) => s.id), commands, dryRun: true }
150
+ }
151
+
152
+ console.log('Companion skills (latest via skills CLI):')
153
+ for (const command of commands) {
154
+ console.log(` $ ${command}`)
155
+ execSync(command, { cwd, stdio: 'inherit', shell: true })
156
+ }
157
+
158
+ return { installed: selected.map((s) => s.id), commands }
159
+ }
160
+
93
161
  /**
94
162
  * Prompt for optional skills when stdin is a TTY.
95
163
  * Required skills are always included.
@@ -108,7 +176,8 @@ export async function resolveSkillSelection(skills, { skipPrompt = false } = {})
108
176
  console.log('Select optional Agent skills (in addition to required):')
109
177
  optional.forEach((skill, index) => {
110
178
  const mark = skill.default ? 'x' : ' '
111
- console.log(` ${index + 1}. [${mark}] ${skill.id} — ${skill.description}`)
179
+ const via = isSkillsCliSkill(skill) ? ' [latest via skills CLI]' : ''
180
+ console.log(` ${index + 1}. [${mark}] ${skill.id} — ${skill.description}${via}`)
112
181
  })
113
182
  console.log('')
114
183
  console.log('Enter comma-separated numbers or ids (empty = defaults only).')
@@ -66,7 +66,7 @@ For Express / branded Account moments, also draft a tiny **design plan** (see [v
66
66
  | Ops pattern | `recommend_page` → **`get_golden_page`** (mirror; do not invent a parallel scaffold aesthetic) | [page-layouts.md](references/page-layouts.md) |
67
67
  | Account / Express / empty / result | `recommend_page` → `get_golden_page` (`login-page` / `landing-page` / `empty-state` / `result-page`) | [surfaces.md](references/surfaces.md) |
68
68
  | Visual direction | — | [visual-craft.md](references/visual-craft.md) (Ops polish / atmosphere / anti-defaults) |
69
- | Components / **API truth** | `search` / **`get_component`** / `get_example` / `recommend_component` | [component-index.md](references/component-index.md) |
69
+ | 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) |
70
70
  | Tokens / rules | `get_design_rules` | [design-system.md](references/design-system.md) |
71
71
  | Snippet | `get_page_snippet` | golden / surface excerpt |
72
72
  | Feedback API | — | [feedback.md](references/feedback.md) |
@@ -85,11 +85,12 @@ For Express / branded Account moments, also draft a tiny **design plan** (see [v
85
85
  ### 4. Wire real API usage
86
86
 
87
87
  - Import from `morya-ui` (or documented subpath + style).
88
- - Forms: `MForm` + fields; `@submit` + `type="submit"`.
88
+ - **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`.
89
+ - Forms: `MForm` + fields; `@submit` + `type="submit"` (or documented footer button pattern on `form-in-dialog`).
89
90
  - Tables: `columns` + `rows` + `row-key`; `#cell-{key}`. There is no `data` prop.
90
91
  - Enums → `MSelect` / `MTreeSelect`; action menus → `MDropdown`.
91
- - Destructive → `MConfirmDialog` / `MConfirmPopup`.
92
- - Feedback → default **`message`**; `toast` only for summary+detail / async. See [feedback.md](references/feedback.md).
92
+ - Destructive → `MConfirmDialog` / `MConfirmPopup` (`confirm-choice`).
93
+ - Feedback → default **`message`**; `toast` only for summary+detail / async; persistent form errors → `errorMessage` / `role="alert"` (`feedback-choice`). See [feedback.md](references/feedback.md).
93
94
  - 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.
94
95
  - **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.
95
96
  - `recommend_page(includeScaffold: true)` returns the **golden page source** when one exists — remap copy/data only; never treat generated fallback as the visual target.
@@ -100,7 +101,7 @@ Run **before** delivery. Do not stop at a structurally correct shell.
100
101
 
101
102
  - **Ops:** apply [visual-craft.md](references/visual-craft.md) § Ops polish (one primary, menu icons, `MStatus` in tables, designed empty, no decorative cards).
102
103
  - **Account / Flow:** one calm brand or empty-state cue from § Atmosphere recipes; form errors via `errorMessage` / token `role="alert"`.
103
- - **Express:** short design plan + one signature; avoid AI-default looks; optional 1–2 token-only motions; honor `useMotion` / `prefers-reduced-motion` (or `respectReducedMotion`).
104
+ - **Express:** short design plan + one signature; avoid AI-default looks; optional 1–2 token-only motions; intensity via `useMotion` (`full` / `reduced` / `none`), not the OS `prefers-reduced-motion` setting.
104
105
  - **All lanes:** responsive, focus visible, domain-real copy (active voice).
105
106
 
106
107
  Named polish modes (`quieter` | `bolder` | `clarify` | `audit` | …): use as an **extra** pass when the user asks to improve an existing screen. See [visual-craft.md](references/visual-craft.md) § Polish modes.
@@ -156,7 +157,8 @@ Details: [optional-companions.md](references/optional-companions.md). Distilled
156
157
  | [page-layouts.md](references/page-layouts.md) | Ops golden layouts |
157
158
  | [visual-craft.md](references/visual-craft.md) | Ops polish, atmosphere recipes, anti-defaults, polish modes |
158
159
  | [design-system.md](references/design-system.md) | Principles, tokens, bans |
159
- | [component-index.md](references/component-index.md) | Scenario → component |
160
+ | [component-index.md](references/component-index.md) | Catalog + decision-id index |
161
+ | [decision-recipes.md](references/decision-recipes.md) | Scenario → component → key props (generated; offline MCP mirror) |
160
162
  | [feedback.md](references/feedback.md) | message / toast / MMessage |
161
163
  | [review-checklist.md](references/review-checklist.md) | Pre-delivery checks |
162
164
  | [optional-companions.md](references/optional-companions.md) | Combining with external design skills |
@@ -4,7 +4,7 @@
4
4
  {
5
5
  "id": 1,
6
6
  "prompt": "我们内部教务系统用 Vue3 + morya-ui。请做一个「课程列表」后台页:按课程名搜索、按开课状态(全部/开课中/已结课)筛选;表格列:名称、状态、学分、更新时间;行内「编辑」进抽屉表单,「删除」要二次确认;右上角「新建课程」。保存/删除成功只要一句话提示。输出单个 Vue SFC,结构对齐列表黄金样例,并做 Ops 精致化(侧栏图标、状态用 MStatus、空态用 MEmpty、行操作不要一排实心按钮)。",
7
- "expected_output": "Ops list: MLayout/MPageFilters/MPageToolbar/MTable rows, MSelect status, MDrawer edit, MConfirmDialog delete, message not toast, MStatus for status, MEmpty in #empty, menu icons, text/small row actions, craft pass applied, no second UI kit.",
7
+ "expected_output": "Ops list: MLayout/MPageHeader/MPageFilters/MTable rows, optional batch MPageToolbar, MSelect status, MDrawer edit, MConfirmDialog delete, message not toast, MStatus for status, MEmpty in #empty, menu icons, text/small row actions, craft pass applied, no second UI kit.",
8
8
  "files": []
9
9
  },
10
10
  {
@@ -16,7 +16,7 @@
16
16
  {
17
17
  "id": 3,
18
18
  "prompt": "运营后台首页仪表盘:四个 KPI(今日订单、待处理、转化率、退款率)、中间一块图表占位、右侧或下方「最近告警」表格(级别、内容、时间)。Vue3 + morya-ui,可先用静态假数据;告警级别可用 Tag,状态用 MStatus。",
19
- "expected_output": "Dashboard: MPageStat grid with domain labels, MPagePlaceholder/chart card, recent MTable with MStatus, MLayout shell, --m-* only, spacious density.",
19
+ "expected_output": "Dashboard: MPageStat grid with domain labels, MCard shadow=always + MEmpty chart pending, recent MTable with MStatus, MLayout shell, --m-* only, spacious density.",
20
20
  "files": []
21
21
  },
22
22
  {
@@ -27,14 +27,14 @@
27
27
  },
28
28
  {
29
29
  "id": 5,
30
- "prompt": "课程列表在零数据时太空了。请设计一个空状态区块(可嵌在列表页内容区):说明还没有课程、引导「创建第一门课程」,视觉克制但有一个记忆点(dashed shell + 轻径向光)。只要空状态相关模板片段或带空状态的列表 SFC 均可,须 morya-ui。",
31
- "expected_output": "Flow empty: MEmpty + next-action MButton, short copy, token atmosphere shell, no emoji clutter, fits list content slot.",
30
+ "prompt": "课程列表在零数据时太空了。请设计一个空状态区块(可嵌在列表页内容区):说明还没有课程、引导「创建第一门课程」;视觉克制,用 morya-ui 默认空态即可,不要自定义 dashed 框或径向光晕。只要空状态相关模板片段或带空状态的列表 SFC 均可。",
31
+ "expected_output": "Flow empty: MPageHeader + MEmpty + next-action MButton, short copy, no custom dashed/radial shell, fits list content slot.",
32
32
  "files": []
33
33
  },
34
34
  {
35
35
  "id": 6,
36
- "prompt": "为「流水线 CI」产品写一个营销落地首页(不是后台):首屏只有品牌名、一句主标题、一句副文、一组 CTA;下面再分「为何选择」「能力」「客户」三节。技术实现用 Vue3 + morya-ui 组件做按钮/标签/折叠 FAQ;颜色走 --m-*,避免紫渐变和奶油衬线陶土那套 AI 默认脸。可有 1–2 个微动效并尊重 prefers-reduced-motion。输出单个 Vue SFC。",
37
- "expected_output": "Express landing: single-job hero, MButton CTAs, sections with one job each, MAccordion FAQ optional, no purple/cream-terracotta defaults, no admin MPageFilters shell, reduced-motion aware.",
36
+ "prompt": "为「流水线 CI」产品写一个营销落地首页(不是后台):首屏只有品牌名、一句主标题、一句副文、一组 CTA;下面再分「为何选择」「能力」「客户」三节。技术实现用 Vue3 + morya-ui 组件做按钮/标签/折叠 FAQ;颜色走 --m-*,避免紫渐变和奶油衬线陶土那套 AI 默认脸。可有 1–2 个微动效,强度跟 useMotion / data-m-motion。输出单个 Vue SFC。",
37
+ "expected_output": "Express landing: single-job hero, MButton CTAs, sections with one job each, MAccordion FAQ optional, no purple/cream-terracotta defaults, no admin MPageFilters shell, motion gated by useMotion / data-m-motion.",
38
38
  "files": []
39
39
  },
40
40
  {
@@ -66,6 +66,24 @@
66
66
  "prompt": "用户列表页上「新建/编辑」不要另开路由:同页 MDialog(约 32rem)里放短表单(姓名、邮箱、角色),页脚取消/保存;保存成功用 message 一句话。输出单个 Vue SFC。",
67
67
  "expected_output": "form-in-dialog: list stays, MDialog + MForm + #footer, message.success not toast string, MTable rows.",
68
68
  "files": []
69
+ },
70
+ {
71
+ "id": 12,
72
+ "prompt": "做一个用户列表表格页:列姓名、角色、状态。状态列必须用 MStatus;行数据绑定用 rows(不要写 data prop);空表用 #empty + MEmpty。Vue3 + morya-ui,输出单个 SFC。",
73
+ "expected_output": "Prop accuracy: MTable columns+rows (not :data), #cell or Status for status, #empty+MEmpty, validate_usage clean.",
74
+ "files": []
75
+ },
76
+ {
77
+ "id": 13,
78
+ "prompt": "列表行删除:点删除后用 MConfirmDialog(危险确认),确认后 message.success('已删除')。不要用 toast,也不要手写 Dialog 当确认框。输出片段或 SFC 均可。",
79
+ "expected_output": "confirm-choice + feedback-choice: MConfirmDialog v-model header/message acceptSeverity=danger @accept, message.success not toast, no hand Dialog confirm.",
80
+ "files": []
81
+ },
82
+ {
83
+ "id": 14,
84
+ "prompt": "短新建弹窗:MDialog v-model + header + #footer(取消 secondary、保存 primary);表单字段 fluid。保存成功 message 一句话。不要用 v-model:visible。",
85
+ "expected_output": "form-surface Dialog recipe: v-model not visible, #footer actions, fluid fields, message.success.",
86
+ "files": []
69
87
  }
70
88
  ]
71
89
  }
@@ -1,6 +1,9 @@
1
1
  # Component index (scenario map)
2
2
 
3
- Full API: docs site `/components` or MCP (`get_component`, `search`, `validate_usage`). This file is for **selection**, not prop manuals.
3
+ Full API: docs site `/components` or MCP (`get_component`, `search`, `validate_usage`).
4
+
5
+ - **Selection + key props**: MCP `recommend_component`, or offline [decision-recipes.md](./decision-recipes.md) (generated from `packages/ui-mcp/src/decisions.ts`).
6
+ - This file is a **catalog + decision-id index**, not a prop manual.
4
7
 
5
8
  ## Shell
6
9
 
@@ -31,68 +34,66 @@ Full API: docs site `/components` or MCP (`get_component`, `search`, `validate_u
31
34
 
32
35
  ## Feedback
33
36
 
34
- | API / component | When |
35
- | --- | --- |
36
- | `message` | **Default** one-line CRUD result |
37
- | `toast` | `summary` + `detail`, or async / background feel |
38
- | `<MMessage>` | Optional host for the `message` service (`appendTo` / placement). **Not** an inline alert |
39
- | field `errorMessage` or token `role="alert"` | Persistent form / auth error |
40
- | `MEmpty` | No-data / first-use / filtered empty (not an error) |
41
- | `MResult` | Terminal outcome: success, failure, 403 / 404 / 500 |
42
- | `MLoading` / `v-loading` / `loading.service` | **Default** region or fullscreen loading mask |
43
- | `MSkeleton` | Layout is already known; placeholder while content arrives |
44
- | `MProgressBar` | Determinate progress |
45
- | `MProgressSpinner` | Inline spinner only, not a region mask |
46
- | `MBlockUI` | Block interaction without a loading message |
37
+ | API / component | When | Decision |
38
+ | --- | --- | --- |
39
+ | `message` | **Default** one-line CRUD result | `feedback-choice` |
40
+ | `toast` | `summary` + `detail`, or async / background feel | `feedback-choice` |
41
+ | `<MMessage>` | Optional host for the `message` service. **Not** an inline alert | `feedback-choice` |
42
+ | field `errorMessage` or token `role="alert"` | Persistent form / auth error | `feedback-choice` |
43
+ | `MEmpty` | No-data / first-use / filtered empty | `empty-result-choice` |
44
+ | `MResult` | Terminal outcome: success, failure, 403 / 404 / 500 | `empty-result-choice` |
45
+ | `MLoading` / `v-loading` / `loading.service` | **Default** region or fullscreen loading mask | `loading-choice` |
46
+ | `MSkeleton` | Layout known; placeholder while content arrives | `loading-choice` |
47
+ | `MProgressBar` | Determinate progress | `loading-choice` |
48
+ | `MProgressSpinner` | Inline spinner only, not a region mask | `loading-choice` |
49
+ | `MBlockUI` | Block interaction without a loading message | `loading-choice` |
47
50
 
48
51
  ## Overlays & menus
49
52
 
50
- `MDialog`, `MDrawer`, `MConfirmDialog` / `MConfirmPopup`, `MPopover`, `MTooltip`, `MDropdown` (**actions only**), `MContextMenu` (right-click), `MCommandMenu` (searchable command palette), `MMenu` / `MMenubar` / `MTieredMenu` / `MMegaMenu`, `MTabs`, `MStepper`
53
+ `MDialog`, `MDrawer`, `MConfirmDialog` / `MConfirmPopup`, `MPopover`, `MTooltip`, `MDropdown` (**actions only**), `MContextMenu`, `MCommandMenu`, `MMenu` / `MMenubar` / `MTieredMenu` / `MMegaMenu`, `MTabs`, `MStepper`
51
54
 
52
55
  ## Surfaces / media
53
56
 
54
57
  `MCard`, `MPanel`, `MAccordion`, `MCarousel`, `MGallery`, `MIcon`, `MScrollbar`, `MInplace`, `MScrollTop`, `MTerminal`
55
58
 
56
- ## Scenario → pick
59
+ ## Scenario → decision
60
+
61
+ Open MCP `recommend_component({ decision })` or the matching section in [decision-recipes.md](./decision-recipes.md) for **when / avoid / recipe props / anti-patterns**.
57
62
 
58
- | Intent | Prefer |
63
+ | Intent | Decision id |
59
64
  | --- | --- |
60
- | Searchable list + paging | `MPageFilters` + `MTable` (+ paginator) |
61
- | Create / edit from a list (few–medium fields) | **`form-in-dialog`** golden (`MDialog` + `MForm`) |
62
- | Create / edit long / multi-section entity | Form golden page **or** `MDrawer` |
63
- | Resource detail / profile | `detail-page` golden |
64
- | Delete | `MConfirmDialog` |
65
- | Lightweight inline status | `MStatus` (dot + label) |
66
- | Status chip / closable label | `MTag` severities |
67
- | Primary / secondary actions | `MSpace` + `MButton` |
68
- | Dashboard KPIs | `MGrid` + `MPageStat` |
69
- | Org tree | `MTree` / `MTreeSelect` |
70
- | Login / auth | `login-page` golden + `MInputPassword` |
71
- | Marketing landing | `landing-page` golden + `MButton` / `MTag` / `MAccordion` |
72
- | Empty list / zero state | `MEmpty` (+ `empty-state` golden or `MTable` `#empty`) |
73
- | Preferences / settings | `settings-page` golden (`MTabs` + `tabs`) |
74
- | Multi-step create | `wizard-form` golden (`MStepper` + `steps`) |
75
- | Region or page is waiting | `MLoading`, `v-loading`, or `loading.service`; known layout → `MSkeleton` |
76
- | Local capped scroll | Explicit `MScrollbar` |
77
- | Resizable two-pane (editor / preview, master-detail) | `MSplitter` |
78
- | Admin sider nav | `MMenu` inside `MLayoutSider` (or `MSidebar` when the shell is not `MLayout`) |
79
- | Few options, all visible | `MRadio` or `MSelectButton` |
80
- | Always-visible option list | `MListbox` |
81
- | Multi-level cascade (not a tree dropdown) | `MCascadeSelect` |
82
- | Right-click actions | `MContextMenu` |
83
- | Global command search | `MCommandMenu` |
84
- | Count or dot on a control | `MBadge` |
85
- | Removable entity chip | `MChip` |
65
+ | Create / edit from a list (few–medium fields) vs long form page | `form-surface-choice` |
66
+ | Dialog vs Drawer vs Popover vs Tooltip | `overlay-choice` |
67
+ | Table vs DataView vs Tree / TreeTable | `data-display-choice` |
68
+ | Select vs TreeSelect vs AutoComplete vs Radio / … | `selection-choice` |
69
+ | Card vs Panel vs Fieldset | `surface-choice` |
70
+ | PageContent vs Flex vs Space vs Grid | `layout-spacing-choice` |
71
+ | PageFilters / Toolbar / Header / Section / Stat / Placeholder | `page-section-choice` |
72
+ | Layout scroll vs MScrollbar vs built-in | `page-scroll-choice` |
73
+ | Avoid double borders / extra Card wrappers | `surface-nesting-choice` |
74
+ | Loading vs Skeleton vs Progress* vs BlockUI | `loading-choice` |
75
+ | Status vs Tag vs Chip vs Badge | `status-label-choice` |
76
+ | Empty vs Result | `empty-result-choice` |
77
+ | Dropdown vs Menu vs ContextMenu vs CommandMenu | `action-menu-choice` |
78
+ | message vs toast vs field / form alert | `feedback-choice` |
79
+ | ConfirmDialog vs ConfirmPopup | `confirm-choice` |
80
+ | Searchable list + paging | `page-section-choice` + `data-display-choice` (+ list golden) |
81
+ | Delete / destructive | `confirm-choice` |
82
+ | Login / auth | `feedback-choice` + `login-page` golden |
83
+ | Marketing landing | `landing-page` golden (Express) |
84
+ | Multi-step create | `wizard-form` golden |
85
+ | Preferences / settings | `settings-page` golden |
86
+ | Resizable two-pane | use `MSplitter` (see docs / `get_component`) |
86
87
 
87
88
  ## Common mistakes
88
89
 
89
90
  | Wrong | Right |
90
91
  | --- | --- |
91
- | `MDropdown` as form enum | `MSelect` |
92
- | `MTable` `:data` | `:rows` (`row-key` defaults to `id`) |
93
- | `<MMessage severity>` as an inline alert | Field `errorMessage`, or a token-styled `role="alert"` |
92
+ | `MDropdown` as form enum | `MSelect` (`selection-choice`) |
93
+ | `MTable` `:data` | `:rows` (`data-display-choice`) |
94
+ | `<MMessage severity>` as an inline alert | Field `errorMessage`, or token `role="alert"` (`feedback-choice`) |
94
95
  | Hand `<table>` | `MTable` |
95
- | Hand modal div | `MDialog` |
96
- | Hand spinner or `MProgressSpinner` as a region / page mask | `MLoading` / `v-loading` |
97
- | Extra `MCard` around every `MPage*` block | Use page components' own surface/gap |
98
- | Assume undocumented props | MCP / docs lookup |
96
+ | Hand modal div | `MDialog` / `MConfirmDialog` |
97
+ | Hand spinner or `MProgressSpinner` as a region / page mask | `MLoading` / `v-loading` (`loading-choice`) |
98
+ | Extra `MCard` around every `MPage*` block | `surface-nesting-choice` |
99
+ | Assume undocumented props | MCP / docs lookup + `validate_usage` |