@morya-ui/mcp 0.3.4 → 0.3.6

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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -151
  3. package/bin/morya-ui-mcp.js +2 -2
  4. package/data/catalog.json +40 -40
  5. package/data/example-coverage.json +2 -2
  6. package/data/golden-pages/dashboard-page.vue +138 -138
  7. package/data/golden-pages/detail-page.vue +178 -178
  8. package/data/golden-pages/empty-state.vue +56 -56
  9. package/data/golden-pages/form-in-dialog.vue +166 -166
  10. package/data/golden-pages/form-page.vue +107 -107
  11. package/data/golden-pages/landing-page.vue +358 -358
  12. package/data/golden-pages/list-page-dense.vue +168 -166
  13. package/data/golden-pages/list-page-rail.vue +272 -270
  14. package/data/golden-pages/list-page.vue +222 -220
  15. package/data/golden-pages/login-page.vue +231 -231
  16. package/data/golden-pages/result-page.vue +84 -84
  17. package/data/golden-pages/settings-page.vue +163 -163
  18. package/data/golden-pages/wizard-form.vue +182 -182
  19. package/dist/__tests__/fidelity.test.d.ts +1 -0
  20. package/dist/__tests__/fidelity.test.js +88 -0
  21. package/dist/__tests__/page-snippets.test.js +42 -5
  22. package/dist/__tests__/tools.test.js +49 -29
  23. package/dist/decisions.d.ts +2 -0
  24. package/dist/decisions.js +34 -6
  25. package/dist/golden-pages.d.ts +6 -1
  26. package/dist/golden-pages.js +34 -33
  27. package/dist/index.js +52 -9
  28. package/dist/page-snippets.js +1041 -280
  29. package/dist/patterns.d.ts +4 -2
  30. package/dist/patterns.js +35 -6
  31. package/dist/reference-brief.d.ts +55 -0
  32. package/dist/reference-brief.js +468 -0
  33. package/dist/style-presets.d.ts +13 -32
  34. package/dist/style-presets.js +100 -291
  35. package/dist/style-shells.d.ts +20 -0
  36. package/dist/style-shells.js +249 -0
  37. package/dist/tools.d.ts +21 -3
  38. package/dist/tools.js +496 -424
  39. package/package.json +11 -13
package/dist/tools.js CHANGED
@@ -4,7 +4,9 @@ import { listGoldenPages, readGoldenPageSource, resolveGoldenPageId } from './go
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
+ import { resolveListCraftVariant, resolveStyleDirection, styleResolution, } from './style-presets.js';
8
+ import { codeCoversBlock, codeCoversComponent, mapReferenceBrief, } from './reference-brief.js';
9
+ import { findStyleShell, listStyleShells, styleShells } from './style-shells.js';
8
10
  function inspectButtonIconOnlyUsage(code, issues) {
9
11
  const pairedTagRe = /<MButton\b([^>]*)>([\s\S]*?)<\/MButton>/gi;
10
12
  let match = pairedTagRe.exec(code);
@@ -65,6 +67,7 @@ function mapDecisionOption(option, locale) {
65
67
  avoidWhen: zh ? option.avoidWhen : option.avoidWhenEn,
66
68
  recipe,
67
69
  antiPatterns: zh ? option.antiPatterns : option.antiPatternsEn,
70
+ ...(option.relatedSnippets?.length ? { relatedSnippets: option.relatedSnippets } : {}),
68
71
  };
69
72
  }
