@morya-ui/mcp 0.3.2 → 0.3.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/dist/tools.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { findComponent, findGuide, loadCatalog, normalizeName, resolveLocale, textResult, toKebab, } from './catalog.js';
2
2
  import { componentDecisions, findDecision, scoreDecision } from './decisions.js';
3
- import { listGoldenPages, readGoldenPageSource } from './golden-pages.js';
3
+ import { listGoldenPages, readGoldenPageSource, resolveGoldenPageId } 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
6
  import { countCatalogResources, countCatalogResourceTemplates } from './resources.js';
7
+ import { findStylePreset, resolveStyleDirection, stylePresets, styleResolution, } from './style-presets.js';
7
8
  function inspectButtonIconOnlyUsage(code, issues) {
8
9
  const pairedTagRe = /<MButton\b([^>]*)>([\s\S]*?)<\/MButton>/gi;
9
10
  let match = pairedTagRe.exec(code);
@@ -47,6 +48,25 @@ function pickLocale(record, locale) {
47
48
  function vueName(name) {
48
49
  return `M${name}`;
49
50
  }
51
+ function mapDecisionOption(option, locale) {
52
+ const zh = locale !== 'en-US';
53
+ const recipe = {
54
+ props: zh ? option.recipe.props : option.recipe.propsEn,
55
+ ...(option.recipe.slots?.length
56
+ ? { slots: zh ? option.recipe.slots : option.recipe.slotsEn }
57
+ : {}),
58
+ ...(option.recipe.events?.length
59
+ ? { events: zh ? option.recipe.events : option.recipe.eventsEn }
60
+ : {}),
61
+ };
62
+ return {
63
+ component: option.component,
64
+ when: zh ? option.when : option.whenEn,
65
+ avoidWhen: zh ? option.avoidWhen : option.avoidWhenEn,
66
+ recipe,
67
+ antiPatterns: zh ? option.antiPatterns : option.antiPatternsEn,
68
+ };
69
+ }
50
70
  function generatedPageCode(patternId, intent, locale) {
51
71
  const zh = locale === 'zh-CN';
52
72
  const isList = patternId === 'admin-list';
@@ -73,149 +93,149 @@ function generatedPageCode(patternId, intent, locale) {
73
93
  const wizardImports = isWizard ? ', MStepper, MSpace' : '';
74
94
  const settingsImports = isSettings ? ', MTabs' : '';
75
95
  const statusImports = isList || isDetail || isDashboard ? ', MStatus' : '';
76
- const script = `<script setup lang="ts">
77
- import { ref } from 'vue'
78
- import { MButton, MCard, MConfigProvider, MInput, MTag, zhCN${layoutImports}${pageImports}${listImports}${formImports}${dashboardImports}${detailImports}${emptyImports}${wizardImports}${settingsImports}${statusImports} } from 'morya-ui'
79
-
80
- const loading = ref(false)
81
- const error = ref('')
82
- const title = ${JSON.stringify(title)}
83
- ${isList ? `const keyword = ref('')
84
- const rows = ref<Record<string, unknown>[]>([])
85
- const columns = [{ key: 'name', label: '${zh ? '名称' : 'Name'}' }, { key: 'status', label: '${zh ? '状态' : 'Status'}' }]
86
- ` : ''}${isForm || isAuth || isWizard ? `const model = ref({ name: '' })
87
- ` : ''}${isDashboard ? `const metrics = ref([
88
- { label: '${zh ? '总用户' : 'Users'}', value: '0' },
89
- { label: '${zh ? '今日活跃' : 'Active today'}', value: '0' },
90
- ])
91
- ` : ''}${isSettings ? `const activeTab = ref('general')
92
- const settingTabs = [{ label: '${zh ? '常规' : 'General'}', value: 'general' }]
93
- ` : ''}${isWizard ? `const activeStep = ref(0)
94
- const wizardSteps = [{ label: '${zh ? '基本信息' : 'Details'}' }, { label: '${zh ? '确认' : 'Confirm'}' }]
95
- ` : ''}
96
-
97
- async function submit() {
98
- loading.value = true
99
- error.value = ''
100
- try {
101
- // Replace with the page API request.
102
- } finally {
103
- loading.value = false
104
- }
105
- }
96
+ const script = `<script setup lang="ts">
97
+ import { ref } from 'vue'
98
+ import { MButton, MCard, MConfigProvider, MInput, MTag, zhCN${layoutImports}${pageImports}${listImports}${formImports}${dashboardImports}${detailImports}${emptyImports}${wizardImports}${settingsImports}${statusImports} } from 'morya-ui'
99
+
100
+ const loading = ref(false)
101
+ const error = ref('')
102
+ const title = ${JSON.stringify(title)}
103
+ ${isList ? `const keyword = ref('')
104
+ const rows = ref<Record<string, unknown>[]>([])
105
+ const columns = [{ key: 'name', label: '${zh ? '名称' : 'Name'}' }, { key: 'status', label: '${zh ? '状态' : 'Status'}' }]
106
+ ` : ''}${isForm || isAuth || isWizard ? `const model = ref({ name: '' })
107
+ ` : ''}${isDashboard ? `const metrics = ref([
108
+ { label: '${zh ? '总用户' : 'Users'}', value: '0' },
109
+ { label: '${zh ? '今日活跃' : 'Active today'}', value: '0' },
110
+ ])
111
+ ` : ''}${isSettings ? `const activeTab = ref('general')
112
+ const settingTabs = [{ label: '${zh ? '常规' : 'General'}', value: 'general' }]
113
+ ` : ''}${isWizard ? `const activeStep = ref(0)
114
+ const wizardSteps = [{ label: '${zh ? '基本信息' : 'Details'}' }, { label: '${zh ? '确认' : 'Confirm'}' }]
115
+ ` : ''}
116
+
117
+ async function submit() {
118
+ loading.value = true
119
+ error.value = ''
120
+ try {
121
+ // Replace with the page API request.
122
+ } finally {
123
+ loading.value = false
124
+ }
125
+ }
106
126
  </script>`;
107
- const listContent = ` <MPageHeader :title="title">
108
- <template #actions>
109
- <MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
110
- </template>
111
- </MPageHeader>
112
- <MPageFilters variant="filled" :aria-label="${zh ? '筛选' : 'Filters'}">
113
- <MSpace wrap>
114
- <MInput v-model="keyword" placeholder="${zh ? '搜索关键词' : 'Search keyword'}" clearable style="width: 14rem" />
115
- <MButton severity="secondary">${zh ? '查询' : 'Search'}</MButton>
116
- <MButton severity="secondary">${zh ? '重置' : 'Reset'}</MButton>
117
- </MSpace>
118
- </MPageFilters>
119
- <MTable :columns="columns" :rows="rows" :loading="loading" paginator :rows-per-page="10" striped bordered row-key="id">
120
- <template #cell-status="{ value }">
121
- <MStatus :label="String(value ?? '')" :severity="value === 'active' ? 'success' : 'secondary'" />
122
- </template>
123
- <template #empty>
124
- <MEmpty
125
- :title="${zh ? '暂无数据' : 'No data yet'}"
126
- :description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
127
- >
128
- <template #extra>
129
- <MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
130
- </template>
131
- </MEmpty>
132
- </template>
127
+ const listContent = ` <MPageHeader :title="title">
128
+ <template #actions>
129
+ <MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
130
+ </template>
131
+ </MPageHeader>
132
+ <MPageFilters variant="filled" :aria-label="${zh ? '筛选' : 'Filters'}">
133
+ <MSpace wrap>
134
+ <MInput v-model="keyword" placeholder="${zh ? '搜索关键词' : 'Search keyword'}" clearable style="width: 14rem" />
135
+ <MButton severity="secondary">${zh ? '查询' : 'Search'}</MButton>
136
+ <MButton severity="secondary">${zh ? '重置' : 'Reset'}</MButton>
137
+ </MSpace>
138
+ </MPageFilters>
139
+ <MTable :columns="columns" :rows="rows" :loading="loading" fill paginator :rows-per-page="10" striped bordered row-key="id">
140
+ <template #cell-status="{ value }">
141
+ <MStatus :label="String(value ?? '')" :severity="value === 'active' ? 'success' : 'secondary'" />
142
+ </template>
143
+ <template #empty>
144
+ <MEmpty
145
+ :title="${zh ? '暂无数据' : 'No data yet'}"
146
+ :description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
147
+ >
148
+ <template #extra>
149
+ <MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
150
+ </template>
151
+ </MEmpty>
152
+ </template>
133
153
  </MTable>`;
134
- const formContent = ` <MPageHeader :title="title" :description="${zh ? '填写表单并保存。' : 'Fill in the form and save.'}" />
135
- <MPageSection variant="form">
136
- <MForm @submit.prevent="submit">
137
- <MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
138
- <MInput v-model="model.name" fluid />
139
- </MFormItem>
140
- <MPageSection variant="actions">
141
- <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
142
- <MButton severity="secondary">${zh ? '取消' : 'Cancel'}</MButton>
143
- </MPageSection>
144
- </MForm>
154
+ const formContent = ` <MPageHeader :title="title" :description="${zh ? '填写表单并保存。' : 'Fill in the form and save.'}" />
155
+ <MPageSection variant="form">
156
+ <MForm @submit.prevent="submit">
157
+ <MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
158
+ <MInput v-model="model.name" fluid />
159
+ </MFormItem>
160
+ <MPageSection variant="actions">
161
+ <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
162
+ <MButton severity="secondary">${zh ? '取消' : 'Cancel'}</MButton>
163
+ </MPageSection>
164
+ </MForm>
145
165
  </MPageSection>`;
146
- const dashboardContent = ` <MPageHeader :title="title" />
147
- <MGrid :cols="2" :x-gap="16" :y-gap="16" responsive="screen">
148
- <MGridItem v-for="metric in metrics" :key="metric.label" :span="1">
149
- <MPageStat :label="metric.label" :value="metric.value" icon="activity" />
150
- </MGridItem>
151
- </MGrid>
152
- <MCard shadow="always" :title="${zh ? '趋势概览' : 'Trend overview'}">
153
- <MSkeleton v-if="loading" height="8rem" />
154
- <MEmpty
155
- v-else
156
- :title="${zh ? '暂无图表数据' : 'No chart data yet'}"
157
- :description="${zh ? '接入图表或业务组件。' : 'Connect charts or business widgets here.'}"
158
- aria-label="${zh ? '图表占位' : 'Chart placeholder'}"
159
- />
166
+ const dashboardContent = ` <MPageHeader :title="title" />
167
+ <MGrid :cols="2" :x-gap="16" :y-gap="16" responsive="screen">
168
+ <MGridItem v-for="metric in metrics" :key="metric.label" :span="1">
169
+ <MPageStat :label="metric.label" :value="metric.value" icon="activity" />
170
+ </MGridItem>
171
+ </MGrid>
172
+ <MCard shadow="always" :title="${zh ? '趋势概览' : 'Trend overview'}">
173
+ <MSkeleton v-if="loading" height="8rem" />
174
+ <MEmpty
175
+ v-else
176
+ :title="${zh ? '暂无图表数据' : 'No chart data yet'}"
177
+ :description="${zh ? '接入图表或业务组件。' : 'Connect charts or business widgets here.'}"
178
+ aria-label="${zh ? '图表占位' : 'Chart placeholder'}"
179
+ />
160
180
  </MCard>`;
161
- const detailContent = ` <MPageHeader :title="title" :description="${zh ? '查看资源摘要与属性。' : 'Review summary and properties.'}">
162
- <template #actions>
163
- <MSpace>
164
- <MStatus :label="${zh ? '启用' : 'Active'}" severity="success" />
165
- <MButton severity="primary">${zh ? '编辑' : 'Edit'}</MButton>
166
- <MButton severity="secondary">${zh ? '返回' : 'Back'}</MButton>
167
- </MSpace>
168
- </template>
169
- </MPageHeader>
170
- <MCard :title="${zh ? '基本信息' : 'Basics'}">
171
- <p style="margin:0;color:var(--m-color-text-muted)">${zh ? '用定义列表或网格展示属性;相关列表用 MTable + rows。' : 'Use a definition grid; related lists use MTable + rows.'}</p>
181
+ const detailContent = ` <MPageHeader :title="title" :description="${zh ? '查看资源摘要与属性。' : 'Review summary and properties.'}">
182
+ <template #actions>
183
+ <MSpace>
184
+ <MStatus :label="${zh ? '启用' : 'Active'}" severity="success" />
185
+ <MButton severity="primary">${zh ? '编辑' : 'Edit'}</MButton>
186
+ <MButton severity="secondary">${zh ? '返回' : 'Back'}</MButton>
187
+ </MSpace>
188
+ </template>
189
+ </MPageHeader>
190
+ <MCard :title="${zh ? '基本信息' : 'Basics'}">
191
+ <p style="margin:0;color:var(--m-color-text-muted)">${zh ? '用定义列表或网格展示属性;相关列表用 MTable + rows。' : 'Use a definition grid; related lists use MTable + rows.'}</p>
172
192
  </MCard>`;
173
- const settingsContent = ` <MPageHeader :title="title" :description="${zh ? '按域分组配置偏好。' : 'Grouped preference settings.'}" />
174
- <MTabs v-model="activeTab" :tabs="settingTabs">
175
- <template #default="{ activeValue }">
176
- <MPageSection v-if="activeValue === 'general'" variant="form" :title="${zh ? '常规' : 'General'}">
177
- <MForm @submit.prevent="submit">
178
- <MFormItem label="${zh ? '显示名称' : 'Display name'}" name="name" required>
179
- <MInput v-model="model.name" fluid />
180
- </MFormItem>
181
- <MPageSection variant="actions">
182
- <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
183
- </MPageSection>
184
- </MForm>
185
- </MPageSection>
186
- </template>
193
+ const settingsContent = ` <MPageHeader :title="title" :description="${zh ? '按域分组配置偏好。' : 'Grouped preference settings.'}" />
194
+ <MTabs v-model="activeTab" :tabs="settingTabs">
195
+ <template #default="{ activeValue }">
196
+ <MPageSection v-if="activeValue === 'general'" variant="form" :title="${zh ? '常规' : 'General'}">
197
+ <MForm @submit.prevent="submit">
198
+ <MFormItem label="${zh ? '显示名称' : 'Display name'}" name="name" required>
199
+ <MInput v-model="model.name" fluid />
200
+ </MFormItem>
201
+ <MPageSection variant="actions">
202
+ <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
203
+ </MPageSection>
204
+ </MForm>
205
+ </MPageSection>
206
+ </template>
187
207
  </MTabs>`;
188
- const wizardContent = ` <MPageHeader :title="title" :description="${zh ? '每步一个主任务。' : 'One job per step.'}" />
189
- <MStepper v-model="activeStep" :steps="wizardSteps" linear />
190
- <MPageSection variant="form">
191
- <MForm @submit.prevent="submit">
192
- <MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
193
- <MInput v-model="model.name" fluid />
194
- </MFormItem>
195
- <MPageSection variant="actions">
196
- <MSpace>
197
- <MButton severity="secondary" text :disabled="activeStep === 0" @click="activeStep = Math.max(activeStep - 1, 0)">${zh ? '上一步' : 'Back'}</MButton>
198
- <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '下一步' : 'Next'}</MButton>
199
- </MSpace>
200
- </MPageSection>
201
- </MForm>
208
+ const wizardContent = ` <MPageHeader :title="title" :description="${zh ? '每步一个主任务。' : 'One job per step.'}" />
209
+ <MStepper v-model="activeStep" :steps="wizardSteps" linear />
210
+ <MPageSection variant="form">
211
+ <MForm @submit.prevent="submit">
212
+ <MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
213
+ <MInput v-model="model.name" fluid />
214
+ </MFormItem>
215
+ <MPageSection variant="actions">
216
+ <MSpace>
217
+ <MButton severity="secondary" text :disabled="activeStep === 0" @click="activeStep = Math.max(activeStep - 1, 0)">${zh ? '上一步' : 'Back'}</MButton>
218
+ <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '下一步' : 'Next'}</MButton>
219
+ </MSpace>
220
+ </MPageSection>
221
+ </MForm>
202
222
  </MPageSection>`;
203
223
  let innerTemplate = '';
204
224
  if (isList) {
205
- innerTemplate = `<MConfigProvider :locale="zhCN">
206
- <MLayout has-sider fill-viewport>
207
- <MLayoutSider bordered />
208
- <MLayout>
209
- <MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
210
- <MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: '${title}' }]" />
211
- </MLayoutHeader>
212
- <MLayoutContent>
213
- <MPageContent>
214
- ${listContent}
215
- </MPageContent>
216
- </MLayoutContent>
217
- </MLayout>
218
- </MLayout>
225
+ innerTemplate = `<MConfigProvider :locale="zhCN">
226
+ <MLayout has-sider fill-viewport>
227
+ <MLayoutSider bordered />
228
+ <MLayout>
229
+ <MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
230
+ <MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: '${title}' }]" />
231
+ </MLayoutHeader>
232
+ <MLayoutContent>
233
+ <MPageContent fill>
234
+ ${listContent}
235
+ </MPageContent>
236
+ </MLayoutContent>
237
+ </MLayout>
238
+ </MLayout>
219
239
  </MConfigProvider>`;
220
240
  }
