@morya-ui/mcp 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/data/catalog.json +1871 -1017
- package/data/example-coverage.json +100 -9
- package/data/golden-pages/dashboard-page.vue +1 -1
- package/data/golden-pages/empty-state.vue +66 -0
- package/data/golden-pages/form-page.vue +7 -3
- package/data/golden-pages/landing-page.vue +328 -0
- package/data/golden-pages/list-page.vue +16 -6
- package/data/golden-pages/login-page.vue +191 -0
- package/dist/__tests__/resources.test.js +1 -1
- package/dist/__tests__/tools.test.js +56 -3
- package/dist/decisions.js +70 -0
- package/dist/golden-pages.js +22 -1
- package/dist/index.js +1 -1
- package/dist/page-snippets.js +115 -2
- package/dist/patterns.d.ts +41 -7
- package/dist/patterns.js +315 -33
- package/dist/resources.d.ts +1 -1
- package/dist/tools.js +153 -56
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -3,7 +3,7 @@ import { componentDecisions, findDecision, scoreDecision } from './decisions.js'
|
|
|
3
3
|
import { listGoldenPages, readGoldenPageSource } from './golden-pages.js';
|
|
4
4
|
import { filterPageSnippets, findPageSnippet, pageSnippets, scorePageSnippet } from './page-snippets.js';
|
|
5
5
|
import { designRules, findPattern, pagePatterns, scorePattern } from './patterns.js';
|
|
6
|
-
import {
|
|
6
|
+
import { countCatalogResources, countCatalogResourceTemplates } from './resources.js';
|
|
7
7
|
function inspectButtonIconOnlyUsage(code, issues) {
|
|
8
8
|
const pairedTagRe = /<MButton\b([^>]*)>([\s\S]*?)<\/MButton>/gi;
|
|
9
9
|
let match = pairedTagRe.exec(code);
|
|
@@ -65,16 +65,17 @@ function generatedPageCode(patternId, intent, locale) {
|
|
|
65
65
|
const pageImports = useLayoutShell
|
|
66
66
|
? ', MPageContent, MPageFilters, MPageHeader, MPageSection, MPageToolbar'
|
|
67
67
|
: '';
|
|
68
|
-
const listImports = isList ? ', MSelect, MSpace, MTable' : '';
|
|
68
|
+
const listImports = isList ? ', MEmpty, MSelect, MSpace, MTable' : '';
|
|
69
69
|
const formImports = isForm || isAuth || isWizard ? ', MForm, MFormItem, MSelect' : '';
|
|
70
70
|
const dashboardImports = isDashboard ? ', MCard, MGrid, MGridItem, MPagePlaceholder, MPageStat, MSkeleton, MTable' : '';
|
|
71
71
|
const detailImports = isDetail ? ', MDivider' : '';
|
|
72
|
-
const emptyImports = isEmpty ? ',
|
|
72
|
+
const emptyImports = isEmpty ? ', MEmpty, MPageToolbar' : '';
|
|
73
73
|
const wizardImports = isWizard ? ', MStepper' : '';
|
|
74
74
|
const settingsImports = isSettings ? ', MTabs' : '';
|
|
75
|
+
const statusImports = isList || isDetail || isDashboard ? ', MStatus' : '';
|
|
75
76
|
const script = `<script setup lang="ts">
|
|
76
77
|
import { ref } from 'vue'
|
|
77
|
-
import { MButton, MCard, MConfigProvider, MInput, MTag, zhCN${layoutImports}${pageImports}${listImports}${formImports}${dashboardImports}${detailImports}${emptyImports}${wizardImports}${settingsImports} } from 'morya-ui'
|
|
78
|
+
import { MButton, MCard, MConfigProvider, MInput, MTag, zhCN${layoutImports}${pageImports}${listImports}${formImports}${dashboardImports}${detailImports}${emptyImports}${wizardImports}${settingsImports}${statusImports} } from 'morya-ui'
|
|
78
79
|
|
|
79
80
|
const loading = ref(false)
|
|
80
81
|
const error = ref('')
|
|
@@ -112,8 +113,18 @@ async function submit() {
|
|
|
112
113
|
</template>
|
|
113
114
|
</MPageToolbar>
|
|
114
115
|
<MTable :columns="columns" :rows="rows" :loading="loading" paginator :rows-per-page="10" striped bordered row-key="id">
|
|
116
|
+
<template #cell-status="{ value }">
|
|
117
|
+
<MStatus :label="String(value ?? '')" :severity="value === 'active' ? 'success' : 'secondary'" />
|
|
118
|
+
</template>
|
|
115
119
|
<template #empty>
|
|
116
|
-
<
|
|
120
|
+
<MEmpty
|
|
121
|
+
:title="${zh ? '暂无数据' : 'No data yet'}"
|
|
122
|
+
:description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
|
|
123
|
+
>
|
|
124
|
+
<template #extra>
|
|
125
|
+
<MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
|
|
126
|
+
</template>
|
|
127
|
+
</MEmpty>
|
|
117
128
|
</template>
|
|
118
129
|
</MTable>`;
|
|
119
130
|
const formContent = ` <MPageHeader :title="title" :description="${zh ? '填写表单并保存。' : 'Fill in the form and save.'}" />
|
|
@@ -210,17 +221,20 @@ ${content}
|
|
|
210
221
|
else if (isEmpty) {
|
|
211
222
|
innerTemplate = `<MConfigProvider :locale="zhCN">
|
|
212
223
|
<main class="m-generated-page">
|
|
213
|
-
<
|
|
214
|
-
<
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
+
<MPageToolbar :title="title">
|
|
225
|
+
<template #actions>
|
|
226
|
+
<MButton severity="primary" @click="submit">${zh ? '新建' : 'Create'}</MButton>
|
|
227
|
+
</template>
|
|
228
|
+
</MPageToolbar>
|
|
229
|
+
<MEmpty
|
|
230
|
+
:title="${zh ? '暂无内容' : 'Nothing here yet'}"
|
|
231
|
+
:description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
|
|
232
|
+
icon="database"
|
|
233
|
+
>
|
|
234
|
+
<template #extra>
|
|
235
|
+
<MButton severity="primary" @click="submit">${zh ? '创建' : 'Create'}</MButton>
|
|
236
|
+
</template>
|
|
237
|
+
</MEmpty>
|
|
224
238
|
</main>
|
|
225
239
|
</MConfigProvider>`;
|
|
226
240
|
}
|
|
@@ -685,8 +699,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
685
699
|
listGoldenPages().find((item) => item.patternId === best.pattern.id)?.id ||
|
|
686
700
|
best.pattern.id;
|
|
687
701
|
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
|
|
702
|
+
? `Call get_golden_page("${goldenId}"), get_pattern, get_design_rules, then get_component/get_example. Pass includeScaffold: true for starter code. Optionally validate_page.`
|
|
703
|
+
: `调用 get_golden_page("${goldenId}")、get_pattern、get_design_rules,再用 get_component/get_example 查 API。需要 starter 时传 includeScaffold: true。可选用 validate_page。`;
|
|
690
704
|
})(),
|
|
691
705
|
};
|
|
692
706
|
if (args.includeScaffold) {
|
|
@@ -720,8 +734,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
720
734
|
file: payload.record.file,
|
|
721
735
|
source: payload.source,
|
|
722
736
|
nextStep: locale === 'en-US'
|
|
723
|
-
? 'Copy this structure and replace business data. Prefer MPage* components
|
|
724
|
-
: '复制此结构并替换业务数据。优先使用 MPage*
|
|
737
|
+
? 'Copy this structure and replace business data. Prefer MPage* components; see get_design_rules for page standards.'
|
|
738
|
+
: '复制此结构并替换业务数据。优先使用 MPage* 组件;页面标准见 get_design_rules。',
|
|
725
739
|
});
|
|
726
740
|
}
|
|
727
741
|
function listGoldenPageCatalog(args = {}) {
|
|
@@ -800,75 +814,158 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
800
814
|
function validatePage(args) {
|
|
801
815
|
const locale = resolveLocale(args.mode);
|
|
802
816
|
const code = args.code || '';
|
|
803
|
-
const
|
|
817
|
+
const suggestions = [];
|
|
804
818
|
if (/#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})\b/i.test(code)) {
|
|
805
|
-
|
|
819
|
+
suggestions.push({
|
|
820
|
+
standardId: 'tokens',
|
|
806
821
|
type: 'raw-color',
|
|
807
822
|
message: locale === 'en-US'
|
|
808
|
-
? '
|
|
809
|
-
: '
|
|
823
|
+
? 'Standard: prefer --m-* design tokens over hex colors.'
|
|
824
|
+
: '标准建议:颜色优先使用 --m-* 设计令牌,而非 hex 色值。',
|
|
810
825
|
});
|
|
811
826
|
}
|
|
812
827
|
if (/\brgb\s*\(/i.test(code) && !/--m-/.test(code)) {
|
|
813
|
-
|
|
828
|
+
suggestions.push({
|
|
829
|
+
standardId: 'tokens',
|
|
814
830
|
type: 'raw-color',
|
|
815
831
|
message: locale === 'en-US'
|
|
816
|
-
? '
|
|
817
|
-
: '
|
|
832
|
+
? 'Standard: prefer --m-* tokens over raw rgb()/rgba() in page styles.'
|
|
833
|
+
: '标准建议:页面样式优先 --m-* 令牌,而非裸 rgb()/rgba()。',
|
|
818
834
|
});
|
|
819
835
|
}
|
|
820
836
|
if (/<MCard\b[^>]*>[\s\S]*?<MTable\b[^>]*\bbordered\b/i.test(code)) {
|
|
821
|
-
|
|
837
|
+
suggestions.push({
|
|
838
|
+
standardId: 'page-sections',
|
|
822
839
|
type: 'double-border',
|
|
823
840
|
message: locale === 'en-US'
|
|
824
|
-
? '
|
|
825
|
-
: '
|
|
841
|
+
? 'Standard: place bordered MTable directly in MPageContent instead of wrapping it with MCard.'
|
|
842
|
+
: '标准建议:bordered 的 MTable 直接放在 MPageContent 内,通常不必再包 MCard。',
|
|
826
843
|
});
|
|
827
844
|
}
|
|
828
845
|
if (/<MLayoutContent\b/i.test(code) && !/<MPageContent\b/i.test(code)) {
|
|
829
|
-
|
|
846
|
+
suggestions.push({
|
|
847
|
+
standardId: 'layout-shell',
|
|
830
848
|
type: 'missing-page-content',
|
|
831
849
|
message: locale === 'en-US'
|
|
832
|
-
? '
|
|
833
|
-
: '
|
|
850
|
+
? 'Standard: prefer MPageContent inside MLayoutContent for consistent page spacing.'
|
|
851
|
+
: '标准建议:MLayoutContent 内使用 MPageContent 统一页面间距。',
|
|
834
852
|
});
|
|
835
853
|
}
|
|
836
854
|
if ((/\bpage[-_](?:list|form|dashboard)?[-_]?(?:filters|toolbar|content|header)\b/i.test(code) ||
|
|
837
855
|
/class="[^"]*filters/i.test(code)) &&
|
|
838
856
|
!/<MPage(?:Content|Filters|Toolbar|Header|Section|Stat)\b/i.test(code)) {
|
|
839
|
-
|
|
857
|
+
suggestions.push({
|
|
858
|
+
standardId: 'page-sections',
|
|
840
859
|
type: 'custom-page-css',
|
|
841
860
|
message: locale === 'en-US'
|
|
842
|
-
? '
|
|
843
|
-
: '
|
|
861
|
+
? 'Standard: prefer MPageFilters, MPageToolbar, MPageHeader, or MPageSection over custom page section CSS.'
|
|
862
|
+
: '标准建议:筛选/工具栏/表单区块优先 MPage* 组件,少写自定义 page section CSS。',
|
|
844
863
|
});
|
|
845
864
|
}
|
|
846
865
|
if (/<MPageFilters\b/i.test(code) && /<MCard\b[^>]*>[\s\S]*?<MPageFilters\b/i.test(code)) {
|
|
847
|
-
|
|
866
|
+
suggestions.push({
|
|
867
|
+
standardId: 'page-sections',
|
|
848
868
|
type: 'redundant-wrapper',
|
|
849
869
|
message: locale === 'en-US'
|
|
850
|
-
? '
|
|
851
|
-
: '
|
|
870
|
+
? 'Standard: MPageFilters already provides a surface; wrapping it in MCard is usually redundant.'
|
|
871
|
+
: '标准建议:MPageFilters 已自带表面样式,通常不必再用 MCard 包裹。',
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
if (/<MPageSection\b[^>]*variant=["']form["']/i.test(code) &&
|
|
875
|
+
/<MCard\b[^>]*>[\s\S]*?<MPageSection\b/i.test(code)) {
|
|
876
|
+
suggestions.push({
|
|
877
|
+
standardId: 'page-sections',
|
|
878
|
+
type: 'redundant-wrapper',
|
|
879
|
+
message: locale === 'en-US'
|
|
880
|
+
? 'Standard: MPageSection variant="form" already provides a surface; wrapping it in MCard is usually redundant.'
|
|
881
|
+
: '标准建议:MPageSection variant="form" 已自带表面样式,通常不必再用 MCard 包裹。',
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
if (/<MPageContent\b[^>]*>[\s\S]*?<MPageContent\b/i.test(code)) {
|
|
885
|
+
suggestions.push({
|
|
886
|
+
standardId: 'layout-shell',
|
|
887
|
+
type: 'redundant-wrapper',
|
|
888
|
+
message: locale === 'en-US'
|
|
889
|
+
? 'Standard: prefer a single MPageContent shell for page padding and section gap.'
|
|
890
|
+
: '标准建议:页面 padding 与区块间距优先只用一层 MPageContent。',
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
if (/<(?:div|section|main)\b[^>]*style=["'][^"']*(?:padding|gap)\s*:[^"']*["'][^>]*>\s*<(?:MPage(?:Content|Filters|Toolbar|Header|Section)|MTable)\b/i.test(code)) {
|
|
894
|
+
suggestions.push({
|
|
895
|
+
standardId: 'page-sections',
|
|
896
|
+
type: 'redundant-wrapper',
|
|
897
|
+
message: locale === 'en-US'
|
|
898
|
+
? 'Standard: avoid extra padded wrappers around MPage* blocks; prefer the components’ built-in spacing.'
|
|
899
|
+
: '标准建议:少在 MPage* 外包带 padding 的容器;优先使用组件自带间距。',
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
if (/style="[^"]*overflow(?:-y|-x)?\s*:\s*(auto|scroll)/i.test(code) ||
|
|
903
|
+
/<style\b[^>]*>[\s\S]*?overflow(?:-y|-x)?\s*:\s*(auto|scroll)/i.test(code)) {
|
|
904
|
+
suggestions.push({
|
|
905
|
+
standardId: 'scroll',
|
|
906
|
+
type: 'native-scroll',
|
|
907
|
+
message: locale === 'en-US'
|
|
908
|
+
? 'Standard: prefer MScrollbar or MLayout scroll regions over overflow:auto/scroll in page styles.'
|
|
909
|
+
: '标准建议:页面滚动优先 MLayout 主滚动或 MScrollbar,少写 overflow:auto/scroll。',
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
if (/::-webkit-scrollbar|scrollbar-width\s*:/i.test(code)) {
|
|
913
|
+
suggestions.push({
|
|
914
|
+
standardId: 'scroll',
|
|
915
|
+
type: 'custom-scrollbar-css',
|
|
916
|
+
message: locale === 'en-US'
|
|
917
|
+
? 'Standard: prefer MScrollbar or layout defaults over native scrollbar CSS in product code.'
|
|
918
|
+
: '标准建议:业务代码优先 MScrollbar 或 Layout 默认滚动,少定制原生滚动条样式。',
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
if (/min-height\s*:\s*100vh/i.test(code) && !/<MLayout\b[^>]*fill-viewport/i.test(code)) {
|
|
922
|
+
suggestions.push({
|
|
923
|
+
standardId: 'layout-shell',
|
|
924
|
+
type: 'viewport-height',
|
|
925
|
+
message: locale === 'en-US'
|
|
926
|
+
? 'Standard: prefer MLayout fillViewport over hand-written min-height:100vh page shells.'
|
|
927
|
+
: '标准建议:整页高度优先 MLayout fillViewport,少写 min-height:100vh。',
|
|
852
928
|
});
|
|
853
929
|
}
|
|
854
930
|
return textResult({
|
|
855
|
-
ok:
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
?
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
931
|
+
ok: true,
|
|
932
|
+
advisory: true,
|
|
933
|
+
summary: locale === 'en-US'
|
|
934
|
+
? suggestions.length === 0
|
|
935
|
+
? 'No deviations from page standards detected.'
|
|
936
|
+
: `${suggestions.length} advisory suggestion(s).`
|
|
937
|
+
: suggestions.length === 0
|
|
938
|
+
? '未发现与页面标准的明显偏差。'
|
|
939
|
+
: `${suggestions.length} 条参考建议。`,
|
|
940
|
+
suggestions,
|
|
941
|
+
nextStep: locale === 'en-US'
|
|
942
|
+
? 'See get_design_rules.standards for the full recommended page standards.'
|
|
943
|
+
: '完整页面标准见 get_design_rules.standards。',
|
|
865
944
|
});
|
|
866
945
|
}
|
|
867
946
|
function getDesignRules(args = {}) {
|
|
868
947
|
const locale = resolveLocale(args.mode);
|
|
869
|
-
if (locale === 'zh-CN')
|
|
870
|
-
return textResult(
|
|
948
|
+
if (locale === 'zh-CN') {
|
|
949
|
+
return textResult({
|
|
950
|
+
meta: designRules.meta,
|
|
951
|
+
standards: designRules.standards,
|
|
952
|
+
tokens: designRules.tokens,
|
|
953
|
+
composition: designRules.composition,
|
|
954
|
+
actions: designRules.actions,
|
|
955
|
+
status: designRules.status,
|
|
956
|
+
feedback: designRules.feedback,
|
|
957
|
+
global: designRules.global,
|
|
958
|
+
});
|
|
959
|
+
}
|
|
871
960
|
return textResult({
|
|
961
|
+
meta: designRules.meta,
|
|
962
|
+
standards: designRules.standards.map((item) => ({
|
|
963
|
+
id: item.id,
|
|
964
|
+
title: item.titleEn,
|
|
965
|
+
recommend: item.recommendEn,
|
|
966
|
+
discouraged: item.discouragedEn,
|
|
967
|
+
mcp: item.mcp,
|
|
968
|
+
})),
|
|
872
969
|
tokens: {
|
|
873
970
|
colors: ['--m-color-primary', '--m-color-surface', '--m-color-text', '--m-color-border'],
|
|
874
971
|
spacing: '--m-space-*',
|
|
@@ -888,14 +985,14 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
888
985
|
default: 'message',
|
|
889
986
|
message: { when: ['single-line action result', 'save/delete/create confirmations'] },
|
|
890
987
|
toast: { when: ['summary + detail', 'async or background notifications'] },
|
|
891
|
-
inlineMessage: {
|
|
988
|
+
inlineMessage: { api: 'field errorMessage | token-styled role=alert', when: ['persistent form/auth errors'] },
|
|
892
989
|
doc: 'docs/feedback-message-vs-toast.md',
|
|
893
990
|
},
|
|
894
991
|
global: [
|
|
895
|
-
'Prefer library components and --m-* tokens
|
|
896
|
-
'Default action feedback to message;
|
|
897
|
-
'Icon-only buttons
|
|
898
|
-
'Form controls
|
|
992
|
+
'Prefer library components and --m-* tokens.',
|
|
993
|
+
'Default action feedback to message; prefer message over toast for single-line-only text.',
|
|
994
|
+
'Icon-only buttons should provide aria-label or ariaLabel.',
|
|
995
|
+
'Form controls should have a visible label or an equivalent accessible name.',
|
|
899
996
|
'Overlays teleport to body by default; only change appendTo for a clear layout constraint.',
|
|
900
997
|
'Prefer documented component variants over deep CSS overrides.',
|
|
901
998
|
],
|
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.5",
|
|
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",
|