@morya-ui/mcp 0.3.3 → 0.3.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/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 { resolveListCraftVariant, resolveStyleDirection, 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);
@@ -64,6 +65,7 @@ function mapDecisionOption(option, locale) {
64
65
  avoidWhen: zh ? option.avoidWhen : option.avoidWhenEn,
65
66
  recipe,
66
67
  antiPatterns: zh ? option.antiPatterns : option.antiPatternsEn,
68
+ ...(option.relatedSnippets?.length ? { relatedSnippets: option.relatedSnippets } : {}),
67
69
  };
68
70
  }
69
71
  function generatedPageCode(patternId, intent, locale) {
@@ -92,149 +94,149 @@ function generatedPageCode(patternId, intent, locale) {
92
94
  const wizardImports = isWizard ? ', MStepper, MSpace' : '';
93
95
  const settingsImports = isSettings ? ', MTabs' : '';
94
96
  const statusImports = isList || isDetail || isDashboard ? ', MStatus' : '';
95
- const script = `<script setup lang="ts">
96
- import { ref } from 'vue'
97
- import { MButton, MCard, MConfigProvider, MInput, MTag, zhCN${layoutImports}${pageImports}${listImports}${formImports}${dashboardImports}${detailImports}${emptyImports}${wizardImports}${settingsImports}${statusImports} } from 'morya-ui'
98
-
99
- const loading = ref(false)
100
- const error = ref('')
101
- const title = ${JSON.stringify(title)}
102
- ${isList ? `const keyword = ref('')
103
- const rows = ref<Record<string, unknown>[]>([])
104
- const columns = [{ key: 'name', label: '${zh ? '名称' : 'Name'}' }, { key: 'status', label: '${zh ? '状态' : 'Status'}' }]
105
- ` : ''}${isForm || isAuth || isWizard ? `const model = ref({ name: '' })
106
- ` : ''}${isDashboard ? `const metrics = ref([
107
- { label: '${zh ? '总用户' : 'Users'}', value: '0' },
108
- { label: '${zh ? '今日活跃' : 'Active today'}', value: '0' },
109
- ])
110
- ` : ''}${isSettings ? `const activeTab = ref('general')
111
- const settingTabs = [{ label: '${zh ? '常规' : 'General'}', value: 'general' }]
112
- ` : ''}${isWizard ? `const activeStep = ref(0)
113
- const wizardSteps = [{ label: '${zh ? '基本信息' : 'Details'}' }, { label: '${zh ? '确认' : 'Confirm'}' }]
114
- ` : ''}
115
-
116
- async function submit() {
117
- loading.value = true
118
- error.value = ''
119
- try {
120
- // Replace with the page API request.
121
- } finally {
122
- loading.value = false
123
- }
124
- }
97
+ const script = `<script setup lang="ts">
98
+ import { ref } from 'vue'
99
+ import { MButton, MCard, MConfigProvider, MInput, MTag, zhCN${layoutImports}${pageImports}${listImports}${formImports}${dashboardImports}${detailImports}${emptyImports}${wizardImports}${settingsImports}${statusImports} } from 'morya-ui'
100
+
101
+ const loading = ref(false)
102
+ const error = ref('')
103
+ const title = ${JSON.stringify(title)}
104
+ ${isList ? `const keyword = ref('')
105
+ const rows = ref<Record<string, unknown>[]>([])
106
+ const columns = [{ key: 'name', label: '${zh ? '名称' : 'Name'}' }, { key: 'status', label: '${zh ? '状态' : 'Status'}' }]
107
+ ` : ''}${isForm || isAuth || isWizard ? `const model = ref({ name: '' })
108
+ ` : ''}${isDashboard ? `const metrics = ref([
109
+ { label: '${zh ? '总用户' : 'Users'}', value: '0' },
110
+ { label: '${zh ? '今日活跃' : 'Active today'}', value: '0' },
111
+ ])
112
+ ` : ''}${isSettings ? `const activeTab = ref('general')
113
+ const settingTabs = [{ label: '${zh ? '常规' : 'General'}', value: 'general' }]
114
+ ` : ''}${isWizard ? `const activeStep = ref(0)
115
+ const wizardSteps = [{ label: '${zh ? '基本信息' : 'Details'}' }, { label: '${zh ? '确认' : 'Confirm'}' }]
116
+ ` : ''}
117
+
118
+ async function submit() {
119
+ loading.value = true
120
+ error.value = ''
121
+ try {
122
+ // Replace with the page API request.
123
+ } finally {
124
+ loading.value = false
125
+ }
126
+ }
125
127
  </script>`;
126
- const listContent = ` <MPageHeader :title="title">
127
- <template #actions>
128
- <MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
129
- </template>
130
- </MPageHeader>
131
- <MPageFilters variant="filled" :aria-label="${zh ? '筛选' : 'Filters'}">
132
- <MSpace wrap>
133
- <MInput v-model="keyword" placeholder="${zh ? '搜索关键词' : 'Search keyword'}" clearable style="width: 14rem" />
134
- <MButton severity="secondary">${zh ? '查询' : 'Search'}</MButton>
135
- <MButton severity="secondary">${zh ? '重置' : 'Reset'}</MButton>
136
- </MSpace>
137
- </MPageFilters>
138
- <MTable :columns="columns" :rows="rows" :loading="loading" paginator :rows-per-page="10" striped bordered row-key="id">
139
- <template #cell-status="{ value }">
140
- <MStatus :label="String(value ?? '')" :severity="value === 'active' ? 'success' : 'secondary'" />
141
- </template>
142
- <template #empty>
143
- <MEmpty
144
- :title="${zh ? '暂无数据' : 'No data yet'}"
145
- :description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
146
- >
147
- <template #extra>
148
- <MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
149
- </template>
150
- </MEmpty>
151
- </template>
128
+ const listContent = ` <MPageHeader :title="title">
129
+ <template #actions>
130
+ <MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
131
+ </template>
132
+ </MPageHeader>
133
+ <MPageFilters variant="filled" :aria-label="${zh ? '筛选' : 'Filters'}">
134
+ <MSpace wrap>
135
+ <MInput v-model="keyword" placeholder="${zh ? '搜索关键词' : 'Search keyword'}" clearable style="width: 14rem" />
136
+ <MButton severity="secondary">${zh ? '查询' : 'Search'}</MButton>
137
+ <MButton severity="secondary">${zh ? '重置' : 'Reset'}</MButton>
138
+ </MSpace>
139
+ </MPageFilters>
140
+ <MTable :columns="columns" :rows="rows" :loading="loading" fill paginator :rows-per-page="10" striped bordered row-key="id">
141
+ <template #cell-status="{ value }">
142
+ <MStatus :label="String(value ?? '')" :severity="value === 'active' ? 'success' : 'secondary'" />
143
+ </template>
144
+ <template #empty>
145
+ <MEmpty
146
+ :title="${zh ? '暂无数据' : 'No data yet'}"
147
+ :description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
148
+ >
149
+ <template #extra>
150
+ <MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
151
+ </template>
152
+ </MEmpty>
153
+ </template>
152
154
  </MTable>`;
153
- const formContent = ` <MPageHeader :title="title" :description="${zh ? '填写表单并保存。' : 'Fill in the form and save.'}" />
154
- <MPageSection variant="form">
155
- <MForm @submit.prevent="submit">
156
- <MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
157
- <MInput v-model="model.name" fluid />
158
- </MFormItem>
159
- <MPageSection variant="actions">
160
- <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
161
- <MButton severity="secondary">${zh ? '取消' : 'Cancel'}</MButton>
162
- </MPageSection>
163
- </MForm>
155
+ const formContent = ` <MPageHeader :title="title" :description="${zh ? '填写表单并保存。' : 'Fill in the form and save.'}" />
156
+ <MPageSection variant="form">
157
+ <MForm @submit.prevent="submit">
158
+ <MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
159
+ <MInput v-model="model.name" fluid />
160
+ </MFormItem>
161
+ <MPageSection variant="actions">
162
+ <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
163
+ <MButton severity="secondary">${zh ? '取消' : 'Cancel'}</MButton>
164
+ </MPageSection>
165
+ </MForm>
164
166
  </MPageSection>`;
165
- const dashboardContent = ` <MPageHeader :title="title" />
166
- <MGrid :cols="2" :x-gap="16" :y-gap="16" responsive="screen">
167
- <MGridItem v-for="metric in metrics" :key="metric.label" :span="1">
168
- <MPageStat :label="metric.label" :value="metric.value" icon="activity" />
169
- </MGridItem>
170
- </MGrid>
171
- <MCard shadow="always" :title="${zh ? '趋势概览' : 'Trend overview'}">
172
- <MSkeleton v-if="loading" height="8rem" />
173
- <MEmpty
174
- v-else
175
- :title="${zh ? '暂无图表数据' : 'No chart data yet'}"
176
- :description="${zh ? '接入图表或业务组件。' : 'Connect charts or business widgets here.'}"
177
- aria-label="${zh ? '图表占位' : 'Chart placeholder'}"
178
- />
167
+ const dashboardContent = ` <MPageHeader :title="title" />
168
+ <MGrid :cols="2" :x-gap="16" :y-gap="16" responsive="screen">
169
+ <MGridItem v-for="metric in metrics" :key="metric.label" :span="1">
170
+ <MPageStat :label="metric.label" :value="metric.value" icon="activity" />
171
+ </MGridItem>
172
+ </MGrid>
173
+ <MCard shadow="always" :title="${zh ? '趋势概览' : 'Trend overview'}">
174
+ <MSkeleton v-if="loading" height="8rem" />
175
+ <MEmpty
176
+ v-else
177
+ :title="${zh ? '暂无图表数据' : 'No chart data yet'}"
178
+ :description="${zh ? '接入图表或业务组件。' : 'Connect charts or business widgets here.'}"
179
+ aria-label="${zh ? '图表占位' : 'Chart placeholder'}"
180
+ />
179
181
  </MCard>`;
180
- const detailContent = ` <MPageHeader :title="title" :description="${zh ? '查看资源摘要与属性。' : 'Review summary and properties.'}">
181
- <template #actions>
182
- <MSpace>
183
- <MStatus :label="${zh ? '启用' : 'Active'}" severity="success" />
184
- <MButton severity="primary">${zh ? '编辑' : 'Edit'}</MButton>
185
- <MButton severity="secondary">${zh ? '返回' : 'Back'}</MButton>
186
- </MSpace>
187
- </template>
188
- </MPageHeader>
189
- <MCard :title="${zh ? '基本信息' : 'Basics'}">
190
- <p style="margin:0;color:var(--m-color-text-muted)">${zh ? '用定义列表或网格展示属性;相关列表用 MTable + rows。' : 'Use a definition grid; related lists use MTable + rows.'}</p>
182
+ const detailContent = ` <MPageHeader :title="title" :description="${zh ? '查看资源摘要与属性。' : 'Review summary and properties.'}">
183
+ <template #actions>
184
+ <MSpace>
185
+ <MStatus :label="${zh ? '启用' : 'Active'}" severity="success" />
186
+ <MButton severity="primary">${zh ? '编辑' : 'Edit'}</MButton>
187
+ <MButton severity="secondary">${zh ? '返回' : 'Back'}</MButton>
188
+ </MSpace>
189
+ </template>
190
+ </MPageHeader>
191
+ <MCard :title="${zh ? '基本信息' : 'Basics'}">
192
+ <p style="margin:0;color:var(--m-color-text-muted)">${zh ? '用定义列表或网格展示属性;相关列表用 MTable + rows。' : 'Use a definition grid; related lists use MTable + rows.'}</p>
191
193
  </MCard>`;
192
- const settingsContent = ` <MPageHeader :title="title" :description="${zh ? '按域分组配置偏好。' : 'Grouped preference settings.'}" />
193
- <MTabs v-model="activeTab" :tabs="settingTabs">
194
- <template #default="{ activeValue }">
195
- <MPageSection v-if="activeValue === 'general'" variant="form" :title="${zh ? '常规' : 'General'}">
196
- <MForm @submit.prevent="submit">
197
- <MFormItem label="${zh ? '显示名称' : 'Display name'}" name="name" required>
198
- <MInput v-model="model.name" fluid />
199
- </MFormItem>
200
- <MPageSection variant="actions">
201
- <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
202
- </MPageSection>
203
- </MForm>
204
- </MPageSection>
205
- </template>
194
+ const settingsContent = ` <MPageHeader :title="title" :description="${zh ? '按域分组配置偏好。' : 'Grouped preference settings.'}" />
195
+ <MTabs v-model="activeTab" :tabs="settingTabs">
196
+ <template #default="{ activeValue }">
197
+ <MPageSection v-if="activeValue === 'general'" variant="form" :title="${zh ? '常规' : 'General'}">
198
+ <MForm @submit.prevent="submit">
199
+ <MFormItem label="${zh ? '显示名称' : 'Display name'}" name="name" required>
200
+ <MInput v-model="model.name" fluid />
201
+ </MFormItem>
202
+ <MPageSection variant="actions">
203
+ <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
204
+ </MPageSection>
205
+ </MForm>
206
+ </MPageSection>
207
+ </template>
206
208
  </MTabs>`;
207
- const wizardContent = ` <MPageHeader :title="title" :description="${zh ? '每步一个主任务。' : 'One job per step.'}" />
208
- <MStepper v-model="activeStep" :steps="wizardSteps" linear />
209
- <MPageSection variant="form">
210
- <MForm @submit.prevent="submit">
211
- <MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
212
- <MInput v-model="model.name" fluid />
213
- </MFormItem>
214
- <MPageSection variant="actions">
215
- <MSpace>
216
- <MButton severity="secondary" text :disabled="activeStep === 0" @click="activeStep = Math.max(activeStep - 1, 0)">${zh ? '上一步' : 'Back'}</MButton>
217
- <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '下一步' : 'Next'}</MButton>
218
- </MSpace>
219
- </MPageSection>
220
- </MForm>
209
+ const wizardContent = ` <MPageHeader :title="title" :description="${zh ? '每步一个主任务。' : 'One job per step.'}" />
210
+ <MStepper v-model="activeStep" :steps="wizardSteps" linear />
211
+ <MPageSection variant="form">
212
+ <MForm @submit.prevent="submit">
213
+ <MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
214
+ <MInput v-model="model.name" fluid />
215
+ </MFormItem>
216
+ <MPageSection variant="actions">
217
+ <MSpace>
218
+ <MButton severity="secondary" text :disabled="activeStep === 0" @click="activeStep = Math.max(activeStep - 1, 0)">${zh ? '上一步' : 'Back'}</MButton>
219
+ <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '下一步' : 'Next'}</MButton>
220
+ </MSpace>
221
+ </MPageSection>
222
+ </MForm>
221
223
  </MPageSection>`;
222
224
  let innerTemplate = '';
223
225
  if (isList) {
224
- innerTemplate = `<MConfigProvider :locale="zhCN">
225
- <MLayout has-sider fill-viewport>
226
- <MLayoutSider bordered />
227
- <MLayout>
228
- <MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
229
- <MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: '${title}' }]" />
230
- </MLayoutHeader>
231
- <MLayoutContent>
232
- <MPageContent>
233
- ${listContent}
234
- </MPageContent>
235
- </MLayoutContent>
236
- </MLayout>
237
- </MLayout>
226
+ innerTemplate = `<MConfigProvider :locale="zhCN">
227
+ <MLayout has-sider fill-viewport>
228
+ <MLayoutSider bordered />
229
+ <MLayout>
230
+ <MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
231
+ <MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: '${title}' }]" />
232
+ </MLayoutHeader>
233
+ <MLayoutContent>
234
+ <MPageContent fill>
235
+ ${listContent}
236
+ </MPageContent>
237
+ </MLayoutContent>
238
+ </MLayout>
239
+ </MLayout>
238
240
  </MConfigProvider>`;