221
241
  else if (useLayoutShell) {
@@ -229,76 +249,76 @@ ${listContent}
229
249
  ? wizardContent
230
250
  : formContent;
231
251
  const pageContentAttrs = isForm || isSettings || isWizard ? ' width="narrow"' : isDashboard ? ' density="spacious"' : '';
232
- innerTemplate = `<MConfigProvider :locale="zhCN">
233
- <MLayout fill-viewport>
234
- <MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
235
- <MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: title }]" />
236
- </MLayoutHeader>
237
- <MLayoutContent>
238
- <MPageContent${pageContentAttrs}>
239
- ${content}
240
- </MPageContent>
241
- </MLayoutContent>
242
- </MLayout>
252
+ innerTemplate = `<MConfigProvider :locale="zhCN">
253
+ <MLayout fill-viewport>
254
+ <MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
255
+ <MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: title }]" />
256
+ </MLayoutHeader>
257
+ <MLayoutContent>
258
+ <MPageContent${pageContentAttrs}>
259
+ ${content}
260
+ </MPageContent>
261
+ </MLayoutContent>
262
+ </MLayout>
243
263
  </MConfigProvider>`;
244
264
  }
245
265
  else if (isAuth) {
246
- innerTemplate = `<MConfigProvider :locale="zhCN">
247
- <main class="m-generated-page m-generated-auth">
248
- <MCard>
249
- <MForm label-position="top" @submit.prevent="submit">
250
- <MFormItem label="${zh ? '邮箱' : 'Email'}" name="email">
251
- <MInput type="email" fluid />
252
- </MFormItem>
253
- <MFormItem label="${zh ? '密码' : 'Password'}" name="password">
254
- <MInput type="password" fluid />
255
- </MFormItem>
256
- <MButton native-type="submit" severity="primary" :loading="loading" fluid>${zh ? '登录' : 'Sign in'}</MButton>
257
- </MForm>
258
- </MCard>
259
- </main>
266
+ innerTemplate = `<MConfigProvider :locale="zhCN">
267
+ <main class="m-generated-page m-generated-auth">
268
+ <MCard>
269
+ <MForm label-position="top" @submit.prevent="submit">
270
+ <MFormItem label="${zh ? '邮箱' : 'Email'}" name="email">
271
+ <MInput type="email" fluid />
272
+ </MFormItem>
273
+ <MFormItem label="${zh ? '密码' : 'Password'}" name="password">
274
+ <MInput type="password" fluid />
275
+ </MFormItem>
276
+ <MButton native-type="submit" severity="primary" :loading="loading" fluid>${zh ? '登录' : 'Sign in'}</MButton>
277
+ </MForm>
278
+ </MCard>
279
+ </main>
260
280
  </MConfigProvider>`;
