@morya-ui/mcp 0.2.3 → 0.2.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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "generatedAt": "2026-09-14T03:19:40.333Z",
3
- "catalogGeneratedAt": "2026-09-14T03:19:36.680Z",
2
+ "generatedAt": "2026-09-14T07:44:52.185Z",
3
+ "catalogGeneratedAt": "2026-09-14T07:44:48.362Z",
4
4
  "components": [
5
5
  {
6
6
  "id": "Accordion",
@@ -2100,10 +2100,8 @@
2100
2100
  },
2101
2101
  "slots": {
2102
2102
  "total": 1,
2103
- "covered": 0,
2104
- "missing": [
2105
- "default"
2106
- ]
2103
+ "covered": 1,
2104
+ "missing": []
2107
2105
  },
2108
2106
  "methods": {
2109
2107
  "total": 0,
@@ -107,12 +107,58 @@ describe('@morya-ui/mcp handlers', () => {
107
107
  const result = read(handlers.search({ query: 'toolbar', scope: 'snippets', limit: 5 }));
108
108
  expect(result.items.some((item) => item.type === 'snippet' && item.id === 'list-toolbar')).toBe(true);
109
109
  });
110
- it('flags double-border page composition issues', () => {
110
+ it('suggests double-border page composition as advisory standard', () => {
111
111
  const result = read(handlers.validatePage({
112
112
  code: '<MLayoutContent><MCard><MTable bordered /></MCard></MLayoutContent>',
113
113
  }));
114
- expect(result.ok).toBe(false);
115
- expect(result.issues.some((issue) => issue.type === 'double-border')).toBe(true);
114
+ expect(result.ok).toBe(true);
115
+ expect(result.advisory).toBe(true);
116
+ expect(result.suggestions.some((item) => item.type === 'double-border')).toBe(true);
117
+ });
118
+ it('suggests MScrollbar over inline overflow scroll styles', () => {
119
+ const result = read(handlers.validatePage({
120
+ code: '<div style="overflow:auto;height:20rem"><MTable /></div>',
121
+ }));
122
+ expect(result.ok).toBe(true);
123
+ expect(result.suggestions.some((item) => item.type === 'native-scroll' && item.standardId === 'scroll')).toBe(true);
124
+ });
125
+ it('suggests MScrollbar over overflow in style blocks', () => {
126
+ const result = read(handlers.validatePage({
127
+ code: '<template><div class="panel">x</div></template><style>.panel { max-height: 20rem; overflow-y: auto; }</style>',
128
+ }));
129
+ expect(result.ok).toBe(true);
130
+ expect(result.suggestions.some((item) => item.type === 'native-scroll')).toBe(true);
131
+ });
132
+ it('returns scrollable-panel snippet for scroll queries', () => {
133
+ const result = read(handlers.getPageSnippet({ section: 'scrollable-panel' }));
134
+ expect(result.id).toBe('scrollable-panel');
135
+ expect(result.template).toContain('MScrollbar');
136
+ expect(result.imports).toContain('MScrollbar');
137
+ });
138
+ it('keeps recommend_page focused on pattern matching', () => {
139
+ const result = read(handlers.recommendPage({ intent: '用户列表页', pageType: 'list' }));
140
+ expect(result.matchedPattern).toBe('admin-list');
141
+ expect(result.nextStep).toContain('get_design_rules');
142
+ expect(result.pageStandards).toBeUndefined();
143
+ expect(result.scrollStandards).toBeUndefined();
144
+ });
145
+ it('keeps get_golden_page focused on source code', () => {
146
+ const result = read(handlers.getGoldenPage({ page: 'list-page' }));
147
+ expect(result.source).toContain('MLayout');
148
+ expect(result.nextStep).toContain('get_design_rules');
149
+ expect(result.pageStandards).toBeUndefined();
150
+ expect(result.scrollStandards).toBeUndefined();
151
+ });
152
+ it('exposes page standards from get_design_rules', () => {
153
+ const result = read(handlers.getDesignRules({ mode: 'zh-CN' }));
154
+ expect(result.meta.nature).toBe('recommended');
155
+ expect(result.standards.some((item) => item.id === 'scroll')).toBe(true);
156
+ expect(result.standards.some((item) => item.id === 'feedback')).toBe(true);
157
+ });
158
+ it('reads page scroll decision guide by id', () => {
159
+ const result = read(handlers.recommendComponent({ decision: 'page-scroll-choice' }));
160
+ expect(result.id).toBe('page-scroll-choice');
161
+ expect(result.options.some((option) => option.component === 'MScrollbar')).toBe(true);
116
162
  });
117
163
  it('lists component decision guides when query is omitted', () => {
118
164
  const result = read(handlers.recommendComponent({ limit: 5 }));
package/dist/decisions.js CHANGED
@@ -227,6 +227,76 @@ export const componentDecisions = [
227
227
  },
228
228
  ],
229
229
  },
230
+ {
231
+ id: 'page-scroll-choice',
232
+ title: '页面滚动如何选择',
233
+ titleEn: 'Choosing page scroll strategy',
234
+ question: '这是整页滚动、组件内置滚动,还是业务手写的局部滚动区?',
235
+ questionEn: 'Is this whole-page scroll, built-in component scroll, or a hand-written local scroll region?',
236
+ keywords: [
237
+ 'scroll',
238
+ 'scrollbar',
239
+ '滚动',
240
+ '滚动条',
241
+ 'overflow',
242
+ '页面滚动',
243
+ 'layout scroll',
244
+ 'panel scroll',
245
+ ],
246
+ options: [
247
+ {
248
+ component: 'MLayout fillViewport',
249
+ when: [
250
+ '整页后台列表/表单/仪表盘',
251
+ '需要 Header + Content + 可选 Sider 的应用骨架',
252
+ '页面主滚动应随 Layout 主题化',
253
+ ],
254
+ whenEn: [
255
+ 'Full admin list/form/dashboard pages',
256
+ 'App shell with header, content, and optional sider',
257
+ 'Main page scroll should follow the layout theme',
258
+ ],
259
+ avoidWhen: ['单个卡片内部的小块内容', '需要业务自行控制滚动的 Dialog/Drawer 内容'],
260
+ avoidWhenEn: ['Small regions inside a single card', 'Dialog/Drawer bodies where apps control scrolling'],
261
+ },
262
+ {
263
+ component: 'MScrollbar',
264
+ when: [
265
+ '业务自行限高的卡片正文、侧栏、日志列表,且希望主题化滚动条',
266
+ 'Dialog / Drawer 等内容区需要主题滚动时由业务显式包一层',
267
+ '组件未内置滚动、又需要统一滚动外观时',
268
+ ],
269
+ whenEn: [
270
+ 'App-owned capped regions (card bodies, side panels, logs) that want themed scrollbars',
271
+ 'Dialog / Drawer content where the app opts into themed scrolling',
272
+ 'No built-in component scroll, but a consistent scrollbar look is desired',
273
+ ],
274
+ avoidWhen: [
275
+ 'Dialog / Drawer / Popover / Splitter 等用户内容插槽被组件库强行包滚动',
276
+ 'Textarea 等原生控件自身的滚动',
277
+ 'MTable / MLayoutContent / Select 弹出层等已内置滚动的区域',
278
+ ],
279
+ avoidWhenEn: [
280
+ 'Library-forced scroll around user content slots such as Dialog, Drawer, Popover, or Splitter',
281
+ 'Native control scrolling such as Textarea',
282
+ 'Areas that already scroll internally (MTable, MLayoutContent, Select popups)',
283
+ ],
284
+ },
285
+ {
286
+ component: 'Built-in (no extra wrapper)',
287
+ when: [
288
+ 'MLayout、MTable、MVirtualScroller、菜单/下拉面板等已内置 MScrollbar',
289
+ '浮层菜单与子菜单(Dropdown/ContextMenu/TieredMenu/Menu flyout)',
290
+ ],
291
+ whenEn: [
292
+ 'MLayout, MTable, MVirtualScroller, and menu/select panels already scroll internally',
293
+ 'Overlay menus and nested flyouts (Dropdown/ContextMenu/TieredMenu/Menu flyout)',
294
+ ],
295
+ avoidWhen: ['在已内置滚动的组件外再包一层滚动容器'],
296
+ avoidWhenEn: ['Wrapping another scroll container around built-in scroll chrome'],
297
+ },
298
+ ],
299
+ },
230
300
  {
231
301
  id: 'surface-nesting-choice',
232
302
  title: '如何避免双边框与多余容器',
@@ -348,6 +348,55 @@ const recentRows = ref<Record<string, unknown>[]>([])`,
348
348
  avoid: ['不要手写 min-height: 100vh'],
349
349
  avoidEn: ['Do not hand-write min-height: 100vh'],
350
350
  },
351
+ {
352
+ id: 'scrollable-panel',
353
+ title: '局部可滚动面板',
354
+ titleEn: 'Scrollable local panel',
355
+ description: '业务自行限高滚动时的可选写法:显式使用 MScrollbar。组件内置滚动区无需再包。',
356
+ descriptionEn: 'Optional pattern when the app wants a themed capped scroll region. Do not wrap component-owned scroll chrome again.',
357
+ pageTypes: ['list', 'form', 'dashboard', 'detail', 'common'],
358
+ keywords: [
359
+ 'scroll',
360
+ 'scrollbar',
361
+ '滚动',
362
+ '滚动条',
363
+ 'overflow',
364
+ 'max-height',
365
+ 'panel',
366
+ 'card body',
367
+ 'sidebar',
368
+ 'log',
369
+ ],
370
+ imports: ['MScrollbar', 'MCard'],
371
+ template: `<MCard title="活动日志">
372
+ <MScrollbar max-height="16rem">
373
+ <ul style="margin:0;padding:0;list-style:none">
374
+ <li v-for="item in logLines" :key="item.id" style="padding:var(--m-space-2) 0;border-bottom:1px solid var(--m-color-border)">
375
+ {{ item.text }}
376
+ </li>
377
+ </ul>
378
+ </MScrollbar>
379
+ </MCard>`,
380
+ scriptSetup: `const logLines = [
381
+ { id: '1', text: '用户 admin 登录成功' },
382
+ { id: '2', text: '导出任务已完成' },
383
+ { id: '3', text: '配置已保存' },
384
+ ]`,
385
+ rules: [
386
+ '整页主滚动推荐 MLayout fillViewport',
387
+ '固定高度用 height;仅超出时才滚动用 max-height',
388
+ 'MTable / Layout / 菜单下拉等通常已内置滚动,一般无需再包一层',
389
+ 'Dialog / Drawer 内容滚动由业务自行决定,需要主题滚动时再包 MScrollbar',
390
+ ],
391
+ rulesEn: [
392
+ 'Main page scroll: prefer MLayout fillViewport',
393
+ 'Use height for fixed viewports; use max-height when scroll should appear only on overflow',
394
+ 'MTable / Layout / menu popups usually scroll internally; an extra wrapper is often unnecessary',
395
+ 'Dialog / Drawer content scrolling is app-owned; wrap MScrollbar only when themed scroll is desired',
396
+ ],
397
+ avoid: ['整页壳写 overflow:auto', '重复包裹已内置滚动的组件'],
398
+ avoidEn: ['overflow:auto on the page shell', 'Double-wrapping components that already scroll'],
399
+ },
351
400
  ];
352
401
  export function findPageSnippet(id) {
353
402
  const key = id.trim().toLowerCase().replace(/[-_\s]/g, '');
@@ -20,7 +20,29 @@ export interface PagePattern {
20
20
  avoid: string[];
21
21
  }
22
22
  export declare const pagePatterns: PagePattern[];
23
+ export interface PageStandard {
24
+ id: string;
25
+ title: string;
26
+ titleEn: string;
27
+ recommend: string[];
28
+ recommendEn: string[];
29
+ discouraged?: string[];
30
+ discouragedEn?: string[];
31
+ mcp?: {
32
+ snippet?: string;
33
+ decision?: string;
34
+ };
35
+ }
36
+ /** Advisory page-writing standards for MCP and human authors — not enforced blockers. */
37
+ export declare const pageStandards: PageStandard[];
23
38
  export declare const designRules: {
39
+ readonly meta: {
40
+ readonly nature: "recommended";
41
+ readonly noteZh: "standards 为页面书写标准(推荐实践);特殊场景可偏离。validate_page 仅给出参考建议。";
42
+ readonly noteEn: "standards are recommended page-writing practices; deviations are fine when justified. validate_page is advisory only.";
43
+ };
44
+ /** Page writing standards — single source for layout, scroll, tokens, feedback, a11y, etc. */
45
+ readonly standards: PageStandard[];
24
46
  readonly tokens: {
25
47
  readonly colors: readonly ["--m-color-primary", "--m-color-surface", "--m-color-text", "--m-color-border"];
26
48
  readonly spacing: "--m-space-*";
@@ -29,8 +51,8 @@ export declare const designRules: {
29
51
  readonly motion: "--m-motion-fast/normal";
30
52
  };
31
53
  readonly composition: {
32
- readonly workflow: readonly ["For full pages: recommend_page then get_golden_page before writing layout code.", "For local edits: get_page_snippet(section) for filters/toolbar/form-actions/KPI blocks instead of reading the whole golden page.", "Use MLayout fillViewport as the app shell; put MPageContent inside MLayoutContent.", "Prefer MPage* components over scoped CSS for filters, toolbars, headers, form surfaces, and KPI cards.", "Use MSpace or MFlex for control groups inside MPageFilters; use MPageToolbar for title + primary action."];
33
- readonly snippets: readonly ["list-filters", "list-toolbar", "list-table", "list-row-actions", "form-header", "form-body", "form-actions", "dashboard-kpi-grid", "dashboard-chart-card", "layout-app-shell"];
54
+ readonly workflow: readonly ["For full pages: recommend_page get_golden_page get_design_rules.", "For local edits: get_page_snippet(section) for filters/toolbar/form-actions/KPI/scrollable-panel blocks.", "Use MLayout fillViewport as the app shell; put MPageContent inside MLayoutContent.", "Prefer MPage* components over scoped CSS for filters, toolbars, headers, form surfaces, and KPI cards.", "Use MSpace or MFlex for control groups inside MPageFilters; use MPageToolbar for title + primary action."];
55
+ readonly snippets: readonly ["list-filters", "list-toolbar", "list-table", "list-row-actions", "form-header", "form-body", "form-actions", "dashboard-kpi-grid", "dashboard-chart-card", "layout-app-shell", "scrollable-panel"];
34
56
  readonly pageStack: {
35
57
  readonly list: readonly ["MLayout", "MLayoutHeader", "MLayoutContent", "MPageContent", "MPageFilters", "MPageToolbar", "MTable"];
36
58
  readonly form: readonly ["MLayout", "MLayoutHeader", "MLayoutContent", "MPageContent", "MPageHeader", "MPageSection", "MForm"];
@@ -94,7 +116,7 @@ export declare const designRules: {
94
116
  };
95
117
  readonly doc: "docs/feedback-message-vs-toast.md";
96
118
  };
97
- readonly global: readonly ["优先使用组件库组件和 --m-* Token,不重复维护第二套色板", "操作反馈默认 message;无 detail 禁止用 toast.add({ summary only })", "图标按钮必须提供 aria-label 或 ariaLabel", "表单字段必须有可见 label 或等价的可访问名称", "浮层默认 Teleport 到 body;只有有明确布局约束时才改 appendTo", "优先使用组件的 documented variant,不通过深层 CSS 覆盖内部样式"];
119
+ readonly global: readonly ["优先使用组件库组件和 --m-* Token", "操作反馈默认 message;仅一行文案时优先于 toast", "图标按钮建议提供 aria-label;表单字段建议有可见 label", "浮层默认 Teleport 到 body;有明确布局约束时再改 appendTo", "优先使用组件 documented variant,少写深层 CSS 覆盖"];
98
120
  };
99
121
  export declare function findPattern(input: string): PagePattern | undefined;
100
122
  export declare function scorePattern(pattern: PagePattern, query: string): number;
package/dist/patterns.js CHANGED
@@ -324,7 +324,127 @@ export const pagePatterns = [
324
324
  ],
325
325
  },
326
326
  ];
327
+ /** Advisory page-writing standards for MCP and human authors — not enforced blockers. */
328
+ export const pageStandards = [
329
+ {
330
+ id: 'layout-shell',
331
+ title: '页面骨架',
332
+ titleEn: 'Page shell',
333
+ recommend: [
334
+ '后台页根布局使用 MLayout fillViewport',
335
+ 'MLayoutContent 内使用 MPageContent 统一 padding 与区块间距',
336
+ '写整页前先 get_golden_page 复制对应类型结构',
337
+ ],
338
+ recommendEn: [
339
+ 'Use MLayout fillViewport as the admin page root',
340
+ 'Place MPageContent inside MLayoutContent for consistent padding and section gap',
341
+ 'Call get_golden_page before writing a full page and copy the matching structure',
342
+ ],
343
+ discouraged: ['手写 min-height:100vh', '在 MLayoutContent 上写 padding/gap'],
344
+ discouragedEn: ['Hand-written min-height:100vh', 'Padding/gap on MLayoutContent instead of MPageContent'],
345
+ mcp: { snippet: 'layout-app-shell' },
346
+ },
347
+ {
348
+ id: 'page-sections',
349
+ title: '页面区块组件',
350
+ titleEn: 'Page section components',
351
+ recommend: [
352
+ '筛选区用 MPageFilters,标题+操作用 MPageToolbar,表单区用 MPageSection',
353
+ '列表页 MTable 直接放在 MPageContent 内',
354
+ '局部区块优先 get_page_snippet(filters / toolbar / form-actions 等)',
355
+ ],
356
+ recommendEn: [
357
+ 'Use MPageFilters, MPageToolbar, and MPageSection for filters, headers/actions, and forms',
358
+ 'Place MTable directly in MPageContent on list pages',
359
+ 'Prefer get_page_snippet for local blocks such as filters, toolbar, or form-actions',
360
+ ],
361
+ discouraged: ['手写 .page-filters / .page-toolbar 类', '用 MCard 包裹 MPageFilters 或 bordered MTable'],
362
+ discouragedEn: [
363
+ 'Hand-written .page-filters / .page-toolbar classes',
364
+ 'Wrapping MPageFilters or bordered MTable with MCard',
365
+ ],
366
+ mcp: { decision: 'surface-nesting-choice' },
367
+ },
368
+ {
369
+ id: 'scroll',
370
+ title: '滚动',
371
+ titleEn: 'Scroll',
372
+ recommend: [
373
+ '组件内置滚动区(Layout 壳、菜单/下拉面板、Table 视口等)使用内置 MScrollbar',
374
+ 'Dialog / Drawer / Popover / Splitter 等内容区不强制主题滚动;需要时由业务自行包 MScrollbar',
375
+ '已内置滚动的组件通常无需再外包一层',
376
+ ],
377
+ recommendEn: [
378
+ 'Component-owned scroll chrome (Layout shell, menu/select panels, Table viewport) uses built-in MScrollbar',
379
+ 'Do not force themed scroll into Dialog / Drawer / Popover / Splitter content; apps can wrap MScrollbar when needed',
380
+ 'Components that already scroll internally usually need no extra wrapper',
381
+ ],
382
+ discouraged: [
383
+ '在 Dialog / Drawer / Popover / Splitter / Textarea 等用户内容区强行内置 MScrollbar',
384
+ '业务代码定制 ::-webkit-scrollbar 替代组件内置滚动',
385
+ '重复包裹已内置滚动的组件',
386
+ ],
387
+ discouragedEn: [
388
+ 'Forcing MScrollbar into user content areas such as Dialog, Drawer, Popover, Splitter, or Textarea',
389
+ '::-webkit-scrollbar styling instead of component-owned scroll chrome',
390
+ 'Extra scroll wrappers around components that already scroll',
391
+ ],
392
+ mcp: { snippet: 'scrollable-panel', decision: 'page-scroll-choice' },
393
+ },
394
+ {
395
+ id: 'tokens',
396
+ title: '设计令牌',
397
+ titleEn: 'Design tokens',
398
+ recommend: ['颜色、间距、圆角、字号优先 --m-* Token', '控件宽度等局部尺寸可用 inline style'],
399
+ recommendEn: [
400
+ 'Prefer --m-* tokens for color, spacing, radius, and typography',
401
+ 'Inline styles are fine for local control widths',
402
+ ],
403
+ discouraged: ['页面级 hex / 裸 rgb() 色值', '维护第二套色板变量'],
404
+ discouragedEn: ['Page-level hex or raw rgb() colors', 'Maintaining a second color palette'],
405
+ },
406
+ {
407
+ id: 'feedback',
408
+ title: '操作反馈',
409
+ titleEn: 'Action feedback',
410
+ recommend: [
411
+ '单行结果(已保存/已删除)默认 message.success / error',
412
+ '需要标题+补充说明时用 toast',
413
+ '危险操作配合 MConfirmDialog',
414
+ ],
415
+ recommendEn: [
416
+ 'Default single-line results to message.success / error',
417
+ 'Use toast when a summary plus detail is needed',
418
+ 'Pair destructive actions with MConfirmDialog',
419
+ ],
420
+ discouraged: ['只有一行文案却用 toast', '删除等危险操作无确认'],
421
+ discouragedEn: ['Toast for single-line-only feedback', 'Destructive actions without confirmation'],
422
+ mcp: { decision: 'overlay-choice' },
423
+ },
424
+ {
425
+ id: 'accessibility',
426
+ title: '无障碍',
427
+ titleEn: 'Accessibility',
428
+ recommend: [
429
+ '图标按钮提供 aria-label 或 ariaLabel',
430
+ '表单控件有可见 label 或等价可访问名称',
431
+ '筛选区、表格等语义区域可加 aria-label',
432
+ ],
433
+ recommendEn: [
434
+ 'Provide aria-label or ariaLabel on icon-only buttons',
435
+ 'Give form controls a visible label or equivalent accessible name',
436
+ 'Add aria-label to semantic regions such as filter bars or tables when helpful',
437
+ ],
438
+ },
439
+ ];
327
440
  export const designRules = {
441
+ meta: {
442
+ nature: 'recommended',
443
+ noteZh: 'standards 为页面书写标准(推荐实践);特殊场景可偏离。validate_page 仅给出参考建议。',
444
+ noteEn: 'standards are recommended page-writing practices; deviations are fine when justified. validate_page is advisory only.',
445
+ },
446
+ /** Page writing standards — single source for layout, scroll, tokens, feedback, a11y, etc. */
447
+ standards: pageStandards,
328
448
  tokens: {
329
449
  colors: ['--m-color-primary', '--m-color-surface', '--m-color-text', '--m-color-border'],
330
450
  spacing: '--m-space-*',
@@ -334,8 +454,8 @@ export const designRules = {
334
454
  },
335
455
  composition: {
336
456
  workflow: [
337
- 'For full pages: recommend_page then get_golden_page before writing layout code.',
338
- 'For local edits: get_page_snippet(section) for filters/toolbar/form-actions/KPI blocks instead of reading the whole golden page.',
457
+ 'For full pages: recommend_page get_golden_page get_design_rules.',
458
+ 'For local edits: get_page_snippet(section) for filters/toolbar/form-actions/KPI/scrollable-panel blocks.',
339
459
  'Use MLayout fillViewport as the app shell; put MPageContent inside MLayoutContent.',
340
460
  'Prefer MPage* components over scoped CSS for filters, toolbars, headers, form surfaces, and KPI cards.',
341
461
  'Use MSpace or MFlex for control groups inside MPageFilters; use MPageToolbar for title + primary action.',
@@ -351,6 +471,7 @@ export const designRules = {
351
471
  'dashboard-kpi-grid',
352
472
  'dashboard-chart-card',
353
473
  'layout-app-shell',
474
+ 'scrollable-panel',
354
475
  ],
355
476
  pageStack: {
356
477
  list: ['MLayout', 'MLayoutHeader', 'MLayoutContent', 'MPageContent', 'MPageFilters', 'MPageToolbar', 'MTable'],
@@ -403,12 +524,11 @@ export const designRules = {
403
524
  doc: 'docs/feedback-message-vs-toast.md',
404
525
  },
405
526
  global: [
406
- '优先使用组件库组件和 --m-* Token,不重复维护第二套色板',
407
- '操作反馈默认 message;无 detail 禁止用 toast.add({ summary only })',
408
- '图标按钮必须提供 aria-label 或 ariaLabel',
409
- '表单字段必须有可见 label 或等价的可访问名称',
410
- '浮层默认 Teleport body;只有有明确布局约束时才改 appendTo',
411
- '优先使用组件的 documented variant,不通过深层 CSS 覆盖内部样式',
527
+ '优先使用组件库组件和 --m-* Token',
528
+ '操作反馈默认 message;仅一行文案时优先于 toast',
529
+ '图标按钮建议提供 aria-label;表单字段建议有可见 label',
530
+ '浮层默认 Teleport 到 body;有明确布局约束时再改 appendTo',
531
+ '优先使用组件 documented variant,少写深层 CSS 覆盖',
412
532
  ],
413
533
  };
414
534
  export function findPattern(input) {
package/dist/tools.js CHANGED
@@ -685,8 +685,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
685
685
  listGoldenPages().find((item) => item.patternId === best.pattern.id)?.id ||
686
686
  best.pattern.id;
687
687
  return locale === 'en-US'
688
- ? `Call get_golden_page("${goldenId}"), get_pattern, get_design_rules, then get_component/get_example. Pass includeScaffold: true for MPage* starter code. Run validate_page on the result.`
689
- : `调用 get_golden_page("${goldenId}")、get_pattern、get_design_rules,再用 get_component/get_example 查 API。需要 starter 时传 includeScaffold: true(已使用 MPage* 组件)。生成后请 validate_page 校验。`;
688
+ ? `Call get_golden_page("${goldenId}"), get_pattern, get_design_rules, then get_component/get_example. Pass includeScaffold: true for starter code. Optionally validate_page.`
689
+ : `调用 get_golden_page("${goldenId}")、get_pattern、get_design_rules,再用 get_component/get_example 查 API。需要 starter 时传 includeScaffold: true。可选用 validate_page。`;
690
690
  })(),
691
691
  };
692
692
  if (args.includeScaffold) {
@@ -720,8 +720,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
720
720
  file: payload.record.file,
721
721
  source: payload.source,
722
722
  nextStep: locale === 'en-US'
723
- ? 'Copy this structure and replace business data. Prefer MPage* components over custom scoped CSS.'
724
- : '复制此结构并替换业务数据。优先使用 MPage* 组件,而不是自定义 scoped CSS。',
723
+ ? 'Copy this structure and replace business data. Prefer MPage* components; see get_design_rules for page standards.'
724
+ : '复制此结构并替换业务数据。优先使用 MPage* 组件;页面标准见 get_design_rules。',
725
725
  });
726
726
  }
727
727
  function listGoldenPageCatalog(args = {}) {
@@ -800,75 +800,130 @@ export function createToolHandlers(catalog = loadCatalog()) {
800
800
  function validatePage(args) {
801
801
  const locale = resolveLocale(args.mode);
802
802
  const code = args.code || '';
803
- const issues = [];
803
+ const suggestions = [];
804
804
  if (/#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})\b/i.test(code)) {
805
- issues.push({
805
+ suggestions.push({
806
+ standardId: 'tokens',
806
807
  type: 'raw-color',
807
808
  message: locale === 'en-US'
808
- ? 'Use --m-* design tokens instead of hex colors.'
809
- : '请使用 --m-* 设计令牌,不要写 hex 色值。',
809
+ ? 'Standard: prefer --m-* design tokens over hex colors.'
810
+ : '标准建议:颜色优先使用 --m-* 设计令牌,而非 hex 色值。',
810
811
  });
811
812
  }
812
813
  if (/\brgb\s*\(/i.test(code) && !/--m-/.test(code)) {
813
- issues.push({
814
+ suggestions.push({
815
+ standardId: 'tokens',
814
816
  type: 'raw-color',
815
817
  message: locale === 'en-US'
816
- ? 'Use --m-* tokens instead of raw rgb()/rgba() page styles.'
817
- : '页面样式请使用 --m-* 令牌,不要写裸 rgb()/rgba()。',
818
+ ? 'Standard: prefer --m-* tokens over raw rgb()/rgba() in page styles.'
819
+ : '标准建议:页面样式优先 --m-* 令牌,而非裸 rgb()/rgba()。',
818
820
  });
819
821
  }
820
822
  if (/<MCard\b[^>]*>[\s\S]*?<MTable\b[^>]*\bbordered\b/i.test(code)) {
821
- issues.push({
823
+ suggestions.push({
824
+ standardId: 'page-sections',
822
825
  type: 'double-border',
823
826
  message: locale === 'en-US'
824
- ? 'Avoid wrapping bordered MTable with MCard; place MTable directly in MPageContent.'
825
- : '避免用 MCard 包裹 bordered 的 MTable;表格应直接放在 MPageContent 内。',
827
+ ? 'Standard: place bordered MTable directly in MPageContent instead of wrapping it with MCard.'
828
+ : '标准建议:bordered 的 MTable 直接放在 MPageContent 内,通常不必再包 MCard。',
826
829
  });
827
830
  }
828
831
  if (/<MLayoutContent\b/i.test(code) && !/<MPageContent\b/i.test(code)) {
829
- issues.push({
832
+ suggestions.push({
833
+ standardId: 'layout-shell',
830
834
  type: 'missing-page-content',
831
835
  message: locale === 'en-US'
832
- ? 'Prefer MPageContent inside MLayoutContent for consistent page spacing.'
833
- : '建议在 MLayoutContent 内使用 MPageContent 统一页面间距。',
836
+ ? 'Standard: prefer MPageContent inside MLayoutContent for consistent page spacing.'
837
+ : '标准建议:MLayoutContent 内使用 MPageContent 统一页面间距。',
834
838
  });
835
839
  }
836
840
  if ((/\bpage[-_](?:list|form|dashboard)?[-_]?(?:filters|toolbar|content|header)\b/i.test(code) ||
837
841
  /class="[^"]*filters/i.test(code)) &&
838
842
  !/<MPage(?:Content|Filters|Toolbar|Header|Section|Stat)\b/i.test(code)) {
839
- issues.push({
843
+ suggestions.push({
844
+ standardId: 'page-sections',
840
845
  type: 'custom-page-css',
841
846
  message: locale === 'en-US'
842
- ? 'Replace custom page section CSS with MPageFilters, MPageToolbar, MPageHeader, or MPageSection.'
843
- : '请用 MPageFilters / MPageToolbar / MPageHeader / MPageSection 替代自定义页面区块 CSS。',
847
+ ? 'Standard: prefer MPageFilters, MPageToolbar, MPageHeader, or MPageSection over custom page section CSS.'
848
+ : '标准建议:筛选/工具栏/表单区块优先 MPage* 组件,少写自定义 page section CSS。',
844
849
  });
845
850
  }
846
851
  if (/<MPageFilters\b/i.test(code) && /<MCard\b[^>]*>[\s\S]*?<MPageFilters\b/i.test(code)) {
847
- issues.push({
852
+ suggestions.push({
853
+ standardId: 'page-sections',
848
854
  type: 'redundant-wrapper',
849
855
  message: locale === 'en-US'
850
- ? 'Do not wrap MPageFilters in MCard; MPageFilters already provides the surface.'
851
- : '不要用 MCard 包裹 MPageFilters;MPageFilters 已自带表面样式。',
856
+ ? 'Standard: MPageFilters already provides a surface; wrapping it in MCard is usually redundant.'
857
+ : '标准建议:MPageFilters 已自带表面样式,通常不必再用 MCard 包裹。',
858
+ });
859
+ }
860
+ if (/style="[^"]*overflow(?:-y|-x)?\s*:\s*(auto|scroll)/i.test(code) ||
861
+ /<style\b[^>]*>[\s\S]*?overflow(?:-y|-x)?\s*:\s*(auto|scroll)/i.test(code)) {
862
+ suggestions.push({
863
+ standardId: 'scroll',
864
+ type: 'native-scroll',
865
+ message: locale === 'en-US'
866
+ ? 'Standard: prefer MScrollbar or MLayout scroll regions over overflow:auto/scroll in page styles.'
867
+ : '标准建议:页面滚动优先 MLayout 主滚动或 MScrollbar,少写 overflow:auto/scroll。',
868
+ });
869
+ }
870
+ if (/::-webkit-scrollbar|scrollbar-width\s*:/i.test(code)) {
871
+ suggestions.push({
872
+ standardId: 'scroll',
873
+ type: 'custom-scrollbar-css',
874
+ message: locale === 'en-US'
875
+ ? 'Standard: prefer MScrollbar or layout defaults over native scrollbar CSS in product code.'
876
+ : '标准建议:业务代码优先 MScrollbar 或 Layout 默认滚动,少定制原生滚动条样式。',
877
+ });
878
+ }
879
+ if (/min-height\s*:\s*100vh/i.test(code) && !/<MLayout\b[^>]*fill-viewport/i.test(code)) {
880
+ suggestions.push({
881
+ standardId: 'layout-shell',
882
+ type: 'viewport-height',
883
+ message: locale === 'en-US'
884
+ ? 'Standard: prefer MLayout fillViewport over hand-written min-height:100vh page shells.'
885
+ : '标准建议:整页高度优先 MLayout fillViewport,少写 min-height:100vh。',
852
886
  });
853
887
  }
854
888
  return textResult({
855
- ok: issues.length === 0,
856
- issues,
857
- recommendations: locale === 'en-US'
858
- ? designRules.composition.workflow
859
- : [
860
- '生成页面前先调用 recommend_page get_golden_page。',
861
- 'MLayout 使用 fillViewport;MLayoutContent 内放 MPageContent。',
862
- '筛选区用 MPageFilters,标题+操作用 MPageToolbar,表单用 MPageSection。',
863
- '列表表格不要额外包 MCard。',
864
- ],
889
+ ok: true,
890
+ advisory: true,
891
+ summary: locale === 'en-US'
892
+ ? suggestions.length === 0
893
+ ? 'No deviations from page standards detected.'
894
+ : `${suggestions.length} advisory suggestion(s).`
895
+ : suggestions.length === 0
896
+ ? '未发现与页面标准的明显偏差。'
897
+ : `${suggestions.length} 条参考建议。`,
898
+ suggestions,
899
+ nextStep: locale === 'en-US'
900
+ ? 'See get_design_rules.standards for the full recommended page standards.'
901
+ : '完整页面标准见 get_design_rules.standards。',
865
902
  });
866
903
  }
867
904
  function getDesignRules(args = {}) {
868
905
  const locale = resolveLocale(args.mode);
869
- if (locale === 'zh-CN')
870
- return textResult(designRules);
906
+ if (locale === 'zh-CN') {
907
+ return textResult({
908
+ meta: designRules.meta,
909
+ standards: designRules.standards,
910
+ tokens: designRules.tokens,
911
+ composition: designRules.composition,
912
+ actions: designRules.actions,
913
+ status: designRules.status,
914
+ feedback: designRules.feedback,
915
+ global: designRules.global,
916
+ });
917
+ }
871
918
  return textResult({
919
+ meta: designRules.meta,
920
+ standards: designRules.standards.map((item) => ({
921
+ id: item.id,
922
+ title: item.titleEn,
923
+ recommend: item.recommendEn,
924
+ discouraged: item.discouragedEn,
925
+ mcp: item.mcp,
926
+ })),
872
927
  tokens: {
873
928
  colors: ['--m-color-primary', '--m-color-surface', '--m-color-text', '--m-color-border'],
874
929
  spacing: '--m-space-*',
@@ -892,10 +947,10 @@ export function createToolHandlers(catalog = loadCatalog()) {
892
947
  doc: 'docs/feedback-message-vs-toast.md',
893
948
  },
894
949
  global: [
895
- 'Prefer library components and --m-* tokens; do not maintain a second color system.',
896
- 'Default action feedback to message; do not use toast with summary-only text.',
897
- 'Icon-only buttons must provide aria-label or ariaLabel.',
898
- 'Form controls must have a visible label or an equivalent accessible name.',
950
+ 'Prefer library components and --m-* tokens.',
951
+ 'Default action feedback to message; prefer message over toast for single-line-only text.',
952
+ 'Icon-only buttons should provide aria-label or ariaLabel.',
953
+ 'Form controls should have a visible label or an equivalent accessible name.',
899
954
  'Overlays teleport to body by default; only change appendTo for a clear layout constraint.',
900
955
  'Prefer documented component variants over deep CSS overrides.',
901
956
  ],
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@morya-ui/mcp",
3
3
  "type": "module",
4
- "version": "0.2.3",
4
+ "version": "0.2.4",
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",