239
241
  }
240
242
  else if (useLayoutShell) {
@@ -248,76 +250,76 @@ ${listContent}
248
250
  ? wizardContent
249
251
  : formContent;
250
252
  const pageContentAttrs = isForm || isSettings || isWizard ? ' width="narrow"' : isDashboard ? ' density="spacious"' : '';
251
- innerTemplate = `<MConfigProvider :locale="zhCN">
252
- <MLayout fill-viewport>
253
- <MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
254
- <MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: title }]" />
255
- </MLayoutHeader>
256
- <MLayoutContent>
257
- <MPageContent${pageContentAttrs}>
258
- ${content}
259
- </MPageContent>
260
- </MLayoutContent>
261
- </MLayout>
253
+ innerTemplate = `<MConfigProvider :locale="zhCN">
254
+ <MLayout fill-viewport>
255
+ <MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
256
+ <MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: title }]" />
257
+ </MLayoutHeader>
258
+ <MLayoutContent>
259
+ <MPageContent${pageContentAttrs}>
260
+ ${content}
261
+ </MPageContent>
262
+ </MLayoutContent>
263
+ </MLayout>
262
264
  </MConfigProvider>`;
263
265
  }
264
266
  else if (isAuth) {
265
- innerTemplate = `<MConfigProvider :locale="zhCN">
266
- <main class="m-generated-page m-generated-auth">
267
- <MCard>
268
- <MForm label-position="top" @submit.prevent="submit">
269
- <MFormItem label="${zh ? '邮箱' : 'Email'}" name="email">
270
- <MInput type="email" fluid />
271
- </MFormItem>
272
- <MFormItem label="${zh ? '密码' : 'Password'}" name="password">
273
- <MInput type="password" fluid />
274
- </MFormItem>
275
- <MButton native-type="submit" severity="primary" :loading="loading" fluid>${zh ? '登录' : 'Sign in'}</MButton>
276
- </MForm>
277
- </MCard>
278
- </main>
267
+ innerTemplate = `<MConfigProvider :locale="zhCN">
268
+ <main class="m-generated-page m-generated-auth">
269
+ <MCard>
270
+ <MForm label-position="top" @submit.prevent="submit">
271
+ <MFormItem label="${zh ? '邮箱' : 'Email'}" name="email">
272
+ <MInput type="email" fluid />
273
+ </MFormItem>
274
+ <MFormItem label="${zh ? '密码' : 'Password'}" name="password">
275
+ <MInput type="password" fluid />
276
+ </MFormItem>
277
+ <MButton native-type="submit" severity="primary" :loading="loading" fluid>${zh ? '登录' : 'Sign in'}</MButton>
278
+ </MForm>
279
+ </MCard>
280
+ </main>
279
281
  </MConfigProvider>`;