261
281
  }
262
282
  else if (isEmpty) {
263
- innerTemplate = `<MConfigProvider :locale="zhCN">
264
- <main class="m-generated-page">
265
- <MPageContent>
266
- <MPageHeader :title="title">
267
- <template #actions>
268
- <MButton severity="primary" @click="submit">${zh ? '新建' : 'Create'}</MButton>
269
- </template>
270
- </MPageHeader>
271
- <MEmpty
272
- :title="${zh ? '暂无内容' : 'Nothing here yet'}"
273
- :description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
274
- icon="database"
275
- >
276
- <template #extra>
277
- <MButton severity="primary" @click="submit">${zh ? '创建' : 'Create'}</MButton>
278
- </template>
279
- </MEmpty>
280
- </MPageContent>
281
- </main>
283
+ innerTemplate = `<MConfigProvider :locale="zhCN">
284
+ <main class="m-generated-page">
285
+ <MPageContent>
286
+ <MPageHeader :title="title">
287
+ <template #actions>
288
+ <MButton severity="primary" @click="submit">${zh ? '新建' : 'Create'}</MButton>
289
+ </template>
290
+ </MPageHeader>
291
+ <MEmpty
292
+ :title="${zh ? '暂无内容' : 'Nothing here yet'}"
293
+ :description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
294
+ icon="database"
295
+ >
296
+ <template #extra>
297
+ <MButton severity="primary" @click="submit">${zh ? '创建' : 'Create'}</MButton>
298
+ </template>
299
+ </MEmpty>
300
+ </MPageContent>
301
+ </main>
282
302
  </MConfigProvider>`;
283
303
  }
284
304
  else {
285
- innerTemplate = `<MConfigProvider :locale="zhCN">
286
- <main class="m-generated-page">
287
- <MCard>
288
- <p class="m-generated-muted">${zh ? '将此区域替换为页面内容。' : 'Replace this area with page content.'}</p>
289
- <MTag value="${zh ? '示例' : 'Example'}" severity="info" />
290
- </MCard>
291
- </main>
305
+ innerTemplate = `<MConfigProvider :locale="zhCN">
306
+ <main class="m-generated-page">
307
+ <MCard>
308
+ <p class="m-generated-muted">${zh ? '将此区域替换为页面内容。' : 'Replace this area with page content.'}</p>
309
+ <MTag value="${zh ? '示例' : 'Example'}" severity="info" />
310
+ </MCard>
311
+ </main>
292
312
  </MConfigProvider>`;
293
313
  }