70
73
  function generatedPageCode(patternId, intent, locale) {
@@ -93,149 +96,149 @@ function generatedPageCode(patternId, intent, locale) {
93
96
  const wizardImports = isWizard ? ', MStepper, MSpace' : '';
94
97
  const settingsImports = isSettings ? ', MTabs' : '';
95
98
  const statusImports = isList || isDetail || isDashboard ? ', MStatus' : '';
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
- }
99
+ const script = `<script setup lang="ts">
100
+ import { ref } from 'vue'
101
+ import { MButton, MCard, MConfigProvider, MInput, MTag, zhCN${layoutImports}${pageImports}${listImports}${formImports}${dashboardImports}${detailImports}${emptyImports}${wizardImports}${settingsImports}${statusImports} } from 'morya-ui'
102
+
103
+ const loading = ref(false)
104
+ const error = ref('')
105
+ const title = ${JSON.stringify(title)}
106
+ ${isList ? `const keyword = ref('')
107
+ const rows = ref<Record<string, unknown>[]>([])
108
+ const columns = [{ key: 'name', label: '${zh ? '名称' : 'Name'}' }, { key: 'status', label: '${zh ? '状态' : 'Status'}' }]
109
+ ` : ''}${isForm || isAuth || isWizard ? `const model = ref({ name: '' })
110
+ ` : ''}${isDashboard ? `const metrics = ref([
111
+ { label: '${zh ? '总用户' : 'Users'}', value: '0' },
112
+ { label: '${zh ? '今日活跃' : 'Active today'}', value: '0' },
113
+ ])
114
+ ` : ''}${isSettings ? `const activeTab = ref('general')
115
+ const settingTabs = [{ label: '${zh ? '常规' : 'General'}', value: 'general' }]
116
+ ` : ''}${isWizard ? `const activeStep = ref(0)
117
+ const wizardSteps = [{ label: '${zh ? '基本信息' : 'Details'}' }, { label: '${zh ? '确认' : 'Confirm'}' }]
118
+ ` : ''}
119
+
120
+ async function submit() {
121
+ loading.value = true
122
+ error.value = ''
123
+ try {
124
+ // Replace with the page API request.
125
+ } finally {
126
+ loading.value = false
127
+ }
128
+ }
126
129
  </script>`;
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>
130
+ const listContent = ` <MPageHeader :title="title">
131
+ <template #actions>
132
+ <MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
133
+ </template>
134
+ </MPageHeader>
135
+ <MPageFilters variant="filled" :aria-label="${zh ? '筛选' : 'Filters'}">
136
+ <MSpace wrap>
137
+ <MInput v-model="keyword" placeholder="${zh ? '搜索关键词' : 'Search keyword'}" clearable style="width: 14rem" />
138
+ <MButton severity="secondary">${zh ? '查询' : 'Search'}</MButton>
139
+ <MButton severity="secondary">${zh ? '重置' : 'Reset'}</MButton>
140
+ </MSpace>
141
+ </MPageFilters>
142
+ <MTable :columns="columns" :rows="rows" :loading="loading" fill paginator :rows-per-page="10" striped bordered row-key="id">
143
+ <template #cell-status="{ value }">
144
+ <MStatus :label="String(value ?? '')" :severity="value === 'active' ? 'success' : 'secondary'" />
145
+ </template>
146
+ <template #empty>
147
+ <MEmpty
148
+ :title="${zh ? '暂无数据' : 'No data yet'}"
149
+ :description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
150
+ >
151
+ <template #extra>
152
+ <MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
153
+ </template>
154
+ </MEmpty>
155
+ </template>
153
156
  </MTable>`;
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>
157
+ const formContent = ` <MPageHeader :title="title" :description="${zh ? '填写表单并保存。' : 'Fill in the form and save.'}" />
158
+ <MPageSection variant="form">
159
+ <MForm @submit.prevent="submit">
160
+ <MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
161
+ <MInput v-model="model.name" fluid />
162
+ </MFormItem>
163
+ <MPageSection variant="actions">
164
+ <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
165
+ <MButton severity="secondary">${zh ? '取消' : 'Cancel'}</MButton>
166
+ </MPageSection>
167
+ </MForm>
165
168
  </MPageSection>`;
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
- />
169
+ const dashboardContent = ` <MPageHeader :title="title" />
170
+ <MGrid :cols="2" :x-gap="16" :y-gap="16" responsive="screen">
171
+ <MGridItem v-for="metric in metrics" :key="metric.label" :span="1">
172
+ <MPageStat :label="metric.label" :value="metric.value" icon="activity" />
173
+ </MGridItem>
174
+ </MGrid>
175
+ <MCard shadow="always" :title="${zh ? '趋势概览' : 'Trend overview'}">
176
+ <MSkeleton v-if="loading" height="8rem" />
177
+ <MEmpty
178
+ v-else
179
+ :title="${zh ? '暂无图表数据' : 'No chart data yet'}"
180
+ :description="${zh ? '接入图表或业务组件。' : 'Connect charts or business widgets here.'}"
181
+ aria-label="${zh ? '图表占位' : 'Chart placeholder'}"
182
+ />
180
183
  </MCard>`;
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>
184
+ const detailContent = ` <MPageHeader :title="title" :description="${zh ? '查看资源摘要与属性。' : 'Review summary and properties.'}">
185
+ <template #actions>
186
+ <MSpace>
187
+ <MStatus :label="${zh ? '启用' : 'Active'}" severity="success" />
188
+ <MButton severity="primary">${zh ? '编辑' : 'Edit'}</MButton>
189
+ <MButton severity="secondary">${zh ? '返回' : 'Back'}</MButton>
190
+ </MSpace>
191
+ </template>
192
+ </MPageHeader>
193
+ <MCard :title="${zh ? '基本信息' : 'Basics'}">
194
+ <p style="margin:0;color:var(--m-color-text-muted)">${zh ? '用定义列表或网格展示属性;相关列表用 MTable + rows。' : 'Use a definition grid; related lists use MTable + rows.'}</p>
192
195
  </MCard>`;
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>
196
+ const settingsContent = ` <MPageHeader :title="title" :description="${zh ? '按域分组配置偏好。' : 'Grouped preference settings.'}" />
197
+ <MTabs v-model="activeTab" :tabs="settingTabs">
198
+ <template #default="{ activeValue }">
199
+ <MPageSection v-if="activeValue === 'general'" variant="form" :title="${zh ? '常规' : 'General'}">
200
+ <MForm @submit.prevent="submit">
201
+ <MFormItem label="${zh ? '显示名称' : 'Display name'}" name="name" required>
202
+ <MInput v-model="model.name" fluid />
203
+ </MFormItem>
204
+ <MPageSection variant="actions">
205
+ <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
206
+ </MPageSection>
207
+ </MForm>
208
+ </MPageSection>
209
+ </template>
207
210
  </MTabs>`;
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>
211
+ const wizardContent = ` <MPageHeader :title="title" :description="${zh ? '每步一个主任务。' : 'One job per step.'}" />
212
+ <MStepper v-model="activeStep" :steps="wizardSteps" linear />
213
+ <MPageSection variant="form">
214
+ <MForm @submit.prevent="submit">
215
+ <MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
216
+ <MInput v-model="model.name" fluid />
217
+ </MFormItem>
218
+ <MPageSection variant="actions">
219
+ <MSpace>
220
+ <MButton severity="secondary" text :disabled="activeStep === 0" @click="activeStep = Math.max(activeStep - 1, 0)">${zh ? '上一步' : 'Back'}</MButton>
221
+ <MButton native-type="submit" severity="primary" :loading="loading">${zh ? '下一步' : 'Next'}</MButton>
222
+ </MSpace>
223
+ </MPageSection>
224
+ </MForm>
222
225
  </MPageSection>`;
223
226
  let innerTemplate = '';
224
227
  if (isList) {
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>
228
+ innerTemplate = `<MConfigProvider :locale="zhCN">
229
+ <MLayout has-sider fill-viewport>
230
+ <MLayoutSider bordered />
231
+ <MLayout>
232
+ <MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
233
+ <MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: '${title}' }]" />
234
+ </MLayoutHeader>
235
+ <MLayoutContent>
236
+ <MPageContent fill>
237
+ ${listContent}
238
+ </MPageContent>
239
+ </MLayoutContent>
240
+ </MLayout>
241
+ </MLayout>
239
242
  </MConfigProvider>`;
240
243
  }
241
244
  else if (useLayoutShell) {
@@ -249,76 +252,76 @@ ${listContent}
249
252
  ? wizardContent
250
253
  : formContent;
251
254
  const pageContentAttrs = isForm || isSettings || isWizard ? ' width="narrow"' : isDashboard ? ' density="spacious"' : '';
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>
255
+ innerTemplate = `<MConfigProvider :locale="zhCN">
256
+ <MLayout fill-viewport>
257
+ <MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
258
+ <MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: title }]" />
259
+ </MLayoutHeader>
260
+ <MLayoutContent>
261
+ <MPageContent${pageContentAttrs}>
262
+ ${content}
263
+ </MPageContent>
264
+ </MLayoutContent>
265
+ </MLayout>
263
266
  </MConfigProvider>`;
264
267
  }
265
268
  else if (isAuth) {
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>
269
+ innerTemplate = `<MConfigProvider :locale="zhCN">
270
+ <main class="m-generated-page m-generated-auth">
271
+ <MCard>
272
+ <MForm label-position="top" @submit.prevent="submit">
273
+ <MFormItem label="${zh ? '邮箱' : 'Email'}" name="email">
274
+ <MInput type="email" fluid />
275
+ </MFormItem>
276
+ <MFormItem label="${zh ? '密码' : 'Password'}" name="password">
277
+ <MInput type="password" fluid />
278
+ </MFormItem>
279
+ <MButton native-type="submit" severity="primary" :loading="loading" fluid>${zh ? '登录' : 'Sign in'}</MButton>
280
+ </MForm>
281
+ </MCard>
282
+ </main>
280
283
  </MConfigProvider>`;
281
284
  }
282
285
  else if (isEmpty) {
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>
286
+ innerTemplate = `<MConfigProvider :locale="zhCN">
287
+ <main class="m-generated-page">
288
+ <MPageContent>
289
+ <MPageHeader :title="title">
290
+ <template #actions>
291
+ <MButton severity="primary" @click="submit">${zh ? '新建' : 'Create'}</MButton>
292
+ </template>
293
+ </MPageHeader>
294
+ <MEmpty
295
+ :title="${zh ? '暂无内容' : 'Nothing here yet'}"
296
+ :description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
297
+ icon="database"
298
+ >
299
+ <template #extra>
300
+ <MButton severity="primary" @click="submit">${zh ? '创建' : 'Create'}</MButton>
301
+ </template>
302
+ </MEmpty>
303
+ </MPageContent>
304
+ </main>
302
305
  </MConfigProvider>`;
303
306
  }
304
307
  else {
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>
308
+ innerTemplate = `<MConfigProvider :locale="zhCN">
309
+ <main class="m-generated-page">
310
+ <MCard>
311
+ <p class="m-generated-muted">${zh ? '将此区域替换为页面内容。' : 'Replace this area with page content.'}</p>
312
+ <MTag value="${zh ? '示例' : 'Example'}" severity="info" />
313
+ </MCard>
314
+ </main>
312
315
  </MConfigProvider>`;
313
316
  }
314
- const template = `<template>
315
- ${innerTemplate}
316
- <p v-if="error" role="alert" class="m-generated-error">${'{{ error }}'}</p>
317
+ const template = `<template>
318
+ ${innerTemplate}
319
+ <p v-if="error" role="alert" class="m-generated-error">${'{{ error }}'}</p>
317
320
  </template>`;
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
+ const style = `<style scoped>
322
+ .m-generated-auth { display: grid; place-items: center; padding: var(--m-space-8); max-width: 24rem; margin: 0 auto; min-height: 100vh; }
323
+ .m-generated-empty { display: grid; gap: var(--m-space-2); justify-items: center; padding: var(--m-space-8); text-align: center; }
324
+ .m-generated-error { color: var(--m-color-danger); padding: 0 var(--m-space-6); }
322
325
  </style>`;
323
326
  return { script, template, style };
324
327
  }
@@ -730,42 +733,68 @@ export function createToolHandlers(catalog = loadCatalog()) {
730
733
  });
731
734
  }
732
735
  const locale = resolveLocale(args.mode);
733
- const styleDirection = resolveStyleDirection({ intent: args.intent, style: args.style });
736
+ const styleFromBrief = args.brief?.style || args.brief?.description;
737
+ const styleDirection = resolveStyleDirection({
738
+ intent: args.intent,
739
+ style: args.style || styleFromBrief,
740
+ });
734
741
  const stylePayload = {
735
742
  resolution: styleDirection.resolution,
736
743
  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
- })),
744
+ summary: styleDirection.summary ?? null,
751
745
  guidance: locale === 'en-US' ? styleDirection.guidanceEn : styleDirection.guidanceZh,
752
746
  priority: locale === 'en-US' ? styleResolution.priorityEn : styleResolution.priorityZh,
747
+ hardRules: locale === 'en-US' ? styleResolution.hardRulesEn : styleResolution.hardRulesZh,
753
748
  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*)。黄金样例锁结构,不是唯一审美。',
749
+ ? '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. Pass brief for fidelity mapping; density=compact|default|spacious.'
750
+ : '没有命名风格预设表。有明确参考/描述就跟用户;若 styleDirection.resolution 为 ask,先询问再写完整视觉。优先用 snippets 拼装;黄金样例仅可选核对块顺序。传 brief 做还原映射;density=compact|default|spacious。',
756
751
  };
752
+ const craftText = [args.style || '', args.intent || '', args.density || '', args.brief?.description || ''].join(' ');
753
+ const density = args.density === 'compact' || args.density === 'dense'
754
+ ? 'compact'
755
+ : args.density === 'spacious'
756
+ ? 'spacious'
757
+ : resolveListCraftVariant(craftText) === 'list-page-dense'
758
+ ? 'compact'
759
+ : 'default';
760
+ const briefMapped = args.brief || args.density || styleFromBrief
761
+ ? mapReferenceBrief({
762
+ ...(args.brief || {}),
763
+ intent: args.intent,
764
+ style: args.style || args.brief?.style,
765
+ density: args.density || args.brief?.density || density,
766
+ surface: args.brief?.surface || args.pageType,
767
+ })
768
+ : null;
757
769
  const resolvedGoldenId = resolveGoldenPageId({
758
770
  patternId: best.pattern.id,
759
771
  canonicalGoldenId: best.pattern.goldenPage,
760
- styleId: styleDirection.preset?.id || args.style || null,
772
+ styleId: craftText || null,
761
773
  });
762
774
  const resolvedGoldenPath = resolvedGoldenId
763
775
  ? `docs/golden-pages/${resolvedGoldenId}.vue`
764
776
  : best.pattern.goldenPage;
777
+ let suggestedSnippets = [...(best.pattern.suggestedSnippets ?? [])];
778
+ if (best.pattern.id === 'admin-list') {
779
+ const listCraft = density === 'compact' ? 'list-page-dense' : resolveListCraftVariant(craftText);
780
+ if (listCraft === 'list-page-dense') {
781
+ suggestedSnippets = suggestedSnippets.map((id) => id === 'list-filters-stack' ? 'list-filters-dense' : id);
782
+ }
783
+ }
784
+ if (briefMapped?.suggestedSnippets.length) {
785
+ for (const id of briefMapped.suggestedSnippets) {
786
+ if (!suggestedSnippets.includes(id))
787
+ suggestedSnippets.push(id);
788
+ }
789
+ }
790
+ const shell = briefMapped?.suggestedShellId
791
+ ? findStyleShell(briefMapped.suggestedShellId)
792
+ : undefined;
765
793
  const result = {
766
794
  intent: args.intent,
767
795
  pageType: args.pageType,
768
796
  style: args.style,
797
+ density,
769
798
  matchedPattern: best.pattern.id,
770
799
  title: locale === 'en-US' ? best.pattern.titleEn : best.pattern.title,
771
800
  confidence: best.score,
@@ -778,23 +807,70 @@ export function createToolHandlers(catalog = loadCatalog()) {
778
807
  interactionRules: best.pattern.interactionRules,
779
808
  avoid: best.pattern.avoid,
780
809
  alternatives: ranked.slice(1, 3).filter((item) => item.score > 0).map((item) => ({ id: item.pattern.id, score: item.score })),
810
+ suggestedSnippets,
811
+ referenceMapping: briefMapped
812
+ ? {
813
+ surface: briefMapped.surface,
814
+ density: briefMapped.density,
815
+ mapping: briefMapped.mapping.map((item) => ({
816
+ block: item.block,
817
+ snippetId: item.snippetId,
818
+ notes: locale === 'en-US' ? item.notesEn : item.notesZh,
819
+ })),
820
+ suggestedShellId: briefMapped.suggestedShellId,
821
+ craftCues: locale === 'en-US' ? briefMapped.craftCuesEn : briefMapped.craftCuesZh,
822
+ validationHints: briefMapped.validationHints,
823
+ tokenRoles: briefMapped.tokenRoles.map((item) => ({
824
+ role: item.role,
825
+ token: item.token,
826
+ notes: locale === 'en-US' ? item.notesEn : item.notesZh,
827
+ })),
828
+ }
829
+ : null,
830
+ styleShell: shell
831
+ ? {
832
+ id: shell.id,
833
+ title: locale === 'en-US' ? shell.titleEn : shell.titleZh,
834
+ signature: locale === 'en-US' ? shell.signatureEn : shell.signatureZh,
835
+ css: shell.css,
836
+ htmlHint: locale === 'en-US' ? shell.htmlHintEn : shell.htmlHintZh,
837
+ }
838
+ : null,
781
839
  nextStep: (() => {
782
840
  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'}」。`;
790
- if (locale === 'en-US') {
791
- return goldenId
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.`;
794
- }
795
- return goldenId
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。`;
841
+ const snippets = suggestedSnippets;
842
+ const snippetHint = snippets.length > 0
843
+ ? locale === 'en-US'
844
+ ? ` Compose with get_page_snippet for: ${snippets.join(', ')}. Use recommend_component for L2 recipes (relatedSnippets).`
845
+ : ` 用 get_page_snippet 拼装:${snippets.join(', ')}。选型用 recommend_component(含 relatedSnippets)。`
846
+ : locale === 'en-US'
847
+ ? ' Compose with get_page_snippet + recommend_component (relatedSnippets).'
848
+ : ' 用 get_page_snippet + recommend_component(relatedSnippets)拼装。';
849
+ const briefHint = briefMapped
850
+ ? locale === 'en-US'
851
+ ? ' Follow referenceMapping block-by-block; pass the same brief to validate_page.'
852
+ : ' 按 referenceMapping 逐块落地;同一 brief 传给 validate_page。'
853
+ : '';
854
+ const shellHint = shell
855
+ ? locale === 'en-US'
856
+ ? ` Optional shell CSS from styleShell id=${shell.id} (or get_style_shells).`
857
+ : ` 可选签名壳 CSS:styleShell id=${shell.id}(或 get_style_shells)。`
858
+ : '';
859
+ const goldenHint = goldenId && args.includeScaffold
860
+ ? locale === 'en-US'
861
+ ? ` Scaffold mirrors ${goldenId} structure only.`
862
+ : ` scaffold 仅镜像 ${goldenId} 结构。`
863
+ : goldenId
864
+ ? locale === 'en-US'
865
+ ? ` Optional: get_golden_page("${goldenId}") only to check whole-page block order.`
866
+ : ` 可选:仅当需核对整页块顺序时再 get_golden_page("${goldenId}")。`
867
+ : locale === 'en-US'
868
+ ? ' Golden pages are optional block-order checks.'
869
+ : ' 黄金样例仅可选核对块顺序。';
870
+ const validateHint = locale === 'en-US'
871
+ ? ' Then always run validate_usage and validate_page (pass the same brief when used).'
872
+ : ' 然后必须跑 validate_usage 与 validate_page(若用了 brief 则原样传入)。';
873
+ return `${snippetHint}${briefHint}${shellHint}${goldenHint}${validateHint}`;
798
874
  })(),