280
282
  }
281
283
  else if (isEmpty) {
282
- innerTemplate = `<MConfigProvider :locale="zhCN">
283
- <main class="m-generated-page">
284
- <MPageContent>
285
- <MPageHeader :title="title">
286
- <template #actions>
287
- <MButton severity="primary" @click="submit">${zh ? '新建' : 'Create'}</MButton>
288
- </template>
289
- </MPageHeader>
290
- <MEmpty
291
- :title="${zh ? '暂无内容' : 'Nothing here yet'}"
292
- :description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
293
- icon="database"
294
- >
295
- <template #extra>
296
- <MButton severity="primary" @click="submit">${zh ? '创建' : 'Create'}</MButton>
297
- </template>
298
- </MEmpty>
299
- </MPageContent>
300
- </main>
284
+ innerTemplate = `<MConfigProvider :locale="zhCN">
285
+ <main class="m-generated-page">
286
+ <MPageContent>
287
+ <MPageHeader :title="title">
288
+ <template #actions>
289
+ <MButton severity="primary" @click="submit">${zh ? '新建' : 'Create'}</MButton>
290
+ </template>
291
+ </MPageHeader>
292
+ <MEmpty
293
+ :title="${zh ? '暂无内容' : 'Nothing here yet'}"
294
+ :description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
295
+ icon="database"
296
+ >
297
+ <template #extra>
298
+ <MButton severity="primary" @click="submit">${zh ? '创建' : 'Create'}</MButton>
299
+ </template>
300
+ </MEmpty>
301
+ </MPageContent>
302
+ </main>
301
303
  </MConfigProvider>`;
302
304
  }
303
305
  else {
304
- innerTemplate = `<MConfigProvider :locale="zhCN">
305
- <main class="m-generated-page">
306
- <MCard>
307
- <p class="m-generated-muted">${zh ? '将此区域替换为页面内容。' : 'Replace this area with page content.'}</p>
308
- <MTag value="${zh ? '示例' : 'Example'}" severity="info" />
309
- </MCard>
310
- </main>
306
+ innerTemplate = `<MConfigProvider :locale="zhCN">
307
+ <main class="m-generated-page">
308
+ <MCard>
309
+ <p class="m-generated-muted">${zh ? '将此区域替换为页面内容。' : 'Replace this area with page content.'}</p>
310
+ <MTag value="${zh ? '示例' : 'Example'}" severity="info" />
311
+ </MCard>
312
+ </main>
311
313
  </MConfigProvider>`;
312
314
  }