294
- const template = `<template>
295
- ${innerTemplate}
296
- <p v-if="error" role="alert" class="m-generated-error">${'{{ error }}'}</p>
314
+ const template = `<template>
315
+ ${innerTemplate}
316
+ <p v-if="error" role="alert" class="m-generated-error">${'{{ error }}'}</p>
297
317
  </template>`;
298
- const style = `<style scoped>
299
- .m-generated-auth { display: grid; place-items: center; padding: var(--m-space-8); max-width: 24rem; margin: 0 auto; min-height: 100vh; }
300
- .m-generated-empty { display: grid; gap: var(--m-space-2); justify-items: center; padding: var(--m-space-8); text-align: center; }
301
- .m-generated-error { color: var(--m-color-danger); padding: 0 var(--m-space-6); }
318
+ const style = `<style scoped>
319
+ .m-generated-auth { display: grid; place-items: center; padding: var(--m-space-8); max-width: 24rem; margin: 0 auto; min-height: 100vh; }
320
+ .m-generated-empty { display: grid; gap: var(--m-space-2); justify-items: center; padding: var(--m-space-8); text-align: center; }
321
+ .m-generated-error { color: var(--m-color-danger); padding: 0 var(--m-space-6); }
302
322
  </style>`;
303
323
  return { script, template, style };