799
875
  };
800
876
  if (args.includeScaffold) {
@@ -807,8 +883,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
807
883
  files: { component: golden.source },
808
884
  warnings: [
809
885
  locale === 'en-US'
810
- ? 'Scaffold is the golden page source. Remap domain copy/data only; never invent props. Then validate_usage + validate_page.'
811
- : '脚手架即黄金样例源码。只替换域文案与数据,禁止臆造 prop。完成后必须 validate_usage + validate_page。',
886
+ ? '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.'
887
+ : '脚手架是黄金样例的可选结构参考。默认优先拼装 suggestedSnippets;若使用本源码只替换域文案与数据。禁止臆造 prop。完成后必须 validate_usage + validate_page。',
812
888
  ],
813
889
  };
814
890
  }
@@ -821,8 +897,8 @@ export function createToolHandlers(catalog = loadCatalog()) {
821
897
  files: { component: componentSource },
822
898
  warnings: [
823
899
  locale === 'en-US'
824
- ? 'No golden page for this pattern — structural fallback only. Prefer get_pattern + get_component. Must validate_usage before delivery.'
825
- : '该模式暂无黄金样例,仅为结构占位。优先 get_pattern + get_component。交付前必须 validate_usage。',
900
+ ? 'No golden page for this pattern — structural fallback only. Prefer suggestedSnippets + get_component. Must validate_usage before delivery.'
901
+ : '该模式暂无黄金样例,仅为结构占位。优先 suggestedSnippets + get_component。交付前必须 validate_usage。',
826
902
  ],
827
903
  };