313
- const template = `<template>
314
- ${innerTemplate}
315
- <p v-if="error" role="alert" class="m-generated-error">${'{{ error }}'}</p>
315
+ const template = `<template>
316
+ ${innerTemplate}
317
+ <p v-if="error" role="alert" class="m-generated-error">${'{{ error }}'}</p>
316
318
  </template>`;
317
- const style = `<style scoped>
318
- .m-generated-auth { display: grid; place-items: center; padding: var(--m-space-8); max-width: 24rem; margin: 0 auto; min-height: 100vh; }
319
- .m-generated-empty { display: grid; gap: var(--m-space-2); justify-items: center; padding: var(--m-space-8); text-align: center; }
320
- .m-generated-error { color: var(--m-color-danger); padding: 0 var(--m-space-6); }
319
+ const style = `<style scoped>
320
+ .m-generated-auth { display: grid; place-items: center; padding: var(--m-space-8); max-width: 24rem; margin: 0 auto; min-height: 100vh; }
321
+ .m-generated-empty { display: grid; gap: var(--m-space-2); justify-items: center; padding: var(--m-space-8); text-align: center; }
322
+ .m-generated-error { color: var(--m-color-danger); padding: 0 var(--m-space-6); }
321
323
  </style>`;
322
324
  return { script, template, style };