304
324
  }
@@ -710,13 +730,47 @@ export function createToolHandlers(catalog = loadCatalog()) {
710
730
  });
711
731
  }
712
732
  const locale = resolveLocale(args.mode);
733
+ const styleDirection = resolveStyleDirection({ intent: args.intent, style: args.style });
734
+ const stylePayload = {
735
+ resolution: styleDirection.resolution,
736
+ confidence: styleDirection.confidence,
737
+ preset: styleDirection.preset
738
+ ? {
739
+ id: styleDirection.preset.id,
740
+ title: locale === 'en-US' ? styleDirection.preset.titleEn : styleDirection.preset.title,
741
+ summary: locale === 'en-US' ? styleDirection.preset.summaryEn : styleDirection.preset.summary,
742
+ apply: locale === 'en-US' ? styleDirection.preset.applyEn : styleDirection.preset.apply,
743
+ avoid: locale === 'en-US' ? styleDirection.preset.avoidEn : styleDirection.preset.avoid,
744
+ }
745
+ : null,
746
+ candidates: styleDirection.candidates.map((item) => ({
747
+ id: item.id,
748
+ title: locale === 'en-US' ? item.titleEn : item.title,
749
+ score: item.score,
750
+ })),
751
+ guidance: locale === 'en-US' ? styleDirection.guidanceEn : styleDirection.guidanceZh,
752
+ priority: locale === 'en-US' ? styleResolution.priorityEn : styleResolution.priorityZh,
753
+ note: locale === 'en-US'
754
+ ? 'If the user supplied a visual reference, follow that first (map to --m-* + M*). Golden pages lock structure, not the only aesthetic.'
755
+ : '若用户提供了参考样式,优先跟参考(映射到 --m-* + M*)。黄金样例锁结构,不是唯一审美。',
756
+ };
757
+ const resolvedGoldenId = resolveGoldenPageId({
758
+ patternId: best.pattern.id,
759
+ canonicalGoldenId: best.pattern.goldenPage,
760
+ styleId: styleDirection.preset?.id || args.style || null,
761
+ });
762
+ const resolvedGoldenPath = resolvedGoldenId
763
+ ? `docs/golden-pages/${resolvedGoldenId}.vue`
764
+ : best.pattern.goldenPage;
713
765
  const result = {
714
766
  intent: args.intent,
715
767
  pageType: args.pageType,
768
+ style: args.style,
716
769
  matchedPattern: best.pattern.id,
717
770
  title: locale === 'en-US' ? best.pattern.titleEn : best.pattern.title,
718
771
  confidence: best.score,
719
- goldenPage: best.pattern.goldenPage,
772
+ goldenPage: resolvedGoldenPath,
773
+ styleDirection: stylePayload,
720
774
  components: best.pattern.components,
721
775
  structure: best.pattern.structure,
722
776
  layout: best.pattern.layout,
@@ -725,23 +779,26 @@ export function createToolHandlers(catalog = loadCatalog()) {
725
779
  avoid: best.pattern.avoid,
726
780
  alternatives: ranked.slice(1, 3).filter((item) => item.score > 0).map((item) => ({ id: item.pattern.id, score: item.score })),
727
781
  nextStep: (() => {
728
- const goldenId = best.pattern.goldenPage?.split('/').pop()?.replace(/\.vue$/i, '') ||
729
- listGoldenPages().find((item) => item.patternId === best.pattern.id)?.id ||
730
- null;
782
+ const goldenId = resolvedGoldenId;
783
+ const styleHint = locale === 'en-US'
784
+ ? styleDirection.resolution === 'offer'
785
+ ? ' Resolve style first (reference → preset → ask among quiet/soft/dense/rail/studio/ink).'
786
+ : ` Apply styleDirection preset "${styleDirection.preset?.id || 'n/a'}".`
787
+ : styleDirection.resolution === 'offer'
788
+ ? ' 先定风格(参考 → 预设 → 请用户从 quiet/soft/dense/rail/studio/ink 选)。'
789
+ : ` 套用 styleDirection 预设「${styleDirection.preset?.id || 'n/a'}」。`;
731
790
  if (locale === 'en-US') {
732
791
  return goldenId
733
- ? `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.`
734
- : `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.`;
792
+ ? `Call get_golden_page("${goldenId}") and mirror structure.${styleHint} Look up unfamiliar APIs with get_component/get_example, then always run validate_usage and validate_page.`
793
+ : `No golden page for this pattern: use get_pattern + get_design_rules.${styleHint} Look up APIs with get_component/get_example, then always run validate_usage and validate_page.`;
735
794
  }
736
795
  return goldenId
737
- ? `调用 get_golden_page("${goldenId}") 并镜像结构。不熟悉的 API 用 get_component/get_example 核对,然后必须跑 validate_usage 与 validate_page。includeScaffold 有黄金样例时返回样例源码,不是另一套审美模板。`
738
- : `该模式暂无黄金样例:用 get_pattern + get_design_rules,API 用 get_component/get_example 核对,然后必须跑 validate_usage 与 validate_page。`;
796
+ ? `调用 get_golden_page("${goldenId}") 并镜像结构。${styleHint} 不熟悉的 API 用 get_component/get_example 核对,然后必须跑 validate_usage 与 validate_page。`
797
+ : `该模式暂无黄金样例:用 get_pattern + get_design_rules。${styleHint} API 用 get_component/get_example 核对,然后必须跑 validate_usage 与 validate_page。`;
739
798
  })(),
740
799
  };
741
800
  if (args.includeScaffold) {
742
- const goldenId = best.pattern.goldenPage?.split('/').pop()?.replace(/\.vue$/i, '') ||
743
- listGoldenPages().find((item) => item.patternId === best.pattern.id)?.id ||
744
- null;
801
+ const goldenId = resolvedGoldenId;
745
802
  const golden = goldenId ? readGoldenPageSource(goldenId) : null;
746
803
  if (golden) {
747
804
  result.scaffold = {
@@ -786,6 +843,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
786
843
  patternId: payload.record.patternId,
787
844
  title: locale === 'en-US' ? payload.record.titleEn : payload.record.title,
788
845
  file: payload.record.file,
846
+ style: payload.record.style ?? null,
847
+ variantOf: payload.record.variantOf ?? null,
789
848
  source: payload.source,
790
849
  nextStep: locale === 'en-US'
791
850
  ? '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.'
@@ -800,6 +859,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
800
859
  patternId: item.patternId,
801
860
  title: locale === 'en-US' ? item.titleEn : item.title,
802
861
  file: item.file,
862
+ style: item.style ?? null,
863
+ variantOf: item.variantOf ?? null,
803
864
  })),
804
865
  });
