@morya-ui/mcp 0.3.3 → 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);
@@ -92,149 +93,149 @@ function generatedPageCode(patternId, intent, locale) {
92
93
  const wizardImports = isWizard ? ', MStepper, MSpace' : '';
93
94
  const settingsImports = isSettings ? ', MTabs' : '';
94
95
  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
- }
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
+ }
125
126
  </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>
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>
152
153
  </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>
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>
164
165
  </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
- />
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
+ />
179
180
  </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>
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>
191
192
  </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>
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>
206
207
  </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>
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>
221
222
  </MPageSection>`;
222
223
  let innerTemplate = '';
223
224
  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>
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>
238
239
  </MConfigProvider>`;
239
240
  }
240
241
  else if (useLayoutShell) {
@@ -248,76 +249,76 @@ ${listContent}
248
249
  ? wizardContent
249
250
  : formContent;
250
251
  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>
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>
262
263
  </MConfigProvider>`;
263
264
  }
264
265
  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>
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>
279
280
  </MConfigProvider>`;
280
281
  }
281
282
  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>
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>
301
302
  </MConfigProvider>`;
302
303
  }
303
304
  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>
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>
311
312
  </MConfigProvider>`;
312
313
  }
313
- const template = `<template>
314
- ${innerTemplate}
315
- <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>
316
317
  </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); }
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); }
321
322
  </style>`;
322
323
  return { script, template, style };
323
324
  }
@@ -729,13 +730,47 @@ export function createToolHandlers(catalog = loadCatalog()) {
729
730
  });
730
731
  }
731
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;
732
765
  const result = {
733
766
  intent: args.intent,
734
767
  pageType: args.pageType,
768
+ style: args.style,
735
769
  matchedPattern: best.pattern.id,
736
770
  title: locale === 'en-US' ? best.pattern.titleEn : best.pattern.title,
737
771
  confidence: best.score,
738
- goldenPage: best.pattern.goldenPage,
772
+ goldenPage: resolvedGoldenPath,
773
+ styleDirection: stylePayload,
739
774
  components: best.pattern.components,
740
775
  structure: best.pattern.structure,
741
776
  layout: best.pattern.layout,
@@ -744,23 +779,26 @@ export function createToolHandlers(catalog = loadCatalog()) {
744
779
  avoid: best.pattern.avoid,
745
780
  alternatives: ranked.slice(1, 3).filter((item) => item.score > 0).map((item) => ({ id: item.pattern.id, score: item.score })),
746
781
  nextStep: (() => {
747
- const goldenId = best.pattern.goldenPage?.split('/').pop()?.replace(/\.vue$/i, '') ||
748
- listGoldenPages().find((item) => item.patternId === best.pattern.id)?.id ||
749
- 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'}」。`;
750
790
  if (locale === 'en-US') {
751
791
  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.`;
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.`;
754
794
  }
755
795
  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。`;
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。`;
758
798
  })(),
759
799
  };
760
800
  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;
801
+ const goldenId = resolvedGoldenId;
764
802
  const golden = goldenId ? readGoldenPageSource(goldenId) : null;
765
803
  if (golden) {
766
804
  result.scaffold = {
@@ -805,6 +843,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
805
843
  patternId: payload.record.patternId,
806
844
  title: locale === 'en-US' ? payload.record.titleEn : payload.record.title,
807
845
  file: payload.record.file,
846
+ style: payload.record.style ?? null,
847
+ variantOf: payload.record.variantOf ?? null,
808
848
  source: payload.source,
809
849
  nextStep: locale === 'en-US'
810
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.'
@@ -819,6 +859,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
819
859
  patternId: item.patternId,
820
860
  title: locale === 'en-US' ? item.titleEn : item.title,
821
861
  file: item.file,
862
+ style: item.style ?? null,
863
+ variantOf: item.variantOf ?? null,
822
864
  })),
823
865
  });
824
866
  }
@@ -1033,6 +1075,19 @@ export function createToolHandlers(catalog = loadCatalog()) {
1033
1075
  : '契约:MTable 行数据使用 rows,不要用 data。见黄金样例 list-page / get_component Table。',
1034
1076
  });
1035
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
+ }
1036
1091
  if (/<MMessage\b[^>]*\bseverity\b/i.test(code)) {
1037
1092
  suggestions.push({
1038
1093
  standardId: 'feedback',
@@ -1097,10 +1152,21 @@ export function createToolHandlers(catalog = loadCatalog()) {
1097
1152
  }
1098
1153
  function getDesignRules(args = {}) {
1099
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
+ };
1100
1165
  if (locale === 'zh-CN') {
1101
1166
  return textResult({
1102
1167
  meta: designRules.meta,
1103
1168
  standards: designRules.standards,
1169
+ styles,
1104
1170
  tokens: designRules.tokens,
1105
1171
  composition: designRules.composition,
1106
1172
  actions: designRules.actions,
@@ -1118,6 +1184,7 @@ export function createToolHandlers(catalog = loadCatalog()) {
1118
1184
  discouraged: item.discouragedEn,
1119
1185
  mcp: item.mcp,
1120
1186
  })),
1187
+ styles,
1121
1188
  tokens: {
1122
1189
  colors: ['--m-color-primary', '--m-color-surface', '--m-color-text', '--m-color-border'],
1123
1190
  spacing: '--m-space-*',
@@ -1159,9 +1226,47 @@ export function createToolHandlers(catalog = loadCatalog()) {
1159
1226
  'Form controls should have a visible label or an equivalent accessible name.',
1160
1227
  'Overlays teleport to body by default; only change appendTo for a clear layout constraint.',
1161
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.',
1162
1230
  ],
1163
1231
  });
1164
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
+ }
1165
1270
  function recommendComponent(args) {
1166
1271
  const locale = resolveLocale(args.mode);
1167
1272
  if (!args.query && !args.decision) {
@@ -1380,6 +1485,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
1380
1485
  'get_pattern',
1381
1486
  'recommend_page',
1382
1487
  'get_design_rules',
1488
+ 'list_style_presets',
1489
+ 'get_style_preset',
1383
1490
  'recommend_component',
1384
1491
  'list_golden_pages',
1385
1492
  'get_golden_page',
@@ -1403,6 +1510,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
1403
1510
  getPattern,
1404
1511
  recommendPage,
1405
1512
  getDesignRules,
1513
+ listStylePresets,
1514
+ getStylePreset,
1406
1515
  recommendComponent,
1407
1516
  listGoldenPageCatalog,
1408
1517
  getGoldenPage,