323
325
  }
@@ -729,13 +731,43 @@ export function createToolHandlers(catalog = loadCatalog()) {
729
731
  });
730
732
  }
731
733
  const locale = resolveLocale(args.mode);
734
+ const styleDirection = resolveStyleDirection({ intent: args.intent, style: args.style });
735
+ const stylePayload = {
736
+ resolution: styleDirection.resolution,
737
+ confidence: styleDirection.confidence,
738
+ summary: styleDirection.summary ?? null,
739
+ guidance: locale === 'en-US' ? styleDirection.guidanceEn : styleDirection.guidanceZh,
740
+ priority: locale === 'en-US' ? styleResolution.priorityEn : styleResolution.priorityZh,
741
+ hardRules: locale === 'en-US' ? styleResolution.hardRulesEn : styleResolution.hardRulesZh,
742
+ note: locale === 'en-US'
743
+ ? 'No named style-preset catalog. Follow an explicit user reference/description; if styleDirection.resolution is ask, ask before inventing the look. Prefer snippets for composition; golden pages are optional block-order checks, not aesthetics.'
744
+ : '没有命名风格预设表。有明确参考/描述就跟用户;若 styleDirection.resolution 为 ask,先询问再写完整视觉。优先用 snippets 拼装;黄金样例仅可选核对块顺序,不是审美模板。',
745
+ };
746
+ const craftText = [args.style || '', args.intent || ''].join(' ');
747
+ const resolvedGoldenId = resolveGoldenPageId({
748
+ patternId: best.pattern.id,
749
+ canonicalGoldenId: best.pattern.goldenPage,
750
+ styleId: craftText || null,
751
+ });
752
+ const resolvedGoldenPath = resolvedGoldenId
753
+ ? `docs/golden-pages/${resolvedGoldenId}.vue`
754
+ : best.pattern.goldenPage;
755
+ let suggestedSnippets = [...(best.pattern.suggestedSnippets ?? [])];
756
+ if (best.pattern.id === 'admin-list') {
757
+ const listCraft = resolveListCraftVariant(craftText);
758
+ if (listCraft === 'list-page-dense') {
759
+ suggestedSnippets = suggestedSnippets.map((id) => id === 'list-filters-stack' ? 'list-filters-dense' : id);
760
+ }
761
+ }
732
762
  const result = {
733
763
  intent: args.intent,
734
764
  pageType: args.pageType,
765
+ style: args.style,
735
766
  matchedPattern: best.pattern.id,
736
767
  title: locale === 'en-US' ? best.pattern.titleEn : best.pattern.title,
737
768
  confidence: best.score,
738
- goldenPage: best.pattern.goldenPage,
769
+ goldenPage: resolvedGoldenPath,
770
+ styleDirection: stylePayload,
739
771
  components: best.pattern.components,
740
772
  structure: best.pattern.structure,
741
773
  layout: best.pattern.layout,
@@ -743,24 +775,39 @@ export function createToolHandlers(catalog = loadCatalog()) {
743
775
  interactionRules: best.pattern.interactionRules,
744
776
  avoid: best.pattern.avoid,
745
777
  alternatives: ranked.slice(1, 3).filter((item) => item.score > 0).map((item) => ({ id: item.pattern.id, score: item.score })),
778
+ suggestedSnippets,
746
779
  nextStep: (() => {
747
- const goldenId = best.pattern.goldenPage?.split('/').pop()?.replace(/\.vue$/i, '') ||
748
- listGoldenPages().find((item) => item.patternId === best.pattern.id)?.id ||
749
- null;
780
+ const goldenId = resolvedGoldenId;
781
+ const snippets = suggestedSnippets;
782
+ const snippetHint = snippets.length > 0
783
+ ? locale === 'en-US'
784
+ ? ` Compose with get_page_snippet for: ${snippets.join(', ')}. Use recommend_component for L2 recipes (relatedSnippets).`
785
+ : ` 用 get_page_snippet 拼装:${snippets.join(', ')}。选型用 recommend_component(含 relatedSnippets)。`
786
+ : locale === 'en-US'
787
+ ? ' Compose with get_page_snippet + recommend_component (relatedSnippets).'
788
+ : ' 用 get_page_snippet + recommend_component(relatedSnippets)拼装。';
789
+ const styleHint = locale === 'en-US'
790
+ ? styleDirection.resolution === 'ask'
791
+ ? ' Style is uncertain — ask the user for a short description or reference before inventing the look.'
792
+ : ` Follow styleDirection (${styleDirection.resolution}): “${styleDirection.summary || 'user/reference'}”; honor explicit user words over companions.`
793
+ : styleDirection.resolution === 'ask'
794
+ ? ' 风格不确定 — 先请用户说明气质或贴参考,再写完整视觉。'
795
+ : ` 按 styleDirection(${styleDirection.resolution})落实「${styleDirection.summary || '用户参考/描述'}」;用户明确指定优先于 companion。`;
796
+ const goldenHint = goldenId
797
+ ? locale === 'en-US'
798
+ ? ` Optional: get_golden_page("${goldenId}") only to check whole-page block order (not the default clone target).`
799
+ : ` 可选:仅当需核对整页块顺序时再 get_golden_page("${goldenId}")(默认不要整页克隆)。`
800
+ : locale === 'en-US'
801
+ ? ' No golden page for this pattern — follow get_pattern structure + snippets.'
802
+ : ' 该模式暂无黄金样例:按 get_pattern 结构 + snippets 拼装。';
750
803
  if (locale === 'en-US') {
751
- return goldenId
752
- ? `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.`
753
- : `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.`;
804
+ return `${snippetHint.trim()}${styleHint}${goldenHint} Look up unfamiliar APIs with get_component/get_example, then always run validate_usage and validate_page.`;
754
805
  }
755
- return goldenId
756
- ? `调用 get_golden_page("${goldenId}") 并镜像结构。不熟悉的 API 用 get_component/get_example 核对,然后必须跑 validate_usage 与 validate_page。includeScaffold 有黄金样例时返回样例源码,不是另一套审美模板。`
757
- : `该模式暂无黄金样例:用 get_pattern + get_design_rules,API 用 get_component/get_example 核对,然后必须跑 validate_usage 与 validate_page。`;
806
+ return `${snippetHint.trim()}${styleHint}${goldenHint} 不熟悉的 API 用 get_component/get_example 核对,然后必须跑 validate_usage 与 validate_page。`;
758
807
  })(),
759
808
  };
760
809
  if (args.includeScaffold) {
761
- const goldenId = best.pattern.goldenPage?.split('/').pop()?.replace(/\.vue$/i, '') ||
762
- listGoldenPages().find((item) => item.patternId === best.pattern.id)?.id ||
763
- null;
810
+ const goldenId = resolvedGoldenId;
764
811
  const golden = goldenId ? readGoldenPageSource(goldenId) : null;
765
812
  if (golden) {
766
813
  result.scaffold = {
@@ -769,8 +816,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
769
816
  files: { component: golden.source },
770
817
  warnings: [
771
818
  locale === 'en-US'
772
- ? 'Scaffold is the golden page source. Remap domain copy/data only; never invent props. Then validate_usage + validate_page.'
773
- : '脚手架即黄金样例源码。只替换域文案与数据,禁止臆造 prop。完成后必须 validate_usage + validate_page。',
819
+ ? 'Scaffold is an optional structure reference from the golden sample. Prefer composing suggestedSnippets; remap domain copy/data only if you use this source. Never invent props. Then validate_usage + validate_page.'
820
+ : '脚手架是黄金样例的可选结构参考。默认优先拼装 suggestedSnippets;若使用本源码只替换域文案与数据。禁止臆造 prop。完成后必须 validate_usage + validate_page。',
774
821
  ],
775
822
  };
776
823
  }
@@ -783,8 +830,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
783
830
  files: { component: componentSource },
784
831
  warnings: [
785
832
  locale === 'en-US'
786
- ? 'No golden page for this pattern — structural fallback only. Prefer get_pattern + get_component. Must validate_usage before delivery.'
787
- : '该模式暂无黄金样例,仅为结构占位。优先 get_pattern + get_component。交付前必须 validate_usage。',
833
+ ? 'No golden page for this pattern — structural fallback only. Prefer suggestedSnippets + get_component. Must validate_usage before delivery.'
834
+ : '该模式暂无黄金样例,仅为结构占位。优先 suggestedSnippets + get_component。交付前必须 validate_usage。',
788
835
  ],
789
836
  };
790
837
  }