805
866
  }
@@ -1014,6 +1075,19 @@ export function createToolHandlers(catalog = loadCatalog()) {
1014
1075
  : '契约:MTable 行数据使用 rows,不要用 data。见黄金样例 list-page / get_component Table。',
1015
1076
  });
1016
1077
  }
1078
+ if (/fill-viewport|fillViewport/i.test(code)
1079
+ && /<MTable\b[^>]*\bpaginator\b/i.test(code)
1080
+ && /<MPageContent\b/i.test(code)
1081
+ && !(/<MPageContent\b[^>]*\bfill\b/i.test(code)
1082
+ && /<MTable\b[^>]*\bfill\b/i.test(code))) {
1083
+ suggestions.push({
1084
+ standardId: 'page-sections',
1085
+ type: 'list-fill-height',
1086
+ message: locale === 'en-US'
1087
+ ? 'Advisory: for a full-viewport admin list whose main job is one table, consider MPageContent fill + MTable fill so the body scrolls and pagination stays at the page bottom. Skip fill for embedded/short/document-scroll tables (see page-scroll-choice / golden list-page).'
1088
+ : '参考建议:若这是全视口后台主列表且表格是页面主任务,可考虑 MPageContent fill + MTable fill(表体滚动、分页贴底)。嵌入表/短页/整页文档滚动则跳过 fill(见 page-scroll-choice / 黄金样例 list-page)。',
1089
+ });
1090
+ }
1017
1091
  if (/<MMessage\b[^>]*\bseverity\b/i.test(code)) {
1018
1092
  suggestions.push({
1019
1093
  standardId: 'feedback',
@@ -1078,10 +1152,21 @@ export function createToolHandlers(catalog = loadCatalog()) {
1078
1152
  }
1079
1153
  function getDesignRules(args = {}) {
1080
1154
  const locale = resolveLocale(args.mode);
1155
+ const styles = {
1156
+ resolution: locale === 'en-US' ? styleResolution.priorityEn : styleResolution.priorityZh,
1157
+ hardRules: locale === 'en-US' ? styleResolution.hardRulesEn : styleResolution.hardRulesZh,
1158
+ presets: stylePresets.map((preset) => ({
1159
+ id: preset.id,
1160
+ title: locale === 'en-US' ? preset.titleEn : preset.title,
1161
+ summary: locale === 'en-US' ? preset.summaryEn : preset.summary,
1162
+ lanes: preset.lanes,
1163
+ })),
1164
+ };
1081
1165
  if (locale === 'zh-CN') {
1082
1166
  return textResult({
1083
1167
  meta: designRules.meta,
1084
1168
  standards: designRules.standards,
1169
+ styles,
1085
1170
  tokens: designRules.tokens,
1086
1171
  composition: designRules.composition,
1087
1172
  actions: designRules.actions,
@@ -1099,6 +1184,7 @@ export function createToolHandlers(catalog = loadCatalog()) {
1099
1184
  discouraged: item.discouragedEn,
1100
1185
  mcp: item.mcp,
1101
1186
  })),
1187
+ styles,
1102
1188
  tokens: {
1103
1189
  colors: ['--m-color-primary', '--m-color-surface', '--m-color-text', '--m-color-border'],
1104
1190
  spacing: '--m-space-*',
@@ -1140,9 +1226,47 @@ export function createToolHandlers(catalog = loadCatalog()) {
1140
1226
  'Form controls should have a visible label or an equivalent accessible name.',
1141
1227
  'Overlays teleport to body by default; only change appendTo for a clear layout constraint.',
1142
1228
  'Prefer documented component variants over deep CSS overrides.',
1229
+ 'Resolve visual style via reference → preset → prompt cues → offer quiet/soft/dense/rail/studio/ink; golden pages lock structure, not aesthetics.',
1143
1230
  ],
1144
1231
  });
1145
1232
  }
1233
+ function listStylePresets(args = {}) {
1234
+ const locale = resolveLocale(args.mode);
1235
+ return textResult({
1236
+ resolution: locale === 'en-US' ? styleResolution.priorityEn : styleResolution.priorityZh,
1237
+ hardRules: locale === 'en-US' ? styleResolution.hardRulesEn : styleResolution.hardRulesZh,
1238
+ presets: stylePresets.map((preset) => ({
1239
+ id: preset.id,
1240
+ title: locale === 'en-US' ? preset.titleEn : preset.title,
1241
+ summary: locale === 'en-US' ? preset.summaryEn : preset.summary,
1242
+ lanes: preset.lanes,
1243
+ keywords: preset.keywords,
1244
+ listGoldenPage: preset.listGoldenPage ?? null,
1245
+ })),
1246
+ });
1247
+ }
1248
+ function getStylePreset(args) {
1249
+ const locale = resolveLocale(args.mode);
1250
+ const preset = findStylePreset(args.style);
1251
+ if (!preset) {
1252
+ return textResult({
1253
+ error: `Style preset not found: ${args.style}`,
1254
+ available: stylePresets.map((item) => item.id),
1255
+ });
1256
+ }
1257
+ return textResult({
1258
+ id: preset.id,
1259
+ title: locale === 'en-US' ? preset.titleEn : preset.title,
1260
+ summary: locale === 'en-US' ? preset.summaryEn : preset.summary,
1261
+ lanes: preset.lanes,
1262
+ keywords: preset.keywords,
1263
+ listGoldenPage: preset.listGoldenPage ?? null,
1264
+ cues: locale === 'en-US' ? preset.cuesEn : preset.cues,
1265
+ apply: locale === 'en-US' ? preset.applyEn : preset.apply,
1266
+ avoid: locale === 'en-US' ? preset.avoidEn : preset.avoid,
1267
+ resolution: locale === 'en-US' ? styleResolution.priorityEn : styleResolution.priorityZh,
1268
+ });
1269
+ }
1146
1270
  function recommendComponent(args) {
1147
1271
  const locale = resolveLocale(args.mode);
1148
1272
  if (!args.query && !args.decision) {
@@ -1170,11 +1294,7 @@ export function createToolHandlers(catalog = loadCatalog()) {
1170
1294
  title: locale === 'en-US' ? decision.titleEn : decision.title,
1171
1295
  question: locale === 'en-US' ? decision.questionEn : decision.question,
1172
1296
  keywords: decision.keywords,
1173
- options: decision.options.map((option) => ({
1174
- component: option.component,
1175
- when: locale === 'en-US' ? option.whenEn : option.when,
1176
- avoidWhen: locale === 'en-US' ? option.avoidWhenEn : option.avoidWhen,
1177
- })),
1297
+ options: decision.options.map((option) => mapDecisionOption(option, locale)),
1178
1298
  });
1179
1299
  }
1180
1300
  if (!args.query) {
@@ -1194,14 +1314,10 @@ export function createToolHandlers(catalog = loadCatalog()) {
1194
1314
  decision: matched.id,
1195
1315
  title: locale === 'en-US' ? matched.titleEn : matched.title,
1196
1316
  question: locale === 'en-US' ? matched.questionEn : matched.question,
1197
- recommendations: matched.options.map((option) => ({
1198
- component: option.component,
1199
- when: locale === 'en-US' ? option.whenEn : option.when,
1200
- avoidWhen: locale === 'en-US' ? option.avoidWhenEn : option.avoidWhen,
1201
- })),
1317
+ recommendations: matched.options.map((option) => mapDecisionOption(option, locale)),
1202
1318
  nextStep: locale === 'en-US'
1203
- ? 'Use get_component and get_example for the selected component before implementing.'
1204
- : '实现前请用 get_component 和 get_example 核对所选组件 API。',
1319
+ ? 'Apply the recipe props/slots/events, then use get_component and get_example before implementing. Run validate_usage before delivery.'
1320
+ : '先按 recipe 的 props/slots/events 落地,再用 get_component / get_example 核对 API;交付前跑 validate_usage。',
1205
1321
  });
1206
1322
  }
1207
1323
  function getSetup(args) {
@@ -1369,6 +1485,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
1369
1485
  'get_pattern',
1370
1486
  'recommend_page',
1371
1487
  'get_design_rules',
1488
+ 'list_style_presets',
1489
+ 'get_style_preset',
1372
1490
  'recommend_component',
1373
1491
  'list_golden_pages',
1374
1492
  'get_golden_page',
@@ -1392,6 +1510,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
1392
1510
  getPattern,
1393
1511
  recommendPage,
1394
1512
  getDesignRules,
1513
+ listStylePresets,
1514
+ getStylePreset,
1395
1515
  recommendComponent,
1396
1516
  listGoldenPageCatalog,
1397
1517
  getGoldenPage,