@morya-ui/mcp 0.2.5 → 0.2.7

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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "generatedAt": "2026-09-15T13:16:12.638Z",
3
- "catalogGeneratedAt": "2026-09-15T13:16:07.335Z",
2
+ "generatedAt": "2026-09-18T07:52:19.611Z",
3
+ "catalogGeneratedAt": "2026-09-18T07:52:14.544Z",
4
4
  "components": [
5
5
  {
6
6
  "id": "Accordion",
@@ -844,12 +844,15 @@
844
844
  {
845
845
  "id": "Empty",
846
846
  "exportName": "MEmpty",
847
- "examples": 8,
847
+ "examples": 12,
848
848
  "props": {
849
- "total": 5,
850
- "covered": 3,
849
+ "total": 10,
850
+ "covered": 5,
851
851
  "missing": [
852
+ "showDescription",
853
+ "showIcon",
852
854
  "image",
855
+ "size",
853
856
  "pt"
854
857
  ]
855
858
  },
@@ -859,13 +862,12 @@
859
862
  "missing": []
860
863
  },
861
864
  "slots": {
862
- "total": 6,
865
+ "total": 5,
863
866
  "covered": 2,
864
867
  "missing": [
865
868
  "icon",
866
869
  "image",
867
- "title",
868
- "description"
870
+ "title"
869
871
  ]
870
872
  },
871
873
  "methods": {
@@ -2085,12 +2087,11 @@
2085
2087
  {
2086
2088
  "id": "Result",
2087
2089
  "exportName": "MResult",
2088
- "examples": 8,
2090
+ "examples": 10,
2089
2091
  "props": {
2090
- "total": 5,
2091
- "covered": 2,
2092
+ "total": 6,
2093
+ "covered": 4,
2092
2094
  "missing": [
2093
- "title",
2094
2095
  "icon",
2095
2096
  "pt"
2096
2097
  ]
@@ -2501,10 +2502,10 @@
2501
2502
  {
2502
2503
  "id": "Status",
2503
2504
  "exportName": "MStatus",
2504
- "examples": 8,
2505
+ "examples": 10,
2505
2506
  "props": {
2506
- "total": 5,
2507
- "covered": 4,
2507
+ "total": 8,
2508
+ "covered": 7,
2508
2509
  "missing": [
2509
2510
  "color"
2510
2511
  ]
@@ -2515,9 +2516,11 @@
2515
2516
  "missing": []
2516
2517
  },
2517
2518
  "slots": {
2518
- "total": 1,
2519
+ "total": 2,
2519
2520
  "covered": 1,
2520
- "missing": []
2521
+ "missing": [
2522
+ "icon"
2523
+ ]
2521
2524
  },
2522
2525
  "methods": {
2523
2526
  "total": 0,
@@ -3193,6 +3196,6 @@
3193
3196
  "summary": {
3194
3197
  "components": 92,
3195
3198
  "componentsWithMissingCoverage": 87,
3196
- "totalExamples": 781
3199
+ "totalExamples": 789
3197
3200
  }
3198
3201
  }
@@ -26,6 +26,12 @@ import { ref } from 'vue'
26
26
 
27
27
  const keyword = ref('')
28
28
  const status = ref<string | undefined>()
29
+ const siderCollapsed = ref(false)
30
+
31
+ const menuModel = [
32
+ { key: 'users', label: '用户管理', icon: 'user', to: '/users' },
33
+ { key: 'roles', label: '角色管理', icon: 'shield', to: '/roles' },
34
+ ]
29
35
 
30
36
  const statusOptions = [
31
37
  { label: '全部', value: '' },
@@ -52,8 +58,13 @@ const rows = [
52
58
  <template>
53
59
  <MConfigProvider :locale="zhCN">
54
60
  <MLayout has-sider fill-viewport>
55
- <MLayoutSider bordered>
56
- <MMenu :model="[{ label: '用户管理', key: 'users' }, { label: '角色管理', key: 'roles' }]" />
61
+ <MLayoutSider v-model:collapsed="siderCollapsed" bordered :collapsed-width="72">
62
+ <MMenu
63
+ :model="menuModel"
64
+ :collapsed="siderCollapsed"
65
+ :collapsed-width="72"
66
+ selected-key="users"
67
+ />
57
68
  </MLayoutSider>
58
69
 
59
70
  <MLayout>
@@ -167,6 +167,27 @@ describe('@morya-ui/mcp handlers', () => {
167
167
  expect(result.ok).toBe(true);
168
168
  expect(result.suggestions.some((item) => item.type === 'redundant-wrapper' && item.standardId === 'page-sections')).toBe(true);
169
169
  });
170
+ it('suggests height-chain when MLayout is used without shell CSS', () => {
171
+ const result = read(handlers.validatePage({
172
+ code: '<MLayout fill-viewport has-sider><MLayoutContent /></MLayout>',
173
+ }));
174
+ expect(result.ok).toBe(true);
175
+ expect(result.suggestions.some((item) => item.type === 'height-chain')).toBe(true);
176
+ });
177
+ it('suggests menu icons when MMenu model has no icon fields', () => {
178
+ const result = read(handlers.validatePage({
179
+ code: `<MMenu :model="[{ key: 'users', label: 'Users', to: '/users' }]" />`,
180
+ }));
181
+ expect(result.ok).toBe(true);
182
+ expect(result.suggestions.some((item) => item.type === 'menu-missing-icons')).toBe(true);
183
+ });
184
+ it('does not suggest menu icons when model items include icon', () => {
185
+ const result = read(handlers.validatePage({
186
+ code: `<MMenu :model="[{ key: 'users', label: 'Users', icon: 'user', to: '/users' }]" />`,
187
+ }));
188
+ expect(result.ok).toBe(true);
189
+ expect(result.suggestions.some((item) => item.type === 'menu-missing-icons')).toBe(false);
190
+ });
170
191
  it('lists component decision guides when query is omitted', () => {
171
192
  const result = read(handlers.recommendComponent({ limit: 5 }));
172
193
  expect(result.kind).toBe('decisions');
package/dist/decisions.js CHANGED
@@ -1,4 +1,46 @@
1
1
  export const componentDecisions = [
2
+ {
3
+ id: 'form-surface-choice',
4
+ title: '新建/编辑用弹窗还是独立表单页',
5
+ titleEn: 'Dialog vs dedicated form page for create/edit',
6
+ question: '表单是从列表触发的短录入,还是多分组/长流程的独立配置?',
7
+ questionEn: 'Is this a short create/edit from a list, or a multi-section dedicated form flow?',
8
+ keywords: ['新建', '编辑', '表单', '弹窗', '路由', 'create', 'edit', 'form', 'dialog', 'page'],
9
+ options: [
10
+ {
11
+ component: 'Dialog',
12
+ when: [
13
+ '从列表/工具栏打开新建或编辑',
14
+ '字段大约 ≤8 个、单段表单',
15
+ '希望保存后仍停留在列表上下文',
16
+ ],
17
+ whenEn: [
18
+ 'Create/edit is launched from a list or toolbar',
19
+ 'About ≤8 fields in a single section',
20
+ 'User should stay in the list context after save',
21
+ ],
22
+ avoidWhen: ['多步骤向导', '多分组长配置页'],
23
+ avoidWhenEn: ['Multi-step wizards', 'Long multi-section configuration pages'],
24
+ },
25
+ {
26
+ component: 'Drawer',
27
+ when: ['表单比 Dialog 更长但仍需对照列表', '详情+编辑侧滑'],
28
+ whenEn: ['Form is longer than a dialog but list context matters', 'Detail plus edit in a side panel'],
29
+ avoidWhen: ['字段极少的确认式录入'],
30
+ avoidWhenEn: ['Tiny confirmation-style forms'],
31
+ },
32
+ {
33
+ component: 'Form',
34
+ when: ['用户明确要求独立表单页', '多分组、长校验、离开需确认的配置流'],
35
+ whenEn: [
36
+ 'The user explicitly asked for a dedicated form page',
37
+ 'Multi-section, heavy validation, or leave-confirm configuration flows',
38
+ ],
39
+ avoidWhen: ['列表上的常规短 CRUD'],
40
+ avoidWhenEn: ['Routine short CRUD from a list'],
41
+ },
42
+ ],
43
+ },
2
44
  {
3
45
  id: 'overlay-choice',
4
46
  title: '如何选择浮层组件',
@@ -9,10 +51,23 @@ export const componentDecisions = [
9
51
  options: [
10
52
  {
11
53
  component: 'Dialog',
12
- when: ['需要用户聚焦完成一项短到中等任务', '需要确认危险操作', '内容不适合直接放在页面流中'],
13
- whenEn: ['The user should focus on a short or medium-sized task', 'A destructive action needs confirmation', 'Content should not interrupt the page flow'],
14
- avoidWhen: ['内容接近完整页面', '用户需要持续查看底层页面上下文'],
15
- avoidWhenEn: ['The content is effectively a full page', 'The user must continuously reference the underlying page'],
54
+ when: [
55
+ '需要用户聚焦完成一项短到中等任务',
56
+ '列表页上的新建/编辑短表单(约 ≤8 个字段)',
57
+ '需要确认危险操作',
58
+ '内容不适合直接放在页面流中',
59
+ ],
60
+ whenEn: [
61
+ 'The user should focus on a short or medium-sized task',
62
+ 'Create/edit short forms launched from a list (~≤8 fields)',
63
+ 'A destructive action needs confirmation',
64
+ 'Content should not interrupt the page flow',
65
+ ],
66
+ avoidWhen: ['内容接近完整多分组页面', '用户需要持续查看底层页面上下文且表单很长'],
67
+ avoidWhenEn: [
68
+ 'The content is effectively a multi-section full page',
69
+ 'The user must continuously reference the underlying page and the form is long',
70
+ ],
16
71
  },
17
72
  {
18
73
  component: 'Drawer',
@@ -47,7 +47,7 @@ export const goldenPageCatalog = [
47
47
  ];
48
48
  const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
49
49
  const bundledDir = join(pkgRoot, 'data/golden-pages');
50
- const repoDir = join(pkgRoot, '../../ai-design-config/docs/golden-pages');
50
+ const repoDir = join(pkgRoot, '../../design-kit/docs/golden-pages');
51
51
  function resolveGoldenPagesDir() {
52
52
  if (existsSync(bundledDir))
53
53
  return bundledDir;
@@ -394,23 +394,51 @@ const recentRows = ref<Record<string, unknown>[]>([])`,
394
394
  descriptionEn: 'Minimal MLayout fillViewport shell with header and content.',
395
395
  pageTypes: ['common'],
396
396
  keywords: ['layout', 'shell', 'breadcrumb', 'sider', 'app'],
397
- imports: ['MLayout', 'MLayoutHeader', 'MLayoutContent', 'MBreadcrumb', 'MConfigProvider', 'zhCN'],
397
+ imports: [
398
+ 'MLayout',
399
+ 'MLayoutHeader',
400
+ 'MLayoutContent',
401
+ 'MLayoutSider',
402
+ 'MMenu',
403
+ 'MBreadcrumb',
404
+ 'MPageContent',
405
+ 'MConfigProvider',
406
+ 'zhCN',
407
+ ],
398
408
  template: `<MConfigProvider :locale="zhCN">
399
- <MLayout fill-viewport>
400
- <MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
401
- <MBreadcrumb :model="[{ label: '首页', to: '/' }, { label: '当前页' }]" />
402
- </MLayoutHeader>
403
- <MLayoutContent>
404
- <MPageContent>
405
- <!-- page sections -->
406
- </MPageContent>
407
- </MLayoutContent>
409
+ <MLayout fill-viewport has-sider>
410
+ <MLayoutSider v-model:collapsed="siderCollapsed" bordered :collapsed-width="72">
411
+ <MMenu
412
+ :model="[
413
+ { key: 'users', label: '用户管理', icon: 'user', to: '/users' },
414
+ { key: 'roles', label: '角色管理', icon: 'shield', to: '/roles' },
415
+ ]"
416
+ :collapsed="siderCollapsed"
417
+ :collapsed-width="72"
418
+ />
419
+ </MLayoutSider>
420
+ <MLayout>
421
+ <MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
422
+ <MBreadcrumb :model="[{ label: '首页', to: '/' }, { label: '当前页' }]" />
423
+ </MLayoutHeader>
424
+ <MLayoutContent>
425
+ <MPageContent>
426
+ <!-- page sections -->
427
+ </MPageContent>
428
+ </MLayoutContent>
429
+ </MLayout>
408
430
  </MLayout>
409
431
  </MConfigProvider>`,
410
- rules: ['根布局用 fillViewport', '面包屑放 MLayoutHeader'],
411
- rulesEn: ['Use fillViewport on the root layout', 'Place breadcrumb in MLayoutHeader'],
412
- avoid: ['不要手写 min-height: 100vh'],
413
- avoidEn: ['Do not hand-write min-height: 100vh'],
432
+ rules: [
433
+ '根布局用 fillViewport',
434
+ '面包屑放 MLayoutHeader',
435
+ ],
436
+ rulesEn: [
437
+ 'Use fillViewport on the root layout',
438
+ 'Place breadcrumb in MLayoutHeader',
439
+ ],
440
+ avoid: ['不要在 MLayoutContent 上写 padding'],
441
+ avoidEn: ['Do not put padding on MLayoutContent'],
414
442
  },
415
443
  {
416
444
  id: 'scrollable-panel',
package/dist/patterns.js CHANGED
@@ -42,30 +42,32 @@ export const pagePatterns = [
42
42
  { component: 'Table', role: '数据展示', reason: 'paginator 启用内置分页' },
43
43
  { component: 'Tag', role: '业务状态展示(分类/可关闭)', required: false },
44
44
  { component: 'Status', role: '行内状态圆点+文案', required: false },
45
- { component: 'Dialog', role: '编辑或删除确认', required: false },
45
+ { component: 'Dialog', role: '新建/编辑短表单(首选)或删除确认', required: false },
46
46
  ],
47
47
  structure: [
48
48
  'MConfigProvider',
49
- '└── MLayout (has-sider)',
50
- ' ├── MLayoutSider → MMenu (optional)',
49
+ '└── MLayout (has-sider fill-viewport)',
50
+ ' ├── MLayoutSider → MMenu',
51
51
  ' └── MLayout',
52
52
  ' ├── MLayoutHeader → MBreadcrumb',
53
53
  ' └── MLayoutContent',
54
54
  ' └── MPageContent',
55
55
  ' ├── MPageFilters (MSpace + MInput + MSelect + MButton)',
56
56
  ' ├── MPageToolbar (title + primary action)',
57
- ' └── MTable (paginator, #cell-* slots, #empty)',
57
+ ' ├── MTable (paginator, #cell-* slots, #empty)',
58
+ ' └── MDialog (create/edit MForm, optional)',
58
59
  ],
59
60
  layout: {
60
61
  page: 'MLayout fillViewport + has-sider;MLayoutContent 内用 MPageContent 堆叠区块',
61
62
  header: 'MLayoutHeader 放面包屑;标题与主操作用 MPageToolbar',
62
63
  filters: 'MPageFilters 包裹筛选控件;内部 MSpace wrap 或 MFlex',
63
64
  data: 'MTable 直接放在 MPageContent 内,不要额外 MCard;paginator + #empty',
65
+ form: '列表新建/编辑默认 MDialog + MForm,不要默认拆独立表单路由',
64
66
  },
65
67
  styleRules: [
66
68
  '新增、保存等主操作使用 primary MButton',
67
69
  '查询等次要操作使用 secondary 或 outlined MButton',
68
- '删除使用 danger MButton,并配合 MDialog 或 MConfirmDialog',
70
+ '删除使用 danger MButton,并配合 MConfirmDialog',
69
71
  '业务状态优先使用 MStatus(圆点+文案)或 MTag(分类标签),不使用 Button severity 表达状态',
70
72
  '间距、颜色、圆角优先使用 --m-* 设计令牌,不直接硬编码色板',
71
73
  ],
@@ -74,10 +76,12 @@ export const pagePatterns = [
74
76
  '加载中禁用重复提交并显示 loading 状态',
75
77
  '空数据使用 MTable #empty 插槽或 DataView 空态,不渲染空白表格',
76
78
  '行级危险操作必须有确认反馈',
79
+ '新建/编辑短表单用 MDialog,保存成功 message.success 并关闭弹窗',
77
80
  'CRUD 成功/删除等单行回执使用 message API,不要默认 Toast',
78
81
  ],
79
82
  avoid: [
80
83
  '不要用 MDialog 承载完整列表页',
84
+ '不要为列表短 CRUD 默认生成 /entities/new 独立表单路由',
81
85
  '不要用 MButton 代替业务状态标签',
82
86
  '不要为每个筛选字段手写一套不一致的 label 和间距',
83
87
  '不要用 toast.add({ summary }) 代替 message.success 表达单行操作结果',
@@ -139,10 +143,50 @@ export const pagePatterns = [
139
143
  ],
140
144
  avoid: [
141
145
  '不要绕过 MForm 直接手写不一致的字段校验状态',
142
- '不要把完整编辑流程塞进过窄的 Dialog,除非字段很少',
146
+ '列表上的短新建/编辑优先 MDialog,不要默认拆成独立路由表单页',
147
+ '不要把完整多分组编辑流程塞进过窄的 Dialog;字段很多时用独立表单页或 MDrawer',
143
148
  '不要使用 placeholder 代替 label',
144
149
  ],
145
150
  },
151
+ {
152
+ id: 'form-in-dialog',
153
+ title: '列表内新建 / 编辑弹窗',
154
+ titleEn: 'Create or edit in a dialog from a list',
155
+ description: '从列表触发的短表单 CRUD:留在列表页,用 MDialog 承载 MForm。',
156
+ descriptionEn: 'Short CRUD forms launched from a list: keep the list page and host MForm in MDialog.',
157
+ keywords: ['弹窗表单', '新建弹窗', '编辑弹窗', '列表新建', 'dialog form', 'modal form', 'inline create', 'inline edit'],
158
+ components: [
159
+ { component: 'Dialog', role: '承载短表单' },
160
+ { component: 'Form', role: '声明式校验' },
161
+ { component: 'FormItem', role: '字段标签与校验', required: false },
162
+ { component: 'Input', role: '文本字段' },
163
+ { component: 'Select', role: '枚举字段', required: false },
164
+ { component: 'Button', role: '页脚保存/取消' },
165
+ ],
166
+ structure: [
167
+ '列表页(list-page)',
168
+ '└── MDialog',
169
+ ' ├── MForm + 字段',
170
+ ' └── #footer → 取消 / 保存',
171
+ ],
172
+ layout: {
173
+ host: '留在列表页,不另开路由',
174
+ dialog: 'MDialog 承载短表单;宽度约 28–36rem',
175
+ form: 'MForm + 字段;页脚用 Dialog #footer',
176
+ },
177
+ styleRules: [
178
+ 'Dialog 宽度约 28–36rem;树/长内容区可限高滚动',
179
+ '页脚用 Dialog #footer,不要把主按钮只放在表单流末尾却看不见',
180
+ ],
181
+ interactionRules: [
182
+ '保存成功后关闭 Dialog 并 message.success',
183
+ '取消或关闭丢弃未保存草稿(或按产品要求提示)',
184
+ ],
185
+ avoid: [
186
+ '不要为 ≤8 字段的列表 CRUD 再开 /entities/new 路由',
187
+ '不要用 Toast 代替单行 message 回执',
188
+ ],
189
+ },
146
190
  {
147
191
  id: 'dashboard',
148
192
  title: '仪表盘 / 监控工作台',
@@ -471,12 +515,12 @@ export const pageStandards = [
471
515
  'Call get_golden_page before writing a full page and copy the matching structure',
472
516
  ],
473
517
  discouraged: [
474
- '手写 min-height:100vh',
518
+ '手写 min-height:100vh 代替 Layout',
475
519
  '在 MLayoutContent 上写 padding/gap',
476
520
  '嵌套多层 MPageContent',
477
521
  ],
478
522
  discouragedEn: [
479
- 'Hand-written min-height:100vh',
523
+ 'Hand-written min-height:100vh instead of Layout',
480
524
  'Padding/gap on MLayoutContent instead of MPageContent',
481
525
  'Nesting multiple MPageContent shells',
482
526
  ],
package/dist/tools.js CHANGED
@@ -927,6 +927,29 @@ export function createToolHandlers(catalog = loadCatalog()) {
927
927
  : '标准建议:整页高度优先 MLayout fillViewport,少写 min-height:100vh。',
928
928
  });
929
929
  }
930
+ if ((/fill-viewport|fillViewport/i.test(code) || /<MLayout\b/i.test(code))
931
+ && !/html\s*,\s*body\s*,\s*#app[\s\S]{0,80}height\s*:\s*100%/i.test(code)) {
932
+ suggestions.push({
933
+ standardId: 'layout-shell',
934
+ type: 'height-chain',
935
+ message: locale === 'en-US'
936
+ ? 'Prefer layout-app-shell / golden pages: include html, body, #app { height: 100% } (morya-app-shell.css) with MLayout.'
937
+ : '对齐 layout-app-shell / 黄金样例:配合 MLayout 使用 html, body, #app { height: 100% }(morya-app-shell.css)。',
938
+ });
939
+ }
940
+ if (/<MMenu\b/i.test(code)) {
941
+ const iconCount = (code.match(/\bicon\s*:/g) || []).length;
942
+ const looksLikeMenuModel = /MMenu[\s\S]{0,1200}\b(label|model)\s*[:=]/i.test(code) || /:model=/i.test(code);
943
+ if (looksLikeMenuModel && iconCount === 0) {
944
+ suggestions.push({
945
+ standardId: 'layout-shell',
946
+ type: 'menu-missing-icons',
947
+ message: locale === 'en-US'
948
+ ? 'Prefer golden / layout-app-shell menu items that include icon (e.g. user, shield, home).'
949
+ : '对齐黄金样例 / layout-app-shell:侧栏菜单项带上 icon(如 user、shield、home)。',
950
+ });
951
+ }
952
+ }
930
953
  return textResult({
931
954
  ok: true,
932
955
  advisory: true,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@morya-ui/mcp",
3
3
  "type": "module",
4
- "version": "0.2.5",
4
+ "version": "0.2.7",
5
5
  "description": "MCP server for morya-ui — component docs, examples, and usage guidance for AI coding agents.",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/morya-space/morya-ui/tree/main/packages/ui-mcp#readme",