@@ -805,6 +852,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
805
852
  patternId: payload.record.patternId,
806
853
  title: locale === 'en-US' ? payload.record.titleEn : payload.record.title,
807
854
  file: payload.record.file,
855
+ style: payload.record.style ?? null,
856
+ variantOf: payload.record.variantOf ?? null,
808
857
  source: payload.source,
809
858
  nextStep: locale === 'en-US'
810
859
  ? '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.'
@@ -819,6 +868,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
819
868
  patternId: item.patternId,
820
869
  title: locale === 'en-US' ? item.titleEn : item.title,
821
870
  file: item.file,
871
+ style: item.style ?? null,
872
+ variantOf: item.variantOf ?? null,
822
873
  })),
823
874
  });
824
875
  }
@@ -1033,6 +1084,19 @@ export function createToolHandlers(catalog = loadCatalog()) {
1033
1084
  : '契约:MTable 行数据使用 rows,不要用 data。见黄金样例 list-page / get_component Table。',
1034
1085
  });
1035
1086
  }
1087
+ if (/fill-viewport|fillViewport/i.test(code)
1088
+ && /<MTable\b[^>]*\bpaginator\b/i.test(code)
1089
+ && /<MPageContent\b/i.test(code)
1090
+ && !(/<MPageContent\b[^>]*\bfill\b/i.test(code)
1091
+ && /<MTable\b[^>]*\bfill\b/i.test(code))) {
1092
+ suggestions.push({
1093
+ standardId: 'page-sections',
1094
+ type: 'list-fill-height',
1095
+ message: locale === 'en-US'
1096
+ ? '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).'
1097
+ : '参考建议:若这是全视口后台主列表且表格是页面主任务,可考虑 MPageContent fill + MTable fill(表体滚动、分页贴底)。嵌入表/短页/整页文档滚动则跳过 fill(见 page-scroll-choice / 黄金样例 list-page)。',
1098
+ });
1099
+ }
1036
1100
  if (/<MMessage\b[^>]*\bseverity\b/i.test(code)) {
1037
1101
  suggestions.push({
1038
1102
  standardId: 'feedback',
@@ -1097,10 +1161,18 @@ export function createToolHandlers(catalog = loadCatalog()) {
1097
1161
  }
1098
1162
  function getDesignRules(args = {}) {
1099
1163
  const locale = resolveLocale(args.mode);
1164
+ const styles = {
1165
+ resolution: locale === 'en-US' ? styleResolution.priorityEn : styleResolution.priorityZh,
1166
+ hardRules: locale === 'en-US' ? styleResolution.hardRulesEn : styleResolution.hardRulesZh,
1167
+ note: locale === 'en-US'
1168
+ ? 'No named style-preset catalog. Follow user reference/description; ask when uncertain.'
1169
+ : '没有命名风格预设表。跟用户参考/描述;不确定就先问。',
1170
+ };
1100
1171
  if (locale === 'zh-CN') {
1101
1172
  return textResult({
1102
1173
  meta: designRules.meta,
1103
1174
  standards: designRules.standards,
1175
+ styles,
1104
1176
  tokens: designRules.tokens,
1105
1177
  composition: designRules.composition,
1106
1178
  actions: designRules.actions,
@@ -1118,6 +1190,7 @@ export function createToolHandlers(catalog = loadCatalog()) {
1118
1190
  discouraged: item.discouragedEn,
1119
1191
  mcp: item.mcp,
1120
1192
  })),
1193
+ styles,
1121
1194
  tokens: {
1122
1195
  colors: ['--m-color-primary', '--m-color-surface', '--m-color-text', '--m-color-border'],
1123
1196
  spacing: '--m-space-*',
@@ -1159,9 +1232,25 @@ export function createToolHandlers(catalog = loadCatalog()) {
1159
1232
  'Form controls should have a visible label or an equivalent accessible name.',
1160
1233
  'Overlays teleport to body by default; only change appendTo for a clear layout constraint.',
1161
1234
  'Prefer documented component variants over deep CSS overrides.',
1235
+ 'Resolve visual style: follow explicit user reference/description first; else infer from clear prompt cues; if uncertain ask once. No named preset catalog. Golden pages lock structure, not aesthetics.',
1162
1236
  ],
1163
1237
  });
1164
1238
  }
1239
+ function getStyleDirection(args = {}) {
1240
+ const locale = resolveLocale(args.mode);
1241
+ const direction = resolveStyleDirection({ intent: args.intent, style: args.style });
1242
+ return textResult({
1243
+ resolution: direction.resolution,
1244
+ confidence: direction.confidence,
1245
+ summary: direction.summary ?? null,
1246
+ guidance: locale === 'en-US' ? direction.guidanceEn : direction.guidanceZh,
1247
+ priority: locale === 'en-US' ? styleResolution.priorityEn : styleResolution.priorityZh,
1248
+ hardRules: locale === 'en-US' ? styleResolution.hardRulesEn : styleResolution.hardRulesZh,
1249
+ note: locale === 'en-US'
1250
+ ? 'No named style-preset catalog. Pass style as free-text user description when they specified one.'
1251
+ : '没有命名风格预设表。用户有描述时把原文放进 style;不确定时 resolution 为 ask,应先问用户。',
1252
+ });
1253
+ }
1165
1254
  function recommendComponent(args) {
1166
1255
  const locale = resolveLocale(args.mode);
1167
1256
  if (!args.query && !args.decision) {
@@ -1380,6 +1469,7 @@ export function createToolHandlers(catalog = loadCatalog()) {
1380
1469
  'get_pattern',
1381
1470
  'recommend_page',
1382
1471
  'get_design_rules',
1472
+ 'get_style_direction',
1383
1473
  'recommend_component',
1384
1474
  'list_golden_pages',
1385
1475
  'get_golden_page',
@@ -1403,6 +1493,7 @@ export function createToolHandlers(catalog = loadCatalog()) {
1403
1493
  getPattern,
1404
1494
  recommendPage,
1405
1495
  getDesignRules,
1496
+ getStyleDirection,
1406
1497
  recommendComponent,
1407
1498
  listGoldenPageCatalog,
1408
1499
  getGoldenPage,