828
904
  }
@@ -931,223 +1007,159 @@ export function createToolHandlers(catalog = loadCatalog()) {
931
1007
  const locale = resolveLocale(args.mode);
932
1008
  const code = args.code || '';
933
1009
  const suggestions = [];
934
- if (/#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})\b/i.test(code)) {
1010
+ const push = (severity, standardId, type, messageZh, messageEn) => {
935
1011
  suggestions.push({
936
- standardId: 'tokens',
937
- type: 'raw-color',
938
- message: locale === 'en-US'
939
- ? 'Standard: prefer --m-* design tokens over hex colors.'
940
- : '标准建议:颜色优先使用 --m-* 设计令牌,而非 hex 色值。',
1012
+ severity,
1013
+ standardId,
1014
+ type,
1015
+ message: locale === 'en-US' ? messageEn : messageZh,
941
1016
  });
1017
+ };
1018
+ if (/#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})\b/i.test(code)) {
1019
+ push('craft', 'tokens', 'raw-color', '标准建议:颜色优先使用 --m-* 设计令牌,而非 hex 色值。', 'Standard: prefer --m-* design tokens over hex colors.');
942
1020
  }
943
1021
  if (/\brgb\s*\(/i.test(code) && !/--m-/.test(code)) {
944
- suggestions.push({
945
- standardId: 'tokens',
946
- type: 'raw-color',
947
- message: locale === 'en-US'
948
- ? 'Standard: prefer --m-* tokens over raw rgb()/rgba() in page styles.'
949
- : '标准建议:页面样式优先 --m-* 令牌,而非裸 rgb()/rgba()。',
950
- });
1022
+ push('craft', 'tokens', 'raw-color', '标准建议:页面样式优先 --m-* 令牌,而非裸 rgb()/rgba()。', 'Standard: prefer --m-* tokens over raw rgb()/rgba() in page styles.');
951
1023
  }
952
1024
  if (/<MCard\b[^>]*>[\s\S]*?<MTable\b[^>]*\bbordered\b/i.test(code)) {
953
- suggestions.push({
954
- standardId: 'page-sections',
955
- type: 'double-border',
956
- message: locale === 'en-US'
957
- ? 'Standard: place bordered MTable directly in MPageContent instead of wrapping it with MCard.'
958
- : '标准建议:bordered 的 MTable 直接放在 MPageContent 内,通常不必再包 MCard。',
959
- });
1025
+ push('craft', 'page-sections', 'double-border', '标准建议:bordered 的 MTable 直接放在 MPageContent 内,通常不必再包 MCard。', 'Standard: place bordered MTable directly in MPageContent instead of wrapping it with MCard.');
960
1026
  }
961
1027
  if (/<MLayoutContent\b/i.test(code) && !/<MPageContent\b/i.test(code)) {
962
- suggestions.push({
963
- standardId: 'layout-shell',
964
- type: 'missing-page-content',
965
- message: locale === 'en-US'
966
- ? 'Standard: prefer MPageContent inside MLayoutContent for consistent page spacing.'
967
- : '标准建议:MLayoutContent 内使用 MPageContent 统一页面间距。',
968
- });
1028
+ push('craft', 'layout-shell', 'missing-page-content', '标准建议:MLayoutContent 内使用 MPageContent 统一页面间距。', 'Standard: prefer MPageContent inside MLayoutContent for consistent page spacing.');
969
1029
  }
970
1030
  if ((/\bpage[-_](?:list|form|dashboard)?[-_]?(?:filters|toolbar|content|header)\b/i.test(code) ||
971
1031
  /class="[^"]*filters/i.test(code)) &&
972
1032
  !/<MPage(?:Content|Filters|Toolbar|Header|Section|Stat)\b/i.test(code)) {
973
- suggestions.push({
974
- standardId: 'page-sections',
975
- type: 'custom-page-css',
976
- message: locale === 'en-US'
977
- ? 'Standard: prefer MPageFilters, MPageToolbar, MPageHeader, or MPageSection over custom page section CSS.'
978
- : '标准建议:筛选/工具栏/表单区块优先 MPage* 组件,少写自定义 page section CSS。',
979
- });
1033
+ push('craft', 'page-sections', 'custom-page-css', '标准建议:筛选/工具栏/表单区块优先 MPage* 组件,少写自定义 page section CSS。', 'Standard: prefer MPageFilters, MPageToolbar, MPageHeader, or MPageSection over custom page section CSS.');
980
1034
  }
981
1035
  if (/<MPageFilters\b/i.test(code) && /<MCard\b[^>]*>[\s\S]*?<MPageFilters\b/i.test(code)) {
982
- suggestions.push({
983
- standardId: 'page-sections',
984
- type: 'redundant-wrapper',
985
- message: locale === 'en-US'
986
- ? 'Standard: MPageFilters already provides a surface; wrapping it in MCard is usually redundant.'
987
- : '标准建议:MPageFilters 已自带表面样式,通常不必再用 MCard 包裹。',
988
- });
1036
+ push('craft', 'page-sections', 'redundant-wrapper', '标准建议:MPageFilters 已自带表面样式,通常不必再用 MCard 包裹。', 'Standard: MPageFilters already provides a surface; wrapping it in MCard is usually redundant.');
989
1037
  }
990
1038
  if (/<MPageSection\b[^>]*variant=["']form["']/i.test(code) &&
991
1039
  /<MCard\b[^>]*>[\s\S]*?<MPageSection\b/i.test(code)) {
992
- suggestions.push({
993
- standardId: 'page-sections',
994
- type: 'redundant-wrapper',
995
- message: locale === 'en-US'
996
- ? 'Standard: MPageSection variant="form" already provides a surface; wrapping it in MCard is usually redundant.'
997
- : '标准建议:MPageSection variant="form" 已自带表面样式,通常不必再用 MCard 包裹。',
998
- });
1040
+ push('craft', 'page-sections', 'redundant-wrapper', '标准建议:MPageSection variant="form" 已自带表面样式,通常不必再用 MCard 包裹。', 'Standard: MPageSection variant="form" already provides a surface; wrapping it in MCard is usually redundant.');
999
1041
  }
1000
1042
  if (/<MPageContent\b[^>]*>[\s\S]*?<MPageContent\b/i.test(code)) {
1001
- suggestions.push({
1002
- standardId: 'layout-shell',
1003
- type: 'redundant-wrapper',
1004
- message: locale === 'en-US'
1005
- ? 'Standard: prefer a single MPageContent shell for page padding and section gap.'
1006
- : '标准建议:页面 padding 与区块间距优先只用一层 MPageContent。',
1007
- });
1043
+ push('craft', 'layout-shell', 'redundant-wrapper', '标准建议:页面 padding 与区块间距优先只用一层 MPageContent。', 'Standard: prefer a single MPageContent shell for page padding and section gap.');
1008
1044
  }
1009
1045
  if (/<(?:div|section|main)\b[^>]*style=["'][^"']*(?:padding|gap)\s*:[^"']*["'][^>]*>\s*<(?:MPage(?:Content|Filters|Toolbar|Header|Section)|MTable)\b/i.test(code)) {
1010
- suggestions.push({
1011
- standardId: 'page-sections',
1012
- type: 'redundant-wrapper',
1013
- message: locale === 'en-US'
1014
- ? 'Standard: avoid extra padded wrappers around MPage* blocks; prefer the components’ built-in spacing.'
1015
- : '标准建议:少在 MPage* 外包带 padding 的容器;优先使用组件自带间距。',
1016
- });
1046
+ push('craft', 'page-sections', 'redundant-wrapper', '标准建议:少在 MPage* 外包带 padding 的容器;优先使用组件自带间距。', 'Standard: avoid extra padded wrappers around MPage* blocks; prefer the components’ built-in spacing.');
1017
1047
  }
1018
1048
  if (/style="[^"]*overflow(?:-y|-x)?\s*:\s*(auto|scroll)/i.test(code) ||
1019
1049
  /<style\b[^>]*>[\s\S]*?overflow(?:-y|-x)?\s*:\s*(auto|scroll)/i.test(code)) {
1020
- suggestions.push({
1021
- standardId: 'scroll',
1022
- type: 'native-scroll',
1023
- message: locale === 'en-US'
1024
- ? 'Standard: prefer MScrollbar or MLayout scroll regions over overflow:auto/scroll in page styles.'
1025
- : '标准建议:页面滚动优先 MLayout 主滚动或 MScrollbar,少写 overflow:auto/scroll。',
1026
- });
1050
+ push('craft', 'scroll', 'native-scroll', '标准建议:页面滚动优先 MLayout 主滚动或 MScrollbar,少写 overflow:auto/scroll。', 'Standard: prefer MScrollbar or MLayout scroll regions over overflow:auto/scroll in page styles.');
1027
1051
  }
1028
1052
  if (/::-webkit-scrollbar|scrollbar-width\s*:/i.test(code)) {
1029
- suggestions.push({
1030
- standardId: 'scroll',
1031
- type: 'custom-scrollbar-css',
1032
- message: locale === 'en-US'
1033
- ? 'Standard: prefer MScrollbar or layout defaults over native scrollbar CSS in product code.'
1034
- : '标准建议:业务代码优先 MScrollbar 或 Layout 默认滚动,少定制原生滚动条样式。',
1035
- });
1053
+ push('craft', 'scroll', 'custom-scrollbar-css', '标准建议:业务代码优先 MScrollbar 或 Layout 默认滚动,少定制原生滚动条样式。', 'Standard: prefer MScrollbar or layout defaults over native scrollbar CSS in product code.');
1036
1054
  }
1037
1055
  if (/min-height\s*:\s*100vh/i.test(code) && !/<MLayout\b[^>]*fill-viewport/i.test(code)) {
1038
- suggestions.push({
1039
- standardId: 'layout-shell',
1040
- type: 'viewport-height',
1041
- message: locale === 'en-US'
1042
- ? 'Standard: prefer MLayout fillViewport over hand-written min-height:100vh page shells.'
1043
- : '标准建议:整页高度优先 MLayout fillViewport,少写 min-height:100vh。',
1044
- });
1056
+ push('craft', 'layout-shell', 'viewport-height', '标准建议:整页高度优先 MLayout fillViewport,少写 min-height:100vh。', 'Standard: prefer MLayout fillViewport over hand-written min-height:100vh page shells.');
1045
1057
  }
1046
- if ((/fill-viewport|fillViewport/i.test(code) || /<MLayout\b/i.test(code))
1047
- && !/html\s*,\s*body\s*,\s*#app[\s\S]{0,80}height\s*:\s*100%/i.test(code)) {
1048
- suggestions.push({
1049
- standardId: 'layout-shell',
1050
- type: 'height-chain',
1051
- message: locale === 'en-US'
1052
- ? 'Prefer layout-app-shell / golden pages: with MLayout, set html, body, #app { height: 100% } when the shell is not using fillViewport.'
1053
- : '对齐 layout-app-shell / 黄金样例:未使用 fillViewport 时,配合 MLayout 设置 html, body, #app { height: 100% }。',
1054
- });
1058
+ if ((/fill-viewport|fillViewport/i.test(code) || /<MLayout\b/i.test(code)) &&
1059
+ !/html\s*,\s*body\s*,\s*#app[\s\S]{0,80}height\s*:\s*100%/i.test(code)) {
1060
+ push('craft', 'layout-shell', 'height-chain', '对齐 layout-app-shell / 黄金样例:未使用 fillViewport 时,配合 MLayout 设置 html, body, #app { height: 100% }。', 'Prefer layout-app-shell / golden pages: with MLayout, set html, body, #app { height: 100% } when the shell is not using fillViewport.');
1055
1061
  }
1056
1062
  if (/<MMenu\b/i.test(code)) {
1057
1063
  const iconCount = (code.match(/\bicon\s*:/g) || []).length;
1058
1064
  const looksLikeMenuModel = /MMenu[\s\S]{0,1200}\b(label|model)\s*[:=]/i.test(code) || /:model=/i.test(code);
1059
1065
  if (looksLikeMenuModel && iconCount === 0) {
1060
- suggestions.push({
1061
- standardId: 'layout-shell',
1062
- type: 'menu-missing-icons',
1063
- message: locale === 'en-US'
1064
- ? 'Prefer golden / layout-app-shell menu items that include icon (e.g. user, shield, home).'
1065
- : '对齐黄金样例 / layout-app-shell:侧栏菜单项带上 icon(如 user、shield、home)。',
1066
- });
1066
+ push('craft', 'layout-shell', 'menu-missing-icons', '对齐黄金样例 / layout-app-shell:侧栏菜单项带上 icon(如 user、shield、home)。', 'Prefer golden / layout-app-shell menu items that include icon (e.g. user, shield, home).');
1067
1067
  }
1068
1068
  }
1069
1069
  if (/<MTable\b[^>]*(?::data|v-bind:data)\s*=/i.test(code) || /<MTable\b[^>]*\sdata\s*=/i.test(code)) {
1070
- suggestions.push({
1071
- standardId: 'feedback',
1072
- type: 'table-data-prop',
1073
- message: locale === 'en-US'
1074
- ? 'Contract: MTable row data uses the rows prop (not data). See golden list-page / get_component Table.'
1075
- : '契约:MTable 行数据使用 rows,不要用 data。见黄金样例 list-page / get_component Table。',
1076
- });
1070
+ push('contract', 'feedback', 'table-data-prop', '契约:MTable 行数据使用 rows,不要用 data。见黄金样例 list-page / get_component Table。', 'Contract: MTable row data uses the rows prop (not data). See golden list-page / get_component Table.');
1077
1071
  }
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
- });
1072
+ if (/fill-viewport|fillViewport/i.test(code) &&
1073
+ /<MTable\b[^>]*\bpaginator\b/i.test(code) &&
1074
+ /<MPageContent\b/i.test(code) &&
1075
+ !(/<MPageContent\b[^>]*\bfill\b/i.test(code) && /<MTable\b[^>]*\bfill\b/i.test(code))) {
1076
+ push('craft', 'page-sections', 'list-fill-height', '参考建议:若这是全视口后台主列表且表格是页面主任务,可考虑 MPageContent fill + MTable fill(表体滚动、分页贴底)。嵌入表/短页/整页文档滚动则跳过 fill(见 page-scroll-choice / 黄金样例 list-page)。', '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).');
1090
1077
  }
1091
1078
  if (/<MMessage\b[^>]*\bseverity\b/i.test(code)) {
1092
- suggestions.push({
1093
- standardId: 'feedback',
1094
- type: 'mmessage-as-alert',
1095
- message: locale === 'en-US'
1096
- ? 'Contract: <MMessage> is the message service host, not an inline alert. Use field errorMessage or a token-styled role="alert".'
1097
- : '契约:<MMessage> 是 message 服务宿主,不是内嵌 Alert。表单常驻错误用字段 errorMessage 或 token 样式的 role="alert"。',
1098
- });
1079
+ push('contract', 'feedback', 'mmessage-as-alert', '契约:<MMessage> 是 message 服务宿主,不是内嵌 Alert。表单常驻错误用字段 errorMessage 或 token 样式的 role="alert"。', 'Contract: <MMessage> is the message service host, not an inline alert. Use field errorMessage or a token-styled role="alert".');
1099
1080
  }
1100
1081
  if (/toast\.(?:success|info|warn|warning|error)\s*\(\s*['"`][^'"`]+['"`]\s*\)/i.test(code)) {
1101
- suggestions.push({
1102
- standardId: 'feedback',
1103
- type: 'toast-one-liner',
1104
- message: locale === 'en-US'
1105
- ? 'Contract: one-line feedback should use message.*; toast is for summary + detail or async feel.'
1106
- : '契约:单行操作回执用 message.*;toast 仅用于 summary + detail 或异步通知感。',
1107
- });
1082
+ push('contract', 'feedback', 'toast-one-liner', '契约:单行操作回执用 message.*;toast 仅用于 summary + detail 或异步通知感。', 'Contract: one-line feedback should use message.*; toast is for summary + detail or async feel.');
1108
1083
  }
1109
1084
  if (/#cell-status[\s\S]{0,500}<MTag\b/i.test(code) &&
1110
1085
  !/#cell-status[\s\S]{0,500}<MStatus\b/i.test(code)) {
1111
- suggestions.push({
1112
- standardId: 'feedback',
1113
- type: 'status-cell-tag',
1114
- message: locale === 'en-US'
1115
- ? 'Prefer MStatus for business status in #cell-status; use MTag for categories / closable chips (see list-status-dot / detail-page).'
1116
- : '行内业务状态优先 MStatus(#cell-status);分类或可关闭标签再用 MTag(见 list-status-dot / detail-page)。',
1117
- });
1086
+ push('craft', 'feedback', 'status-cell-tag', '行内业务状态优先 MStatus(#cell-status);分类或可关闭标签再用 MTag(见 list-status-dot / detail-page)。', 'Prefer MStatus for business status in #cell-status; use MTag for categories / closable chips (see list-status-dot / detail-page).');
1118
1087
  }
1119
1088
  if (/<MTabs\b[^>]*(?::items|v-bind:items)\s*=/i.test(code) || /<MTabs\b[^>]*\sitems\s*=/i.test(code)) {
1120
- suggestions.push({
1121
- standardId: 'feedback',
1122
- type: 'tabs-items-prop',
1123
- message: locale === 'en-US'
1124
- ? 'Contract: MTabs uses the tabs prop with v-model (not items / value). See settings-page golden.'
1125
- : '契约:MTabs 使用 v-model + tabs(不是 items / value)。见黄金样例 settings-page。',
1126
- });
1089
+ push('contract', 'feedback', 'tabs-items-prop', '契约:MTabs 使用 v-model + tabs(不是 items / value)。见黄金样例 settings-page。', 'Contract: MTabs uses the tabs prop with v-model (not items / value). See settings-page golden.');
1127
1090
  }
1128
1091
  if (/<MStepper\b[^>]*(?::items|v-bind:items)\s*=/i.test(code) || /<MStepper\b[^>]*\sitems\s*=/i.test(code)) {
1129
- suggestions.push({
1130
- standardId: 'feedback',
1131
- type: 'stepper-items-prop',
1132
- message: locale === 'en-US'
1133
- ? 'Contract: MStepper uses the steps prop with v-model (not items). See wizard-form golden.'
1134
- : '契约:MStepper 使用 v-model + steps(不是 items)。见黄金样例 wizard-form。',
1135
- });
1092
+ push('contract', 'feedback', 'stepper-items-prop', '契约:MStepper 使用 v-model + steps(不是 items)。见黄金样例 wizard-form。', 'Contract: MStepper uses the steps prop with v-model (not items). See wizard-form golden.');
1093
+ }
1094
+ // Craft: placeholder / lorem copy
1095
+ if (/(?:title|description|label|placeholder)\s*=\s*["'](?:示例|样例|Name|No data|暂无数据|lorem|placeholder)["']/i.test(code) ||
1096
+ />\s*(?:示例|样例|No data|暂无数据|Lorem ipsum)\s*</i.test(code)) {
1097
+ push('craft', 'craft', 'placeholder-copy', '工艺建议:用领域真实文案,避免「示例 / Name / No data / 暂无数据」。', 'Craft: use domain-real copy — avoid “示例 / Name / No data / 暂无数据”.');
1098
+ }
1099
+ // Craft: more than one filled primary in viewport (heuristic)
1100
+ {
1101
+ const primaryFilled = code.match(/<MButton\b(?![^>]*(?:\btext\b|\boutlined\b|\blink\b))[^>]*\bseverity\s*=\s*["']primary["'][^>]*>|<MButton\b(?![^>]*(?:\btext\b|\boutlined\b|\blink\b|\bseverity\s*=))[^>]*>/gi);
1102
+ // Count explicit severity="primary" without text/outlined
1103
+ const explicitPrimary = (code.match(/<MButton\b(?![^>]*(?:\btext\b|\boutlined\b))[^>]*severity\s*=\s*["']primary["']/gi) || []).length;
1104
+ if (explicitPrimary > 1) {
1105
+ push('craft', 'craft', 'multiple-primary', '工艺建议:主视口内通常只需一个 filled primary 按钮。', 'Craft: usually only one filled primary button in the main viewport.');
1106
+ }
1107
+ void primaryFilled;
1108
+ }
1109
+ // Craft: icon-only without accessible name
1110
+ {
1111
+ const iconOnlyRe = /<MButton\b([^>]*)\b(?:icon-only|iconOnly)\b([^>]*)\/?>/gi;
1112
+ let m = iconOnlyRe.exec(code);
1113
+ while (m !== null) {
1114
+ const attrs = `${m[1] || ''}${m[2] || ''}`;
1115
+ if (!/\baria-label\b|\bariaLabel\b|\baria-labelledby\b/i.test(attrs)) {
1116
+ push('craft', 'a11y', 'icon-only-missing-aria', '工艺建议:仅图标的 MButton 需要 aria-label / ariaLabel。', 'Craft: icon-only MButton needs aria-label / ariaLabel.');
1117
+ break;
1118
+ }
1119
+ m = iconOnlyRe.exec(code);
1120
+ }
1121
+ }
1122
+ // Brief fidelity: required blocks / components
1123
+ const mapped = args.brief
1124
+ ? mapReferenceBrief(args.brief)
1125
+ : null;
1126
+ const requiredBlocks = [
1127
+ ...(args.requiredBlocks || []),
1128
+ ...(mapped?.validationHints.requiredBlocks || []),
1129
+ ];
1130
+ const requiredComponents = [
1131
+ ...(args.requiredComponents || []),
1132
+ ...(mapped?.validationHints.requiredComponents || []),
1133
+ ];
1134
+ for (const block of requiredBlocks) {
1135
+ if (!codeCoversBlock(code, block)) {
1136
+ push('contract', 'fidelity', 'brief-missing-block', `还原度:参考要求的区块「${block}」未在代码中检测到。`, `Fidelity: required reference block “${block}” was not detected in the code.`);
1137
+ }
1138
+ }
1139
+ for (const component of requiredComponents) {
1140
+ if (!codeCoversComponent(code, component)) {
1141
+ push('contract', 'fidelity', 'brief-missing-component', `还原度:参考要求的组件 M${component.replace(/^M/, '')} 未出现。`, `Fidelity: required component M${component.replace(/^M/, '')} is missing.`);
1142
+ }
1136
1143
  }
1144
+ const contract = suggestions.filter((item) => item.severity === 'contract');
1145
+ const craft = suggestions.filter((item) => item.severity === 'craft');
1146
+ const ok = contract.length === 0;
1137
1147
  return textResult({
1138
- ok: true,
1148
+ ok,
1139
1149
  advisory: true,
1140
1150
  summary: locale === 'en-US'
1141
1151
  ? suggestions.length === 0
1142
1152
  ? 'No deviations from page standards detected.'
1143
- : `${suggestions.length} advisory suggestion(s).`
1153
+ : `${contract.length} contract issue(s), ${craft.length} craft suggestion(s).`
1144
1154
  : suggestions.length === 0
1145
1155
  ? '未发现与页面标准的明显偏差。'
1146
- : `${suggestions.length} 条参考建议。`,
1156
+ : `${contract.length} 条契约问题,${craft.length} 条工艺建议。`,
1157
+ contract,
1158
+ craft,
1147
1159
  suggestions,
1148
1160
  nextStep: locale === 'en-US'
1149
- ? 'See get_design_rules.standards for the full recommended page standards.'
1150
- : '完整页面标准见 get_design_rules.standards。',
1161
+ ? 'Fix contract issues before delivery; craft suggestions may be waived with justification. Pass the same brief used in map_reference / recommend_page. See get_design_rules / get_style_shells.'
1162
+ : '契约问题交付前必须修好;工艺建议可说明后放过。brief 与 map_reference / recommend_page 保持一致。另见 get_design_rules / get_style_shells。',
1151
1163
  });
1152
1164
  }
1153
1165
  function getDesignRules(args = {}) {
@@ -1155,24 +1167,30 @@ export function createToolHandlers(catalog = loadCatalog()) {
1155
1167
  const styles = {
1156
1168
  resolution: locale === 'en-US' ? styleResolution.priorityEn : styleResolution.priorityZh,
1157
1169
  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
- })),
1170
+ note: locale === 'en-US'
1171
+ ? 'No named style-preset catalog. Follow user reference/description; ask when uncertain. Use map_reference for fidelity mapping and get_style_shells for pasteable token CSS.'
1172
+ : '没有命名风格预设表。跟用户参考/描述;不确定就先问。还原用 map_reference;可粘贴签名壳见 get_style_shells。',
1164
1173
  };
1174
+ const shells = styleShells.map((shell) => ({
1175
+ id: shell.id,
1176
+ surface: shell.surface,
1177
+ title: locale === 'en-US' ? shell.titleEn : shell.titleZh,
1178
+ signature: locale === 'en-US' ? shell.signatureEn : shell.signatureZh,
1179
+ when: locale === 'en-US' ? shell.whenEn : shell.whenZh,
1180
+ }));
1165
1181
  if (locale === 'zh-CN') {
1166
1182
  return textResult({
1167
1183
  meta: designRules.meta,
1168
1184
  standards: designRules.standards,
1169
1185
  styles,
1186
+ shells,
1170
1187
  tokens: designRules.tokens,
1171
1188
  composition: designRules.composition,
1172
1189
  actions: designRules.actions,
1173
1190
  status: designRules.status,
1174
1191
  feedback: designRules.feedback,
1175
1192
  global: designRules.global,
1193
+ themeOverride: '品牌差异优先在应用入口覆盖 --m-color-primary / 字体等主题变量,页面 CSS 只用 --m-*;见 DESIGN.md「主题覆盖」。',
1176
1194
  });
1177
1195
  }
1178
1196
  return textResult({
@@ -1185,6 +1203,7 @@ export function createToolHandlers(catalog = loadCatalog()) {
1185
1203
  mcp: item.mcp,
1186
1204
  })),
1187
1205
  styles,
1206
+ shells,
1188
1207
  tokens: {
1189
1208
  colors: ['--m-color-primary', '--m-color-surface', '--m-color-text', '--m-color-border'],
1190
1209
  spacing: '--m-space-*',
@@ -1219,6 +1238,7 @@ export function createToolHandlers(catalog = loadCatalog()) {
1219
1238
  },
1220
1239
  doc: 'design-kit/.agents/skills/morya-ui-pages/references/feedback.md',
1221
1240
  },
1241
+ themeOverride: 'Prefer brand differences via theme overrides (--m-color-primary / fonts) at the app entry; page CSS uses --m-* only. See DESIGN.md theme override section.',
1222
1242
  global: [
1223
1243
  'Prefer library components and --m-* tokens.',
1224
1244
  'Default action feedback to message; prefer message over toast for single-line-only text.',
@@ -1226,45 +1246,95 @@ export function createToolHandlers(catalog = loadCatalog()) {
1226
1246
  'Form controls should have a visible label or an equivalent accessible name.',
1227
1247
  'Overlays teleport to body by default; only change appendTo for a clear layout constraint.',
1228
1248
  '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.',
1249
+ '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.',
1250
+ 'Pass brief to map_reference / recommend_page / validate_page for fidelity.',
1230
1251
  ],
1231
1252
  });
1232
1253
  }
1233
- function listStylePresets(args = {}) {
1254
+ function mapReference(args) {
1234
1255
  const locale = resolveLocale(args.mode);
1256
+ const mapped = mapReferenceBrief(args);
1257
+ const shell = mapped.suggestedShellId ? findStyleShell(mapped.suggestedShellId) : undefined;
1235
1258
  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,
1259
+ surface: mapped.surface,
1260
+ density: mapped.density,
1261
+ styleSummary: mapped.styleSummary,
1262
+ suggestedSnippets: mapped.suggestedSnippets,
1263
+ suggestedShellId: mapped.suggestedShellId,
1264
+ mapping: mapped.mapping.map((item) => ({
1265
+ block: item.block,
1266
+ snippetId: item.snippetId,
1267
+ notes: locale === 'en-US' ? item.notesEn : item.notesZh,
1245
1268
  })),
1269
+ tokenRoles: mapped.tokenRoles.map((item) => ({
1270
+ role: item.role,
1271
+ token: item.token,
1272
+ notes: locale === 'en-US' ? item.notesEn : item.notesZh,
1273
+ })),
1274
+ craftCues: locale === 'en-US' ? mapped.craftCuesEn : mapped.craftCuesZh,
1275
+ validationHints: mapped.validationHints,
1276
+ styleShell: shell
1277
+ ? {
1278
+ id: shell.id,
1279
+ title: locale === 'en-US' ? shell.titleEn : shell.titleZh,
1280
+ signature: locale === 'en-US' ? shell.signatureEn : shell.signatureZh,
1281
+ css: shell.css,
1282
+ htmlHint: locale === 'en-US' ? shell.htmlHintEn : shell.htmlHintZh,
1283
+ }
1284
+ : null,
1285
+ nextStep: locale === 'en-US'
1286
+ ? 'Compose get_page_snippet for each mapping.snippetId; apply styleShell.css when needed; pass the same brief to validate_page.'
1287
+ : '按 mapping.snippetId 逐个 get_page_snippet;需要时粘贴 styleShell.css;同一 brief 交给 validate_page。',
1246
1288
  });
1247
1289
  }
1248
- function getStylePreset(args) {
1290
+ function getStyleShells(args = {}) {
1249
1291
  const locale = resolveLocale(args.mode);
1250
- const preset = findStylePreset(args.style);
1251
- if (!preset) {
1292
+ if (args.shell) {
1293
+ const shell = findStyleShell(args.shell);
1294
+ if (!shell) {
1295
+ return textResult({
1296
+ error: `Style shell not found: ${args.shell}`,
1297
+ available: styleShells.map((item) => item.id),
1298
+ });
1299
+ }
1252
1300
  return textResult({
1253
- error: `Style preset not found: ${args.style}`,
1254
- available: stylePresets.map((item) => item.id),
1301
+ id: shell.id,
1302
+ surface: shell.surface,
1303
+ title: locale === 'en-US' ? shell.titleEn : shell.titleZh,
1304
+ signature: locale === 'en-US' ? shell.signatureEn : shell.signatureZh,
1305
+ when: locale === 'en-US' ? shell.whenEn : shell.whenZh,
1306
+ htmlHint: locale === 'en-US' ? shell.htmlHintEn : shell.htmlHintZh,
1307
+ css: shell.css,
1255
1308
  });
1256
1309
  }
1310
+ const items = listStyleShells(args.surface).map((shell) => ({
1311
+ id: shell.id,
1312
+ surface: shell.surface,
1313
+ title: locale === 'en-US' ? shell.titleEn : shell.titleZh,
1314
+ signature: locale === 'en-US' ? shell.signatureEn : shell.signatureZh,
1315
+ when: locale === 'en-US' ? shell.whenEn : shell.whenZh,
1316
+ }));
1257
1317
  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,
1318
+ total: items.length,
1319
+ items,
1320
+ nextStep: locale === 'en-US'
1321
+ ? 'Pass shell id to get_style_shells({ shell }) for pasteable CSS. Brand color via theme --m-color-primary, not page hex.'
1322
+ : '传 shell id 给 get_style_shells({ shell }) 取可粘贴 CSS。品牌色走主题 --m-color-primary,不要在页面写 hex。',
1323
+ });
1324
+ }
1325
+ function getStyleDirection(args = {}) {
1326
+ const locale = resolveLocale(args.mode);
1327
+ const direction = resolveStyleDirection({ intent: args.intent, style: args.style });
1328
+ return textResult({
1329
+ resolution: direction.resolution,
1330
+ confidence: direction.confidence,
1331
+ summary: direction.summary ?? null,
1332
+ guidance: locale === 'en-US' ? direction.guidanceEn : direction.guidanceZh,
1333
+ priority: locale === 'en-US' ? styleResolution.priorityEn : styleResolution.priorityZh,
1334
+ hardRules: locale === 'en-US' ? styleResolution.hardRulesEn : styleResolution.hardRulesZh,
1335
+ note: locale === 'en-US'
1336
+ ? 'No named style-preset catalog. Pass style as free-text user description when they specified one.'
1337
+ : '没有命名风格预设表。用户有描述时把原文放进 style;不确定时 resolution 为 ask,应先问用户。',
1268
1338
  });
1269
1339
  }
1270
1340
  function recommendComponent(args) {
@@ -1484,9 +1554,10 @@ export function createToolHandlers(catalog = loadCatalog()) {
1484
1554
  'list_patterns',
1485
1555
  'get_pattern',
1486
1556
  'recommend_page',
1557
+ 'map_reference',
1487
1558
  'get_design_rules',
1488
- 'list_style_presets',
1489
- 'get_style_preset',
1559
+ 'get_style_direction',
1560
+ 'get_style_shells',
1490
1561
  'recommend_component',
1491
1562
  'list_golden_pages',
1492
1563
  'get_golden_page',
@@ -1509,9 +1580,10 @@ export function createToolHandlers(catalog = loadCatalog()) {
1509
1580
  listPatterns,
1510
1581
  getPattern,
1511
1582
  recommendPage,
1583
+ mapReference,
1512
1584
  getDesignRules,
1513
- listStylePresets,
1514
- getStylePreset,
1585
+ getStyleDirection,
1586
+ getStyleShells,
1515
1587
  recommendComponent,
1516
1588
  listGoldenPageCatalog,
1517
1589
  getGoldenPage,