@morya-ui/mcp 0.2.9 → 0.3.0

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/dist/tools.js CHANGED
@@ -57,7 +57,7 @@ function generatedPageCode(patternId, intent, locale) {
57
57
  const isSettings = patternId === 'settings-page';
58
58
  const isAuth = patternId === 'auth-page';
59
59
  const isForm = patternId === 'form-page' || isSettings;
60
- const useLayoutShell = isList || isForm || isDashboard || isDetail || isSettings;
60
+ const useLayoutShell = isList || isForm || isDashboard || isDetail || isSettings || isWizard;
61
61
  const title = intent || (zh ? '业务页面' : 'Business page');
62
62
  const layoutImports = useLayoutShell
63
63
  ? ', MLayout, MLayoutContent, MLayoutHeader, MLayoutSider, MBreadcrumb'
@@ -68,9 +68,9 @@ function generatedPageCode(patternId, intent, locale) {
68
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
- const detailImports = isDetail ? ', MDivider' : '';
71
+ const detailImports = isDetail ? ', MSpace' : '';
72
72
  const emptyImports = isEmpty ? ', MEmpty, MPageToolbar' : '';
73
- const wizardImports = isWizard ? ', MStepper' : '';
73
+ const wizardImports = isWizard ? ', MStepper, MSpace' : '';
74
74
  const settingsImports = isSettings ? ', MTabs' : '';
75
75
  const statusImports = isList || isDetail || isDashboard ? ', MStatus' : '';
76
76
  const script = `<script setup lang="ts">
@@ -79,6 +79,7 @@ import { MButton, MCard, MConfigProvider, MInput, MTag, zhCN${layoutImports}${pa
79
79
 
80
80
  const loading = ref(false)
81
81
  const error = ref('')
82
+ const title = ${JSON.stringify(title)}
82
83
  ${isList ? `const keyword = ref('')
83
84
  const rows = ref<Record<string, unknown>[]>([])
84
85
  const columns = [{ key: 'name', label: '${zh ? '名称' : 'Name'}' }, { key: 'status', label: '${zh ? '状态' : 'Status'}' }]
@@ -87,7 +88,10 @@ const columns = [{ key: 'name', label: '${zh ? '名称' : 'Name'}' }, { key: 'st
87
88
  { label: '${zh ? '总用户' : 'Users'}', value: '0' },
88
89
  { label: '${zh ? '今日活跃' : 'Active today'}', value: '0' },
89
90
  ])
91
+ ` : ''}${isSettings ? `const activeTab = ref('general')
92
+ const settingTabs = [{ label: '${zh ? '常规' : 'General'}', value: 'general' }]
90
93
  ` : ''}${isWizard ? `const activeStep = ref(0)
94
+ const wizardSteps = [{ label: '${zh ? '基本信息' : 'Details'}' }, { label: '${zh ? '确认' : 'Confirm'}' }]
91
95
  ` : ''}
92
96
 
93
97
  async function submit() {
@@ -149,24 +153,48 @@ async function submit() {
149
153
  <MSkeleton v-if="loading" height="8rem" />
150
154
  <MPagePlaceholder v-else :description="${zh ? '接入图表或业务组件。' : 'Connect charts or business widgets here.'}" aria-label="${zh ? '图表占位' : 'Chart placeholder'}" />
151
155
  </MCard>`;
152
- const detailContent = ` <MPageToolbar :title="title">
156
+ const detailContent = ` <MPageHeader :title="title" :description="${zh ? '查看资源摘要与属性。' : 'Review summary and properties.'}">
153
157
  <template #actions>
154
- <MButton severity="primary" outlined>${zh ? '编辑' : 'Edit'}</MButton>
158
+ <MSpace>
159
+ <MStatus :label="${zh ? '启用' : 'Active'}" severity="success" />
160
+ <MButton severity="primary">${zh ? '编辑' : 'Edit'}</MButton>
161
+ <MButton severity="secondary">${zh ? '返回' : 'Back'}</MButton>
162
+ </MSpace>
155
163
  </template>
156
- </MPageToolbar>
157
- <MTag value="${zh ? '正常' : 'Active'}" severity="success" />
158
- <MCard>
159
- <MDivider />
160
- <p style="margin:0;color:var(--m-color-text-muted)">${zh ? '示例资源详情' : 'Example resource details'}</p>
164
+ </MPageHeader>
165
+ <MCard :title="${zh ? '基本信息' : 'Basics'}">
166
+ <p style="margin:0;color:var(--m-color-text-muted)">${zh ? '用定义列表或网格展示属性;相关列表用 MTable + rows。' : 'Use a definition grid; related lists use MTable + rows.'}</p>
161
167
  </MCard>`;
162
- const settingsContent = ` <h1 class="m-generated-title">${title}</h1>
163
- <MTabs :value="'general'" :items="[{ label: '${zh ? '常规' : 'General'}', value: 'general' }]" />
164
- <MForm class="m-generated-form" @submit.prevent="submit">
165
- <MFormItem label="${zh ? '显示名称' : 'Display name'}" name="name">
166
- <MInput v-model="model.name" fluid />
167
- </MFormItem>
168
- <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存设置' : 'Save settings'}</MButton>
169
- </MForm>`;
168
+ const settingsContent = ` <MPageHeader :title="title" :description="${zh ? '按域分组配置偏好。' : 'Grouped preference settings.'}" />
169
+ <MTabs v-model="activeTab" :tabs="settingTabs">
170
+ <template #default="{ activeValue }">
171
+ <MPageSection v-if="activeValue === 'general'" variant="form" :title="${zh ? '常规' : 'General'}">
172
+ <MForm @submit.prevent="submit">
173
+ <MFormItem label="${zh ? '显示名称' : 'Display name'}" name="name" required>
174
+ <MInput v-model="model.name" fluid />
175
+ </MFormItem>
176
+ <MPageSection variant="actions">
177
+ <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
178
+ </MPageSection>
179
+ </MForm>
180
+ </MPageSection>
181
+ </template>
182
+ </MTabs>`;
183
+ const wizardContent = ` <MPageHeader :title="title" :description="${zh ? '每步一个主任务。' : 'One job per step.'}" />
184
+ <MStepper v-model="activeStep" :steps="wizardSteps" linear />
185
+ <MPageSection variant="form">
186
+ <MForm @submit.prevent="submit">
187
+ <MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
188
+ <MInput v-model="model.name" fluid />
189
+ </MFormItem>
190
+ <MPageSection variant="actions">
191
+ <MSpace>
192
+ <MButton severity="secondary" text :disabled="activeStep === 0" @click="activeStep = Math.max(activeStep - 1, 0)">${zh ? '上一步' : 'Back'}</MButton>
193
+ <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '下一步' : 'Next'}</MButton>
194
+ </MSpace>
195
+ </MPageSection>
196
+ </MForm>
197
+ </MPageSection>`;
170
198
  let innerTemplate = '';
171
199
  if (isList) {
172
200
  innerTemplate = `<MConfigProvider :locale="zhCN">
@@ -186,12 +214,20 @@ ${listContent}
186
214
  </MConfigProvider>`;
187
215
  }
188
216
  else if (useLayoutShell) {
189
- const content = isDashboard ? dashboardContent : isDetail ? detailContent : isSettings ? settingsContent : formContent;
190
- const pageContentAttrs = isForm || isSettings ? ' width="narrow"' : isDashboard ? ' density="spacious"' : '';
217
+ const content = isDashboard
218
+ ? dashboardContent
219
+ : isDetail
220
+ ? detailContent
221
+ : isSettings
222
+ ? settingsContent
223
+ : isWizard
224
+ ? wizardContent
225
+ : formContent;
226
+ const pageContentAttrs = isForm || isSettings || isWizard ? ' width="narrow"' : isDashboard ? ' density="spacious"' : '';
191
227
  innerTemplate = `<MConfigProvider :locale="zhCN">
192
228
  <MLayout fill-viewport>
193
229
  <MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
194
- <MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: '${title}' }]" />
230
+ <MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: title }]" />
195
231
  </MLayoutHeader>
