@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.
- package/data/catalog.json +79 -39
- package/data/example-coverage.json +4 -6
- package/dist/__tests__/tools.test.js +49 -3
- package/dist/decisions.js +70 -0
- package/dist/page-snippets.js +49 -0
- package/dist/patterns.d.ts +25 -3
- package/dist/patterns.js +128 -8
- package/dist/tools.js +94 -39
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-09-
|
|
3
|
-
"catalogGeneratedAt": "2026-09-
|
|
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":
|
|
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('
|
|
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(
|
|
115
|
-
expect(result.
|
|
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: '如何避免双边框与多余容器',
|
package/dist/page-snippets.js
CHANGED
|
@@ -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, '');
|
package/dist/patterns.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
338
|
-
'For local edits: get_page_snippet(section) for filters/toolbar/form-actions/KPI blocks
|
|
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
|
|
408
|
-
'
|
|
409
|
-
'
|
|
410
|
-
'
|
|
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
|
|
689
|
-
: `调用 get_golden_page("${goldenId}")、get_pattern、get_design_rules,再用 get_component/get_example 查 API。需要 starter 时传 includeScaffold: true
|
|
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
|
|
724
|
-
: '复制此结构并替换业务数据。优先使用 MPage*
|
|
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
|
|
803
|
+
const suggestions = [];
|
|
804
804
|
if (/#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})\b/i.test(code)) {
|
|
805
|
-
|
|
805
|
+
suggestions.push({
|
|
806
|
+
standardId: 'tokens',
|
|
806
807
|
type: 'raw-color',
|
|
807
808
|
message: locale === 'en-US'
|
|
808
|
-
? '
|
|
809
|
-
: '
|
|
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
|
-
|
|
814
|
+
suggestions.push({
|
|
815
|
+
standardId: 'tokens',
|
|
814
816
|
type: 'raw-color',
|
|
815
817
|
message: locale === 'en-US'
|
|
816
|
-
? '
|
|
817
|
-
: '
|
|
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
|
-
|
|
823
|
+
suggestions.push({
|
|
824
|
+
standardId: 'page-sections',
|
|
822
825
|
type: 'double-border',
|
|
823
826
|
message: locale === 'en-US'
|
|
824
|
-
? '
|
|
825
|
-
: '
|
|
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
|
-
|
|
832
|
+
suggestions.push({
|
|
833
|
+
standardId: 'layout-shell',
|
|
830
834
|
type: 'missing-page-content',
|
|
831
835
|
message: locale === 'en-US'
|
|
832
|
-
? '
|
|
833
|
-
: '
|
|
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
|
-
|
|
843
|
+
suggestions.push({
|
|
844
|
+
standardId: 'page-sections',
|
|
840
845
|
type: 'custom-page-css',
|
|
841
846
|
message: locale === 'en-US'
|
|
842
|
-
? '
|
|
843
|
-
: '
|
|
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
|
-
|
|
852
|
+
suggestions.push({
|
|
853
|
+
standardId: 'page-sections',
|
|
848
854
|
type: 'redundant-wrapper',
|
|
849
855
|
message: locale === 'en-US'
|
|
850
|
-
? '
|
|
851
|
-
: '
|
|
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:
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
?
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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(
|
|
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
|
|
896
|
-
'Default action feedback to message;
|
|
897
|
-
'Icon-only buttons
|
|
898
|
-
'Form controls
|
|
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.
|
|
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",
|