196
232
  <MLayoutContent>
197
233
  <MPageContent${pageContentAttrs}>
@@ -236,19 +272,6 @@ ${content}
236
272
  </template>
237
273
  </MEmpty>
238
274
  </main>
239
- </MConfigProvider>`;
240
- }
241
- else if (isWizard) {
242
- innerTemplate = `<MConfigProvider :locale="zhCN">
243
- <main class="m-generated-page">
244
- <MCard>
245
- <MStepper v-model="activeStep" :items="[${zh ? "'基本信息', '确认'" : "'Details', 'Confirm'"}]" />
246
- <MForm label-position="top" @submit.prevent="submit">
247
- <MFormItem label="${zh ? '名称' : 'Name'}" name="name"><MInput v-model="model.name" fluid /></MFormItem>
248
- <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '下一步' : 'Next'}</MButton>
249
- </MForm>
250
- </MCard>
251
- </main>
252
275
  </MConfigProvider>`;
253
276
  }
254
277
  else {
@@ -697,24 +720,48 @@ export function createToolHandlers(catalog = loadCatalog()) {
697
720
  nextStep: (() => {
698
721
  const goldenId = best.pattern.goldenPage?.split('/').pop()?.replace(/\.vue$/i, '') ||
699
722
  listGoldenPages().find((item) => item.patternId === best.pattern.id)?.id ||
700
- best.pattern.id;
701
- return locale === 'en-US'
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。`;
723
+ null;
724
+ if (locale === 'en-US') {
725
+ return goldenId
726
+ ? `Call get_golden_page("${goldenId}") and mirror it. Look up unfamiliar APIs with get_component/get_example, then always run validate_usage and validate_page. includeScaffold returns the golden source when available — not a separate aesthetic template.`
727
+ : `No golden page for this pattern: use get_pattern + get_design_rules, look up APIs with get_component/get_example, then always run validate_usage and validate_page.`;
728
+ }
729
+ return goldenId
730
+ ? `调用 get_golden_page("${goldenId}") 并镜像结构。不熟悉的 API 用 get_component/get_example 核对,然后必须跑 validate_usage 与 validate_page。includeScaffold 有黄金样例时返回样例源码,不是另一套审美模板。`
731
+ : `该模式暂无黄金样例:用 get_pattern + get_design_rules,API 用 get_component/get_example 核对,然后必须跑 validate_usage 与 validate_page。`;
704
732
  })(),
705
733
  };
706
734
  if (args.includeScaffold) {
707
- const code = generatedPageCode(best.pattern.id, args.intent, locale);
708
- const componentSource = `${code.script}\n\n${code.template}\n\n${code.style}`;
709
- result.scaffold = {
710
- vue: code,
711
- files: { component: componentSource },
712
- warnings: [
713
- locale === 'en-US'
714
- ? 'Scaffold only: replace sample API state, data, and events with the application implementation.'
715
- : '仅为脚手架:请将示例 API 状态、数据和事件替换为实际业务实现。',
716
- ],
717
- };
735
+ const goldenId = best.pattern.goldenPage?.split('/').pop()?.replace(/\.vue$/i, '') ||
736
+ listGoldenPages().find((item) => item.patternId === best.pattern.id)?.id ||
737
+ null;
738
+ const golden = goldenId ? readGoldenPageSource(goldenId) : null;
739
+ if (golden) {
740
+ result.scaffold = {
741
+ source: 'golden-page',
742
+ goldenPage: golden.record.id,
743
+ files: { component: golden.source },
744
+ warnings: [
745
+ locale === 'en-US'
746
+ ? 'Scaffold is the golden page source. Remap domain copy/data only; never invent props. Then validate_usage + validate_page.'
747
+ : '脚手架即黄金样例源码。只替换域文案与数据,禁止臆造 prop。完成后必须 validate_usage + validate_page。',
748
+ ],
749
+ };
750
+ }
751
+ else {
752
+ const code = generatedPageCode(best.pattern.id, args.intent, locale);
753
+ const componentSource = `${code.script}\n\n${code.template}\n\n${code.style}`;
754
+ result.scaffold = {
755
+ source: 'generated-fallback',
756
+ vue: code,
757
+ files: { component: componentSource },
758
+ warnings: [
759
+ locale === 'en-US'
760
+ ? 'No golden page for this pattern — structural fallback only. Prefer get_pattern + get_component. Must validate_usage before delivery.'
761
+ : '该模式暂无黄金样例,仅为结构占位。优先 get_pattern + get_component。交付前必须 validate_usage。',
762
+ ],
763
+ };
764
+ }
718
765
  }
719
766
  return textResult(result);
720
767
  }
@@ -734,8 +781,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
734
781
  file: payload.record.file,
735
782
  source: payload.source,
736
783
  nextStep: locale === 'en-US'
737
- ? 'Copy this structure and replace business data. Prefer MPage* components; see get_design_rules for page standards.'
738
- : '复制此结构并替换业务数据。优先使用 MPage* 组件;页面标准见 get_design_rules。',
784
+ ? 'Mirror this structure and replace business data. Look up unfamiliar APIs with get_component/get_example, then always run validate_usage and validate_page. See get_design_rules for page standards.'
785
+ : '镜像此结构并替换业务数据。不熟悉的 API get_component/get_example 核对,然后必须跑 validate_usage 与 validate_page。页面标准见 get_design_rules。',
739
786
  });
740
787
  }
741
788
  function listGoldenPageCatalog(args = {}) {
@@ -769,8 +816,9 @@ export function createToolHandlers(catalog = loadCatalog()) {
769
816
  const ranked = pageSnippets
770
817
  .map((snippet) => ({ snippet, score: scorePageSnippet(snippet, args.section) }))
771
818
  .sort((a, b) => b.score - a.score || a.snippet.id.localeCompare(b.snippet.id));
772
- const match = findPageSnippet(args.section) || (ranked[0]?.score ? ranked[0].snippet : undefined);
773
- if (!match || (ranked[0]?.score ?? 0) === 0) {
819
+ const exact = findPageSnippet(args.section);
820
+ const match = exact || (ranked[0]?.score ? ranked[0].snippet : undefined);
821
+ if (!match || (!exact && (ranked[0]?.score ?? 0) === 0)) {
774
822
  return textResult({
775
823
  error: `Page snippet not found: ${args.section}`,
776
824
  availableSnippets: pageSnippets.map((item) => item.id),
@@ -802,7 +850,7 @@ export function createToolHandlers(catalog = loadCatalog()) {
802
850
  template: match.template,
803
851
  };
804
852
  }
805
- if (!findPageSnippet(args.section) && ranked.length > 1 && ranked[0]?.score) {
853
+ if (!exact && ranked.length > 1 && ranked[0]?.score) {
806
854
  payload.matchedBy = 'query';
807
855
  payload.alternatives = ranked
808
856
  .slice(1, 4)
@@ -950,6 +998,61 @@ export function createToolHandlers(catalog = loadCatalog()) {
950
998
  });
951
999
  }
952
1000
  }
1001
+ if (/<MTable\b[^>]*(?::data|v-bind:data)\s*=/i.test(code) || /<MTable\b[^>]*\sdata\s*=/i.test(code)) {
1002
+ suggestions.push({
1003
+ standardId: 'feedback',
1004
+ type: 'table-data-prop',
1005
+ message: locale === 'en-US'
1006
+ ? 'Contract: MTable row data uses the rows prop (not data). See golden list-page / get_component Table.'
1007
+ : '契约:MTable 行数据使用 rows,不要用 data。见黄金样例 list-page / get_component Table。',
1008
+ });
1009
+ }
1010
+ if (/<MMessage\b[^>]*\bseverity\b/i.test(code)) {
1011
+ suggestions.push({
1012
+ standardId: 'feedback',
1013
+ type: 'mmessage-as-alert',
1014
+ message: locale === 'en-US'
1015
+ ? 'Contract: <MMessage> is the message service host, not an inline alert. Use field errorMessage or a token-styled role="alert".'
1016
+ : '契约:<MMessage> 是 message 服务宿主,不是内嵌 Alert。表单常驻错误用字段 errorMessage 或 token 样式的 role="alert"。',
1017
+ });
1018
+ }
1019
+ if (/toast\.(?:success|info|warn|warning|error)\s*\(\s*['"`][^'"`]+['"`]\s*\)/i.test(code)) {
1020
+ suggestions.push({
1021
+ standardId: 'feedback',
1022
+ type: 'toast-one-liner',
1023
+ message: locale === 'en-US'
1024
+ ? 'Contract: one-line feedback should use message.*; toast is for summary + detail or async feel.'
1025
+ : '契约:单行操作回执用 message.*;toast 仅用于 summary + detail 或异步通知感。',
1026
+ });
1027
+ }
1028
+ if (/#cell-status[\s\S]{0,500}<MTag\b/i.test(code) &&
1029
+ !/#cell-status[\s\S]{0,500}<MStatus\b/i.test(code)) {
1030
+ suggestions.push({
1031
+ standardId: 'feedback',
1032
+ type: 'status-cell-tag',
1033
+ message: locale === 'en-US'
1034
+ ? 'Prefer MStatus for business status in #cell-status; use MTag for categories / closable chips (see list-status-dot / detail-page).'
1035
+ : '行内业务状态优先 MStatus(#cell-status);分类或可关闭标签再用 MTag(见 list-status-dot / detail-page)。',
1036
+ });
1037
+ }
1038
+ if (/<MTabs\b[^>]*(?::items|v-bind:items)\s*=/i.test(code) || /<MTabs\b[^>]*\sitems\s*=/i.test(code)) {
1039
+ suggestions.push({
1040
+ standardId: 'feedback',
1041
+ type: 'tabs-items-prop',
1042
+ message: locale === 'en-US'
1043
+ ? 'Contract: MTabs uses the tabs prop with v-model (not items / value). See settings-page golden.'
1044
+ : '契约:MTabs 使用 v-model + tabs(不是 items / value)。见黄金样例 settings-page。',
1045
+ });
1046
+ }
1047
+ if (/<MStepper\b[^>]*(?::items|v-bind:items)\s*=/i.test(code) || /<MStepper\b[^>]*\sitems\s*=/i.test(code)) {
1048
+ suggestions.push({
1049
+ standardId: 'feedback',
1050
+ type: 'stepper-items-prop',
1051
+ message: locale === 'en-US'
1052
+ ? 'Contract: MStepper uses the steps prop with v-model (not items). See wizard-form golden.'
1053
+ : '契约:MStepper 使用 v-model + steps(不是 items)。见黄金样例 wizard-form。',
1054
+ });
1055
+ }
953
1056
  return textResult({
954
1057
  ok: true,
955
1058
  advisory: true,
@@ -994,7 +1097,7 @@ export function createToolHandlers(catalog = loadCatalog()) {
994
1097
  spacing: '--m-space-*',
995
1098
  radius: '--m-radius-sm/md/lg',
996
1099
  typography: '--m-font-size-xs/sm/md/lg',
997
- motion: '--m-motion-fast/normal',
1100
+ motion: '--m-motion-fast/normal/enter/exit/distance/ease; intensity via useMotion (data-m-motion); enter/exit via motion.transitions / transition prop (fade, scale-fade, zoom, slide-*, dialog, drawer, …)',
998
1101
  },
999
1102
  composition: designRules.composition,
1000
1103
  actions: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@morya-ui/mcp",
3
3
  "type": "module",
4
- "version": "0.2.9",
4
+ "version": "0.3.0",
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",