@morya-ui/mcp 0.3.5 → 0.3.7
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/LICENSE +21 -21
- package/README.md +154 -151
- package/bin/morya-ui-mcp.js +2 -2
- package/data/catalog.json +487 -389
- package/data/example-coverage.json +56 -74
- package/data/golden-pages/dashboard-page.vue +138 -138
- package/data/golden-pages/detail-page.vue +178 -178
- package/data/golden-pages/empty-state.vue +56 -56
- package/data/golden-pages/form-in-dialog.vue +166 -166
- package/data/golden-pages/form-page.vue +107 -107
- package/data/golden-pages/landing-page.vue +358 -358
- package/data/golden-pages/list-page-dense.vue +168 -168
- package/data/golden-pages/list-page-rail.vue +272 -272
- package/data/golden-pages/list-page.vue +222 -222
- package/data/golden-pages/login-page.vue +231 -231
- package/data/golden-pages/result-page.vue +84 -84
- package/data/golden-pages/settings-page.vue +163 -163
- package/data/golden-pages/wizard-form.vue +182 -182
- package/dist/__tests__/fidelity.test.d.ts +1 -0
- package/dist/__tests__/fidelity.test.js +88 -0
- package/dist/__tests__/page-snippets.test.js +13 -5
- package/dist/__tests__/tools.test.js +10 -8
- package/dist/decisions.js +6 -16
- package/dist/index.js +46 -3
- package/dist/page-snippets.js +722 -635
- package/dist/reference-brief.d.ts +55 -0
- package/dist/reference-brief.js +468 -0
- package/dist/style-shells.d.ts +20 -0
- package/dist/style-shells.js +249 -0
- package/dist/tools.d.ts +26 -0
- package/dist/tools.js +471 -374
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -5,6 +5,8 @@ import { filterPageSnippets, findPageSnippet, pageSnippets, scorePageSnippet } f
|
|
|
5
5
|
import { designRules, findPattern, pagePatterns, scorePattern } from './patterns.js';
|
|
6
6
|
import { countCatalogResources, countCatalogResourceTemplates } from './resources.js';
|
|
7
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);
|
|
@@ -94,149 +96,149 @@ function generatedPageCode(patternId, intent, locale) {
|
|
|
94
96
|
const wizardImports = isWizard ? ', MStepper, MSpace' : '';
|
|
95
97
|
const settingsImports = isSettings ? ', MTabs' : '';
|
|
96
98
|
const statusImports = isList || isDetail || isDashboard ? ', MStatus' : '';
|
|
97
|
-
const script = `<script setup lang="ts">
|
|
98
|
-
import { ref } from 'vue'
|
|
99
|
-
import { MButton, MCard, MConfigProvider, MInput, MTag, zhCN${layoutImports}${pageImports}${listImports}${formImports}${dashboardImports}${detailImports}${emptyImports}${wizardImports}${settingsImports}${statusImports} } from 'morya-ui'
|
|
100
|
-
|
|
101
|
-
const loading = ref(false)
|
|
102
|
-
const error = ref('')
|
|
103
|
-
const title = ${JSON.stringify(title)}
|
|
104
|
-
${isList ? `const keyword = ref('')
|
|
105
|
-
const rows = ref<Record<string, unknown>[]>([])
|
|
106
|
-
const columns = [{ key: 'name', label: '${zh ? '名称' : 'Name'}' }, { key: 'status', label: '${zh ? '状态' : 'Status'}' }]
|
|
107
|
-
` : ''}${isForm || isAuth || isWizard ? `const model = ref({ name: '' })
|
|
108
|
-
` : ''}${isDashboard ? `const metrics = ref([
|
|
109
|
-
{ label: '${zh ? '总用户' : 'Users'}', value: '0' },
|
|
110
|
-
{ label: '${zh ? '今日活跃' : 'Active today'}', value: '0' },
|
|
111
|
-
])
|
|
112
|
-
` : ''}${isSettings ? `const activeTab = ref('general')
|
|
113
|
-
const settingTabs = [{ label: '${zh ? '常规' : 'General'}', value: 'general' }]
|
|
114
|
-
` : ''}${isWizard ? `const activeStep = ref(0)
|
|
115
|
-
const wizardSteps = [{ label: '${zh ? '基本信息' : 'Details'}' }, { label: '${zh ? '确认' : 'Confirm'}' }]
|
|
116
|
-
` : ''}
|
|
117
|
-
|
|
118
|
-
async function submit() {
|
|
119
|
-
loading.value = true
|
|
120
|
-
error.value = ''
|
|
121
|
-
try {
|
|
122
|
-
// Replace with the page API request.
|
|
123
|
-
} finally {
|
|
124
|
-
loading.value = false
|
|
125
|
-
}
|
|
126
|
-
}
|
|
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
|
+
}
|
|
127
129
|
</script>`;
|
|
128
|
-
const listContent = ` <MPageHeader :title="title">
|
|
129
|
-
<template #actions>
|
|
130
|
-
<MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
|
|
131
|
-
</template>
|
|
132
|
-
</MPageHeader>
|
|
133
|
-
<MPageFilters variant="filled" :aria-label="${zh ? '筛选' : 'Filters'}">
|
|
134
|
-
<MSpace wrap>
|
|
135
|
-
<MInput v-model="keyword" placeholder="${zh ? '搜索关键词' : 'Search keyword'}" clearable style="width: 14rem" />
|
|
136
|
-
<MButton severity="secondary">${zh ? '查询' : 'Search'}</MButton>
|
|
137
|
-
<MButton severity="secondary">${zh ? '重置' : 'Reset'}</MButton>
|
|
138
|
-
</MSpace>
|
|
139
|
-
</MPageFilters>
|
|
140
|
-
<MTable :columns="columns" :rows="rows" :loading="loading" fill paginator :rows-per-page="10" striped bordered row-key="id">
|
|
141
|
-
<template #cell-status="{ value }">
|
|
142
|
-
<MStatus :label="String(value ?? '')" :severity="value === 'active' ? 'success' : 'secondary'" />
|
|
143
|
-
</template>
|
|
144
|
-
<template #empty>
|
|
145
|
-
<MEmpty
|
|
146
|
-
:title="${zh ? '暂无数据' : 'No data yet'}"
|
|
147
|
-
:description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
|
|
148
|
-
>
|
|
149
|
-
<template #extra>
|
|
150
|
-
<MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
|
|
151
|
-
</template>
|
|
152
|
-
</MEmpty>
|
|
153
|
-
</template>
|
|
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>
|
|
154
156
|
</MTable>`;
|
|
155
|
-
const formContent = ` <MPageHeader :title="title" :description="${zh ? '填写表单并保存。' : 'Fill in the form and save.'}" />
|
|
156
|
-
<MPageSection variant="form">
|
|
157
|
-
<MForm @submit.prevent="submit">
|
|
158
|
-
<MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
|
|
159
|
-
<MInput v-model="model.name" fluid />
|
|
160
|
-
</MFormItem>
|
|
161
|
-
<MPageSection variant="actions">
|
|
162
|
-
<MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
|
|
163
|
-
<MButton severity="secondary">${zh ? '取消' : 'Cancel'}</MButton>
|
|
164
|
-
</MPageSection>
|
|
165
|
-
</MForm>
|
|
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>
|
|
166
168
|
</MPageSection>`;
|
|
167
|
-
const dashboardContent = ` <MPageHeader :title="title" />
|
|
168
|
-
<MGrid :cols="2" :x-gap="16" :y-gap="16" responsive="screen">
|
|
169
|
-
<MGridItem v-for="metric in metrics" :key="metric.label" :span="1">
|
|
170
|
-
<MPageStat :label="metric.label" :value="metric.value" icon="activity" />
|
|
171
|
-
</MGridItem>
|
|
172
|
-
</MGrid>
|
|
173
|
-
<MCard shadow="always" :title="${zh ? '趋势概览' : 'Trend overview'}">
|
|
174
|
-
<MSkeleton v-if="loading" height="8rem" />
|
|
175
|
-
<MEmpty
|
|
176
|
-
v-else
|
|
177
|
-
:title="${zh ? '暂无图表数据' : 'No chart data yet'}"
|
|
178
|
-
:description="${zh ? '接入图表或业务组件。' : 'Connect charts or business widgets here.'}"
|
|
179
|
-
aria-label="${zh ? '图表占位' : 'Chart placeholder'}"
|
|
180
|
-
/>
|
|
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
|
+
/>
|
|
181
183
|
</MCard>`;
|
|
182
|
-
const detailContent = ` <MPageHeader :title="title" :description="${zh ? '查看资源摘要与属性。' : 'Review summary and properties.'}">
|
|
183
|
-
<template #actions>
|
|
184
|
-
<MSpace>
|
|
185
|
-
<MStatus :label="${zh ? '启用' : 'Active'}" severity="success" />
|
|
186
|
-
<MButton severity="primary">${zh ? '编辑' : 'Edit'}</MButton>
|
|
187
|
-
<MButton severity="secondary">${zh ? '返回' : 'Back'}</MButton>
|
|
188
|
-
</MSpace>
|
|
189
|
-
</template>
|
|
190
|
-
</MPageHeader>
|
|
191
|
-
<MCard :title="${zh ? '基本信息' : 'Basics'}">
|
|
192
|
-
<p style="margin:0;color:var(--m-color-text-muted)">${zh ? '用定义列表或网格展示属性;相关列表用 MTable + rows。' : 'Use a definition grid; related lists use MTable + rows.'}</p>
|
|
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>
|
|
193
195
|
</MCard>`;
|
|
194
|
-
const settingsContent = ` <MPageHeader :title="title" :description="${zh ? '按域分组配置偏好。' : 'Grouped preference settings.'}" />
|
|
195
|
-
<MTabs v-model="activeTab" :tabs="settingTabs">
|
|
196
|
-
<template #default="{ activeValue }">
|
|
197
|
-
<MPageSection v-if="activeValue === 'general'" variant="form" :title="${zh ? '常规' : 'General'}">
|
|
198
|
-
<MForm @submit.prevent="submit">
|
|
199
|
-
<MFormItem label="${zh ? '显示名称' : 'Display name'}" name="name" required>
|
|
200
|
-
<MInput v-model="model.name" fluid />
|
|
201
|
-
</MFormItem>
|
|
202
|
-
<MPageSection variant="actions">
|
|
203
|
-
<MButton native-type="submit" severity="primary" :loading="loading">${zh ? '保存' : 'Save'}</MButton>
|
|
204
|
-
</MPageSection>
|
|
205
|
-
</MForm>
|
|
206
|
-
</MPageSection>
|
|
207
|
-
</template>
|
|
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>
|
|
208
210
|
</MTabs>`;
|
|
209
|
-
const wizardContent = ` <MPageHeader :title="title" :description="${zh ? '每步一个主任务。' : 'One job per step.'}" />
|
|
210
|
-
<MStepper v-model="activeStep" :steps="wizardSteps" linear />
|
|
211
|
-
<MPageSection variant="form">
|
|
212
|
-
<MForm @submit.prevent="submit">
|
|
213
|
-
<MFormItem label="${zh ? '名称' : 'Name'}" name="name" required>
|
|
214
|
-
<MInput v-model="model.name" fluid />
|
|
215
|
-
</MFormItem>
|
|
216
|
-
<MPageSection variant="actions">
|
|
217
|
-
<MSpace>
|
|
218
|
-
<MButton severity="secondary" text :disabled="activeStep === 0" @click="activeStep = Math.max(activeStep - 1, 0)">${zh ? '上一步' : 'Back'}</MButton>
|
|
219
|
-
<MButton native-type="submit" severity="primary" :loading="loading">${zh ? '下一步' : 'Next'}</MButton>
|
|
220
|
-
</MSpace>
|
|
221
|
-
</MPageSection>
|
|
222
|
-
</MForm>
|
|
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>
|
|
223
225
|
</MPageSection>`;
|
|
224
226
|
let innerTemplate = '';
|
|
225
227
|
if (isList) {
|
|
226
|
-
innerTemplate = `<MConfigProvider :locale="zhCN">
|
|
227
|
-
<MLayout has-sider fill-viewport>
|
|
228
|
-
<MLayoutSider bordered />
|
|
229
|
-
<MLayout>
|
|
230
|
-
<MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
|
|
231
|
-
<MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: '${title}' }]" />
|
|
232
|
-
</MLayoutHeader>
|
|
233
|
-
<MLayoutContent>
|
|
234
|
-
<MPageContent fill>
|
|
235
|
-
${listContent}
|
|
236
|
-
</MPageContent>
|
|
237
|
-
</MLayoutContent>
|
|
238
|
-
</MLayout>
|
|
239
|
-
</MLayout>
|
|
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>
|
|
240
242
|
</MConfigProvider>`;
|
|
241
243
|
}
|
|
242
244
|
else if (useLayoutShell) {
|
|
@@ -250,76 +252,76 @@ ${listContent}
|
|
|
250
252
|
? wizardContent
|
|
251
253
|
: formContent;
|
|
252
254
|
const pageContentAttrs = isForm || isSettings || isWizard ? ' width="narrow"' : isDashboard ? ' density="spacious"' : '';
|
|
253
|
-
innerTemplate = `<MConfigProvider :locale="zhCN">
|
|
254
|
-
<MLayout fill-viewport>
|
|
255
|
-
<MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
|
|
256
|
-
<MBreadcrumb :model="[{ label: '${zh ? '首页' : 'Home'}', to: '/' }, { label: title }]" />
|
|
257
|
-
</MLayoutHeader>
|
|
258
|
-
<MLayoutContent>
|
|
259
|
-
<MPageContent${pageContentAttrs}>
|
|
260
|
-
${content}
|
|
261
|
-
</MPageContent>
|
|
262
|
-
</MLayoutContent>
|
|
263
|
-
</MLayout>
|
|
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>
|
|
264
266
|
</MConfigProvider>`;
|
|
265
267
|
}
|
|
266
268
|
else if (isAuth) {
|
|
267
|
-
innerTemplate = `<MConfigProvider :locale="zhCN">
|
|
268
|
-
<main class="m-generated-page m-generated-auth">
|
|
269
|
-
<MCard>
|
|
270
|
-
<MForm label-position="top" @submit.prevent="submit">
|
|
271
|
-
<MFormItem label="${zh ? '邮箱' : 'Email'}" name="email">
|
|
272
|
-
<MInput type="email" fluid />
|
|
273
|
-
</MFormItem>
|
|
274
|
-
<MFormItem label="${zh ? '密码' : 'Password'}" name="password">
|
|
275
|
-
<MInput type="password" fluid />
|
|
276
|
-
</MFormItem>
|
|
277
|
-
<MButton native-type="submit" severity="primary" :loading="loading" fluid>${zh ? '登录' : 'Sign in'}</MButton>
|
|
278
|
-
</MForm>
|
|
279
|
-
</MCard>
|
|
280
|
-
</main>
|
|
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>
|
|
281
283
|
</MConfigProvider>`;
|
|
282
284
|
}
|
|
283
285
|
else if (isEmpty) {
|
|
284
|
-
innerTemplate = `<MConfigProvider :locale="zhCN">
|
|
285
|
-
<main class="m-generated-page">
|
|
286
|
-
<MPageContent>
|
|
287
|
-
<MPageHeader :title="title">
|
|
288
|
-
<template #actions>
|
|
289
|
-
<MButton severity="primary" @click="submit">${zh ? '新建' : 'Create'}</MButton>
|
|
290
|
-
</template>
|
|
291
|
-
</MPageHeader>
|
|
292
|
-
<MEmpty
|
|
293
|
-
:title="${zh ? '暂无内容' : 'Nothing here yet'}"
|
|
294
|
-
:description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
|
|
295
|
-
icon="database"
|
|
296
|
-
>
|
|
297
|
-
<template #extra>
|
|
298
|
-
<MButton severity="primary" @click="submit">${zh ? '创建' : 'Create'}</MButton>
|
|
299
|
-
</template>
|
|
300
|
-
</MEmpty>
|
|
301
|
-
</MPageContent>
|
|
302
|
-
</main>
|
|
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>
|
|
303
305
|
</MConfigProvider>`;
|
|
304
306
|
}
|
|
305
307
|
else {
|
|
306
|
-
innerTemplate = `<MConfigProvider :locale="zhCN">
|
|
307
|
-
<main class="m-generated-page">
|
|
308
|
-
<MCard>
|
|
309
|
-
<p class="m-generated-muted">${zh ? '将此区域替换为页面内容。' : 'Replace this area with page content.'}</p>
|
|
310
|
-
<MTag value="${zh ? '示例' : 'Example'}" severity="info" />
|
|
311
|
-
</MCard>
|
|
312
|
-
</main>
|
|
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>
|
|
313
315
|
</MConfigProvider>`;
|
|
314
316
|
}
|
|
315
|
-
const template = `<template>
|
|
316
|
-
${innerTemplate}
|
|
317
|
-
<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>
|
|
318
320
|
</template>`;
|
|
319
|
-
const style = `<style scoped>
|
|
320
|
-
.m-generated-auth { display: grid; place-items: center; padding: var(--m-space-8); max-width: 24rem; margin: 0 auto; min-height: 100vh; }
|
|
321
|
-
.m-generated-empty { display: grid; gap: var(--m-space-2); justify-items: center; padding: var(--m-space-8); text-align: center; }
|
|
322
|
-
.m-generated-error { color: var(--m-color-danger); padding: 0 var(--m-space-6); }
|
|
321
|
+
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); }
|
|
323
325
|
</style>`;
|
|
324
326
|
return { script, template, style };
|
|
325
327
|
}
|
|
@@ -731,7 +733,11 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
731
733
|
});
|
|
732
734
|
}
|
|
733
735
|
const locale = resolveLocale(args.mode);
|
|
734
|
-
const
|
|
736
|
+
const styleFromBrief = args.brief?.style || args.brief?.description;
|
|
737
|
+
const styleDirection = resolveStyleDirection({
|
|
738
|
+
intent: args.intent,
|
|
739
|
+
style: args.style || styleFromBrief,
|
|
740
|
+
});
|
|
735
741
|
const stylePayload = {
|
|
736
742
|
resolution: styleDirection.resolution,
|
|
737
743
|
confidence: styleDirection.confidence,
|
|
@@ -740,10 +746,26 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
740
746
|
priority: locale === 'en-US' ? styleResolution.priorityEn : styleResolution.priorityZh,
|
|
741
747
|
hardRules: locale === 'en-US' ? styleResolution.hardRulesEn : styleResolution.hardRulesZh,
|
|
742
748
|
note: locale === 'en-US'
|
|
743
|
-
? 'No named style-preset catalog. Follow an explicit user reference/description; if styleDirection.resolution is ask, ask before inventing the look. Prefer snippets for composition; golden pages are optional block-order checks, not aesthetics.'
|
|
744
|
-
: '没有命名风格预设表。有明确参考/描述就跟用户;若 styleDirection.resolution 为 ask,先询问再写完整视觉。优先用 snippets
|
|
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。',
|
|
745
751
|
};
|
|
746
|
-
const craftText = [args.style || '', args.intent || ''].join(' ');
|
|
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;
|
|
747
769
|
const resolvedGoldenId = resolveGoldenPageId({
|
|
748
770
|
patternId: best.pattern.id,
|
|
749
771
|
canonicalGoldenId: best.pattern.goldenPage,
|
|
@@ -754,15 +776,25 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
754
776
|
: best.pattern.goldenPage;
|
|
755
777
|
let suggestedSnippets = [...(best.pattern.suggestedSnippets ?? [])];
|
|
756
778
|
if (best.pattern.id === 'admin-list') {
|
|
757
|
-
const listCraft = resolveListCraftVariant(craftText);
|
|
779
|
+
const listCraft = density === 'compact' ? 'list-page-dense' : resolveListCraftVariant(craftText);
|
|
758
780
|
if (listCraft === 'list-page-dense') {
|
|
759
781
|
suggestedSnippets = suggestedSnippets.map((id) => id === 'list-filters-stack' ? 'list-filters-dense' : id);
|
|
760
782
|
}
|
|
761
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;
|
|
762
793
|
const result = {
|
|
763
794
|
intent: args.intent,
|
|
764
795
|
pageType: args.pageType,
|
|
765
796
|
style: args.style,
|
|
797
|
+
density,
|
|
766
798
|
matchedPattern: best.pattern.id,
|
|
767
799
|
title: locale === 'en-US' ? best.pattern.titleEn : best.pattern.title,
|
|
768
800
|
confidence: best.score,
|
|
@@ -776,6 +808,34 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
776
808
|
avoid: best.pattern.avoid,
|
|
777
809
|
alternatives: ranked.slice(1, 3).filter((item) => item.score > 0).map((item) => ({ id: item.pattern.id, score: item.score })),
|
|
778
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,
|
|
779
839
|
nextStep: (() => {
|
|
780
840
|
const goldenId = resolvedGoldenId;
|
|
781
841
|
const snippets = suggestedSnippets;
|
|
@@ -786,24 +846,31 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
786
846
|
: locale === 'en-US'
|
|
787
847
|
? ' Compose with get_page_snippet + recommend_component (relatedSnippets).'
|
|
788
848
|
: ' 用 get_page_snippet + recommend_component(relatedSnippets)拼装。';
|
|
789
|
-
const
|
|
790
|
-
? styleDirection.resolution === 'ask'
|
|
791
|
-
? ' Style is uncertain — ask the user for a short description or reference before inventing the look.'
|
|
792
|
-
: ` Follow styleDirection (${styleDirection.resolution}): “${styleDirection.summary || 'user/reference'}”; honor explicit user words over companions.`
|
|
793
|
-
: styleDirection.resolution === 'ask'
|
|
794
|
-
? ' 风格不确定 — 先请用户说明气质或贴参考,再写完整视觉。'
|
|
795
|
-
: ` 按 styleDirection(${styleDirection.resolution})落实「${styleDirection.summary || '用户参考/描述'}」;用户明确指定优先于 companion。`;
|
|
796
|
-
const goldenHint = goldenId
|
|
849
|
+
const briefHint = briefMapped
|
|
797
850
|
? locale === 'en-US'
|
|
798
|
-
?
|
|
799
|
-
:
|
|
800
|
-
:
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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}`;
|
|
807
874
|
})(),
|
|
808
875
|
};
|
|
809
876
|
if (args.includeScaffold) {
|
|
@@ -940,223 +1007,166 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
940
1007
|
const locale = resolveLocale(args.mode);
|
|
941
1008
|
const code = args.code || '';
|
|
942
1009
|
const suggestions = [];
|
|
943
|
-
|
|
1010
|
+
const push = (severity, standardId, type, messageZh, messageEn) => {
|
|
944
1011
|
suggestions.push({
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
: '标准建议:颜色优先使用 --m-* 设计令牌,而非 hex 色值。',
|
|
1012
|
+
severity,
|
|
1013
|
+
standardId,
|
|
1014
|
+
type,
|
|
1015
|
+
message: locale === 'en-US' ? messageEn : messageZh,
|
|
950
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.');
|
|
951
1020
|
}
|
|
952
1021
|
if (/\brgb\s*\(/i.test(code) && !/--m-/.test(code)) {
|
|
953
|
-
|
|
954
|
-
standardId: 'tokens',
|
|
955
|
-
type: 'raw-color',
|
|
956
|
-
message: locale === 'en-US'
|
|
957
|
-
? 'Standard: prefer --m-* tokens over raw rgb()/rgba() in page styles.'
|
|
958
|
-
: '标准建议:页面样式优先 --m-* 令牌,而非裸 rgb()/rgba()。',
|
|
959
|
-
});
|
|
1022
|
+
push('craft', 'tokens', 'raw-color', '标准建议:页面样式优先 --m-* 令牌,而非裸 rgb()/rgba()。', 'Standard: prefer --m-* tokens over raw rgb()/rgba() in page styles.');
|
|
960
1023
|
}
|
|
961
1024
|
if (/<MCard\b[^>]*>[\s\S]*?<MTable\b[^>]*\bbordered\b/i.test(code)) {
|
|
962
|
-
|
|
963
|
-
standardId: 'page-sections',
|
|
964
|
-
type: 'double-border',
|
|
965
|
-
message: locale === 'en-US'
|
|
966
|
-
? 'Standard: place bordered MTable directly in MPageContent instead of wrapping it with MCard.'
|
|
967
|
-
: '标准建议:bordered 的 MTable 直接放在 MPageContent 内,通常不必再包 MCard。',
|
|
968
|
-
});
|
|
1025
|
+
push('craft', 'page-sections', 'double-border', '标准建议:bordered 的 MTable 直接放在 MPageContent 内,通常不必再包 MCard。', 'Standard: place bordered MTable directly in MPageContent instead of wrapping it with MCard.');
|
|
969
1026
|
}
|
|
970
1027
|
if (/<MLayoutContent\b/i.test(code) && !/<MPageContent\b/i.test(code)) {
|
|
971
|
-
|
|
972
|
-
standardId: 'layout-shell',
|
|
973
|
-
type: 'missing-page-content',
|
|
974
|
-
message: locale === 'en-US'
|
|
975
|
-
? 'Standard: prefer MPageContent inside MLayoutContent for consistent page spacing.'
|
|
976
|
-
: '标准建议:MLayoutContent 内使用 MPageContent 统一页面间距。',
|
|
977
|
-
});
|
|
1028
|
+
push('craft', 'layout-shell', 'missing-page-content', '标准建议:MLayoutContent 内使用 MPageContent 统一页面间距。', 'Standard: prefer MPageContent inside MLayoutContent for consistent page spacing.');
|
|
978
1029
|
}
|
|
979
1030
|
if ((/\bpage[-_](?:list|form|dashboard)?[-_]?(?:filters|toolbar|content|header)\b/i.test(code) ||
|
|
980
1031
|
/class="[^"]*filters/i.test(code)) &&
|
|
981
1032
|
!/<MPage(?:Content|Filters|Toolbar|Header|Section|Stat)\b/i.test(code)) {
|
|
982
|
-
|
|
983
|
-
standardId: 'page-sections',
|
|
984
|
-
type: 'custom-page-css',
|
|
985
|
-
message: locale === 'en-US'
|
|
986
|
-
? 'Standard: prefer MPageFilters, MPageToolbar, MPageHeader, or MPageSection over custom page section CSS.'
|
|
987
|
-
: '标准建议:筛选/工具栏/表单区块优先 MPage* 组件,少写自定义 page section CSS。',
|
|
988
|
-
});
|
|
1033
|
+
push('craft', 'page-sections', 'custom-page-css', '标准建议:筛选/工具栏/表单区块优先 MPage* 组件,少写自定义 page section CSS。', 'Standard: prefer MPageFilters, MPageToolbar, MPageHeader, or MPageSection over custom page section CSS.');
|
|
989
1034
|
}
|
|
990
1035
|
if (/<MPageFilters\b/i.test(code) && /<MCard\b[^>]*>[\s\S]*?<MPageFilters\b/i.test(code)) {
|
|
991
|
-
|
|
992
|
-
standardId: 'page-sections',
|
|
993
|
-
type: 'redundant-wrapper',
|
|
994
|
-
message: locale === 'en-US'
|
|
995
|
-
? 'Standard: MPageFilters already provides a surface; wrapping it in MCard is usually redundant.'
|
|
996
|
-
: '标准建议:MPageFilters 已自带表面样式,通常不必再用 MCard 包裹。',
|
|
997
|
-
});
|
|
1036
|
+
push('craft', 'page-sections', 'redundant-wrapper', '标准建议:MPageFilters 已自带表面样式,通常不必再用 MCard 包裹。', 'Standard: MPageFilters already provides a surface; wrapping it in MCard is usually redundant.');
|
|
998
1037
|
}
|
|
999
1038
|
if (/<MPageSection\b[^>]*variant=["']form["']/i.test(code) &&
|
|
1000
1039
|
/<MCard\b[^>]*>[\s\S]*?<MPageSection\b/i.test(code)) {
|
|
1001
|
-
|
|
1002
|
-
standardId: 'page-sections',
|
|
1003
|
-
type: 'redundant-wrapper',
|
|
1004
|
-
message: locale === 'en-US'
|
|
1005
|
-
? 'Standard: MPageSection variant="form" already provides a surface; wrapping it in MCard is usually redundant.'
|
|
1006
|
-
: '标准建议:MPageSection variant="form" 已自带表面样式,通常不必再用 MCard 包裹。',
|
|
1007
|
-
});
|
|
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.');
|
|
1008
1041
|
}
|
|
1009
1042
|
if (/<MPageContent\b[^>]*>[\s\S]*?<MPageContent\b/i.test(code)) {
|
|
1010
|
-
|
|
1011
|
-
standardId: 'layout-shell',
|
|
1012
|
-
type: 'redundant-wrapper',
|
|
1013
|
-
message: locale === 'en-US'
|
|
1014
|
-
? 'Standard: prefer a single MPageContent shell for page padding and section gap.'
|
|
1015
|
-
: '标准建议:页面 padding 与区块间距优先只用一层 MPageContent。',
|
|
1016
|
-
});
|
|
1043
|
+
push('craft', 'layout-shell', 'redundant-wrapper', '标准建议:页面 padding 与区块间距优先只用一层 MPageContent。', 'Standard: prefer a single MPageContent shell for page padding and section gap.');
|
|
1017
1044
|
}
|
|
1018
1045
|
if (/<(?:div|section|main)\b[^>]*style=["'][^"']*(?:padding|gap)\s*:[^"']*["'][^>]*>\s*<(?:MPage(?:Content|Filters|Toolbar|Header|Section)|MTable)\b/i.test(code)) {
|
|
1019
|
-
|
|
1020
|
-
standardId: 'page-sections',
|
|
1021
|
-
type: 'redundant-wrapper',
|
|
1022
|
-
message: locale === 'en-US'
|
|
1023
|
-
? 'Standard: avoid extra padded wrappers around MPage* blocks; prefer the components’ built-in spacing.'
|
|
1024
|
-
: '标准建议:少在 MPage* 外包带 padding 的容器;优先使用组件自带间距。',
|
|
1025
|
-
});
|
|
1046
|
+
push('craft', 'page-sections', 'redundant-wrapper', '标准建议:少在 MPage* 外包带 padding 的容器;优先使用组件自带间距。', 'Standard: avoid extra padded wrappers around MPage* blocks; prefer the components’ built-in spacing.');
|
|
1026
1047
|
}
|
|
1027
1048
|
if (/style="[^"]*overflow(?:-y|-x)?\s*:\s*(auto|scroll)/i.test(code) ||
|
|
1028
1049
|
/<style\b[^>]*>[\s\S]*?overflow(?:-y|-x)?\s*:\s*(auto|scroll)/i.test(code)) {
|
|
1029
|
-
|
|
1030
|
-
standardId: 'scroll',
|
|
1031
|
-
type: 'native-scroll',
|
|
1032
|
-
message: locale === 'en-US'
|
|
1033
|
-
? 'Standard: prefer MScrollbar or MLayout scroll regions over overflow:auto/scroll in page styles.'
|
|
1034
|
-
: '标准建议:页面滚动优先 MLayout 主滚动或 MScrollbar,少写 overflow:auto/scroll。',
|
|
1035
|
-
});
|
|
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.');
|
|
1036
1051
|
}
|
|
1037
1052
|
if (/::-webkit-scrollbar|scrollbar-width\s*:/i.test(code)) {
|
|
1038
|
-
|
|
1039
|
-
standardId: 'scroll',
|
|
1040
|
-
type: 'custom-scrollbar-css',
|
|
1041
|
-
message: locale === 'en-US'
|
|
1042
|
-
? 'Standard: prefer MScrollbar or layout defaults over native scrollbar CSS in product code.'
|
|
1043
|
-
: '标准建议:业务代码优先 MScrollbar 或 Layout 默认滚动,少定制原生滚动条样式。',
|
|
1044
|
-
});
|
|
1053
|
+
push('craft', 'scroll', 'custom-scrollbar-css', '标准建议:业务代码优先 MScrollbar 或 Layout 默认滚动,少定制原生滚动条样式。', 'Standard: prefer MScrollbar or layout defaults over native scrollbar CSS in product code.');
|
|
1045
1054
|
}
|
|
1046
1055
|
if (/min-height\s*:\s*100vh/i.test(code) && !/<MLayout\b[^>]*fill-viewport/i.test(code)) {
|
|
1047
|
-
|
|
1048
|
-
standardId: 'layout-shell',
|
|
1049
|
-
type: 'viewport-height',
|
|
1050
|
-
message: locale === 'en-US'
|
|
1051
|
-
? 'Standard: prefer MLayout fillViewport over hand-written min-height:100vh page shells.'
|
|
1052
|
-
: '标准建议:整页高度优先 MLayout fillViewport,少写 min-height:100vh。',
|
|
1053
|
-
});
|
|
1056
|
+
push('craft', 'layout-shell', 'viewport-height', '标准建议:整页高度优先 MLayout fillViewport,少写 min-height:100vh。', 'Standard: prefer MLayout fillViewport over hand-written min-height:100vh page shells.');
|
|
1054
1057
|
}
|
|
1055
|
-
if ((/fill-viewport|fillViewport/i.test(code) || /<MLayout\b/i.test(code))
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
standardId: 'layout-shell',
|
|
1059
|
-
type: 'height-chain',
|
|
1060
|
-
message: locale === 'en-US'
|
|
1061
|
-
? 'Prefer layout-app-shell / golden pages: with MLayout, set html, body, #app { height: 100% } when the shell is not using fillViewport.'
|
|
1062
|
-
: '对齐 layout-app-shell / 黄金样例:未使用 fillViewport 时,配合 MLayout 设置 html, body, #app { height: 100% }。',
|
|
1063
|
-
});
|
|
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.');
|
|
1064
1061
|
}
|
|
1065
1062
|
if (/<MMenu\b/i.test(code)) {
|
|
1066
1063
|
const iconCount = (code.match(/\bicon\s*:/g) || []).length;
|
|
1067
1064
|
const looksLikeMenuModel = /MMenu[\s\S]{0,1200}\b(label|model)\s*[:=]/i.test(code) || /:model=/i.test(code);
|
|
1068
1065
|
if (looksLikeMenuModel && iconCount === 0) {
|
|
1069
|
-
|
|
1070
|
-
standardId: 'layout-shell',
|
|
1071
|
-
type: 'menu-missing-icons',
|
|
1072
|
-
message: locale === 'en-US'
|
|
1073
|
-
? 'Prefer golden / layout-app-shell menu items that include icon (e.g. user, shield, home).'
|
|
1074
|
-
: '对齐黄金样例 / layout-app-shell:侧栏菜单项带上 icon(如 user、shield、home)。',
|
|
1075
|
-
});
|
|
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).');
|
|
1076
1067
|
}
|
|
1077
1068
|
}
|
|
1078
1069
|
if (/<MTable\b[^>]*(?::data|v-bind:data)\s*=/i.test(code) || /<MTable\b[^>]*\sdata\s*=/i.test(code)) {
|
|
1079
|
-
|
|
1080
|
-
standardId: 'feedback',
|
|
1081
|
-
type: 'table-data-prop',
|
|
1082
|
-
message: locale === 'en-US'
|
|
1083
|
-
? 'Contract: MTable row data uses the rows prop (not data). See golden list-page / get_component Table.'
|
|
1084
|
-
: '契约:MTable 行数据使用 rows,不要用 data。见黄金样例 list-page / get_component Table。',
|
|
1085
|
-
});
|
|
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.');
|
|
1086
1071
|
}
|
|
1087
|
-
if (/
|
|
1088
|
-
&& /<MTable\b[^>]
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1072
|
+
if (/<MTable\b[^>]*\bvirtual\b/i.test(code)
|
|
1073
|
+
&& !(/<MTable\b[^>]*(?:max-height|maxHeight|table-height|tableHeight|fill)\b/i.test(code))) {
|
|
1074
|
+
push('contract', 'feedback', 'table-virtual-height', '契约:MTable virtual 需要明确高度(maxHeight / tableHeight / fill),否则视口无法计算。', 'Contract: MTable virtual requires an explicit height (maxHeight / tableHeight / fill).');
|
|
1075
|
+
}
|
|
1076
|
+
if (/vxe-table|VXETable|ElTableColumn|a-table\b/i.test(code) && /MTable|morya-ui/i.test(code)) {
|
|
1077
|
+
push('craft', 'feedback', 'table-prefer-mtable', '参考建议:管理列表优先用 MTable(virtual / resizable / filterable / edit-config 等企业能力)。仅当需要 Excel 级框选/透视时再引入专用网格库。', 'Advisory: prefer MTable for admin lists (virtual / resizable / filterable / edit-config). Reach for Excel-class grids only when area-select/pivot is required.');
|
|
1078
|
+
}
|
|
1079
|
+
if (/fill-viewport|fillViewport/i.test(code) &&
|
|
1080
|
+
/<MTable\b[^>]*\bpaginator\b/i.test(code) &&
|
|
1081
|
+
/<MPageContent\b/i.test(code) &&
|
|
1082
|
+
!(/<MPageContent\b[^>]*\bfill\b/i.test(code) && /<MTable\b[^>]*\bfill\b/i.test(code))) {
|
|
1083
|
+
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).');
|
|
1099
1084
|
}
|
|
1100
1085
|
if (/<MMessage\b[^>]*\bseverity\b/i.test(code)) {
|
|
1101
|
-
|
|
1102
|
-
standardId: 'feedback',
|
|
1103
|
-
type: 'mmessage-as-alert',
|
|
1104
|
-
message: locale === 'en-US'
|
|
1105
|
-
? 'Contract: <MMessage> is the message service host, not an inline alert. Use field errorMessage or a token-styled role="alert".'
|
|
1106
|
-
: '契约:<MMessage> 是 message 服务宿主,不是内嵌 Alert。表单常驻错误用字段 errorMessage 或 token 样式的 role="alert"。',
|
|
1107
|
-
});
|
|
1086
|
+
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".');
|
|
1108
1087
|
}
|
|
1109
1088
|
if (/toast\.(?:success|info|warn|warning|error)\s*\(\s*['"`][^'"`]+['"`]\s*\)/i.test(code)) {
|
|
1110
|
-
|
|
1111
|
-
standardId: 'feedback',
|
|
1112
|
-
type: 'toast-one-liner',
|
|
1113
|
-
message: locale === 'en-US'
|
|
1114
|
-
? 'Contract: one-line feedback should use message.*; toast is for summary + detail or async feel.'
|
|
1115
|
-
: '契约:单行操作回执用 message.*;toast 仅用于 summary + detail 或异步通知感。',
|
|
1116
|
-
});
|
|
1089
|
+
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.');
|
|
1117
1090
|
}
|
|
1118
1091
|
if (/#cell-status[\s\S]{0,500}<MTag\b/i.test(code) &&
|
|
1119
1092
|
!/#cell-status[\s\S]{0,500}<MStatus\b/i.test(code)) {
|
|
1120
|
-
|
|
1121
|
-
standardId: 'feedback',
|
|
1122
|
-
type: 'status-cell-tag',
|
|
1123
|
-
message: locale === 'en-US'
|
|
1124
|
-
? 'Prefer MStatus for business status in #cell-status; use MTag for categories / closable chips (see list-status-dot / detail-page).'
|
|
1125
|
-
: '行内业务状态优先 MStatus(#cell-status);分类或可关闭标签再用 MTag(见 list-status-dot / detail-page)。',
|
|
1126
|
-
});
|
|
1093
|
+
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).');
|
|
1127
1094
|
}
|
|
1128
1095
|
if (/<MTabs\b[^>]*(?::items|v-bind:items)\s*=/i.test(code) || /<MTabs\b[^>]*\sitems\s*=/i.test(code)) {
|
|
1129
|
-
|
|
1130
|
-
standardId: 'feedback',
|
|
1131
|
-
type: 'tabs-items-prop',
|
|
1132
|
-
message: locale === 'en-US'
|
|
1133
|
-
? 'Contract: MTabs uses the tabs prop with v-model (not items / value). See settings-page golden.'
|
|
1134
|
-
: '契约:MTabs 使用 v-model + tabs(不是 items / value)。见黄金样例 settings-page。',
|
|
1135
|
-
});
|
|
1096
|
+
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.');
|
|
1136
1097
|
}
|
|
1137
1098
|
if (/<MStepper\b[^>]*(?::items|v-bind:items)\s*=/i.test(code) || /<MStepper\b[^>]*\sitems\s*=/i.test(code)) {
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1099
|
+
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.');
|
|
1100
|
+
}
|
|
1101
|
+
// Craft: placeholder / lorem copy
|
|
1102
|
+
if (/(?:title|description|label|placeholder)\s*=\s*["'](?:示例|样例|Name|No data|暂无数据|lorem|placeholder)["']/i.test(code) ||
|
|
1103
|
+
/>\s*(?:示例|样例|No data|暂无数据|Lorem ipsum)\s*</i.test(code)) {
|
|
1104
|
+
push('craft', 'craft', 'placeholder-copy', '工艺建议:用领域真实文案,避免「示例 / Name / No data / 暂无数据」。', 'Craft: use domain-real copy — avoid “示例 / Name / No data / 暂无数据”.');
|
|
1105
|
+
}
|
|
1106
|
+
// Craft: more than one filled primary in viewport (heuristic)
|
|
1107
|
+
{
|
|
1108
|
+
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);
|
|
1109
|
+
// Count explicit severity="primary" without text/outlined
|
|
1110
|
+
const explicitPrimary = (code.match(/<MButton\b(?![^>]*(?:\btext\b|\boutlined\b))[^>]*severity\s*=\s*["']primary["']/gi) || []).length;
|
|
1111
|
+
if (explicitPrimary > 1) {
|
|
1112
|
+
push('craft', 'craft', 'multiple-primary', '工艺建议:主视口内通常只需一个 filled primary 按钮。', 'Craft: usually only one filled primary button in the main viewport.');
|
|
1113
|
+
}
|
|
1114
|
+
void primaryFilled;
|
|
1115
|
+
}
|
|
1116
|
+
// Craft: icon-only without accessible name
|
|
1117
|
+
{
|
|
1118
|
+
const iconOnlyRe = /<MButton\b([^>]*)\b(?:icon-only|iconOnly)\b([^>]*)\/?>/gi;
|
|
1119
|
+
let m = iconOnlyRe.exec(code);
|
|
1120
|
+
while (m !== null) {
|
|
1121
|
+
const attrs = `${m[1] || ''}${m[2] || ''}`;
|
|
1122
|
+
if (!/\baria-label\b|\bariaLabel\b|\baria-labelledby\b/i.test(attrs)) {
|
|
1123
|
+
push('craft', 'a11y', 'icon-only-missing-aria', '工艺建议:仅图标的 MButton 需要 aria-label / ariaLabel。', 'Craft: icon-only MButton needs aria-label / ariaLabel.');
|
|
1124
|
+
break;
|
|
1125
|
+
}
|
|
1126
|
+
m = iconOnlyRe.exec(code);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
// Brief fidelity: required blocks / components
|
|
1130
|
+
const mapped = args.brief
|
|
1131
|
+
? mapReferenceBrief(args.brief)
|
|
1132
|
+
: null;
|
|
1133
|
+
const requiredBlocks = [
|
|
1134
|
+
...(args.requiredBlocks || []),
|
|
1135
|
+
...(mapped?.validationHints.requiredBlocks || []),
|
|
1136
|
+
];
|
|
1137
|
+
const requiredComponents = [
|
|
1138
|
+
...(args.requiredComponents || []),
|
|
1139
|
+
...(mapped?.validationHints.requiredComponents || []),
|
|
1140
|
+
];
|
|
1141
|
+
for (const block of requiredBlocks) {
|
|
1142
|
+
if (!codeCoversBlock(code, block)) {
|
|
1143
|
+
push('contract', 'fidelity', 'brief-missing-block', `还原度:参考要求的区块「${block}」未在代码中检测到。`, `Fidelity: required reference block “${block}” was not detected in the code.`);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
for (const component of requiredComponents) {
|
|
1147
|
+
if (!codeCoversComponent(code, component)) {
|
|
1148
|
+
push('contract', 'fidelity', 'brief-missing-component', `还原度:参考要求的组件 M${component.replace(/^M/, '')} 未出现。`, `Fidelity: required component M${component.replace(/^M/, '')} is missing.`);
|
|
1149
|
+
}
|
|
1145
1150
|
}
|
|
1151
|
+
const contract = suggestions.filter((item) => item.severity === 'contract');
|
|
1152
|
+
const craft = suggestions.filter((item) => item.severity === 'craft');
|
|
1153
|
+
const ok = contract.length === 0;
|
|
1146
1154
|
return textResult({
|
|
1147
|
-
ok
|
|
1155
|
+
ok,
|
|
1148
1156
|
advisory: true,
|
|
1149
1157
|
summary: locale === 'en-US'
|
|
1150
1158
|
? suggestions.length === 0
|
|
1151
1159
|
? 'No deviations from page standards detected.'
|
|
1152
|
-
: `${
|
|
1160
|
+
: `${contract.length} contract issue(s), ${craft.length} craft suggestion(s).`
|
|
1153
1161
|
: suggestions.length === 0
|
|
1154
1162
|
? '未发现与页面标准的明显偏差。'
|
|
1155
|
-
: `${
|
|
1163
|
+
: `${contract.length} 条契约问题,${craft.length} 条工艺建议。`,
|
|
1164
|
+
contract,
|
|
1165
|
+
craft,
|
|
1156
1166
|
suggestions,
|
|
1157
1167
|
nextStep: locale === 'en-US'
|
|
1158
|
-
? '
|
|
1159
|
-
: '
|
|
1168
|
+
? '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.'
|
|
1169
|
+
: '契约问题交付前必须修好;工艺建议可说明后放过。brief 与 map_reference / recommend_page 保持一致。另见 get_design_rules / get_style_shells。',
|
|
1160
1170
|
});
|
|
1161
1171
|
}
|
|
1162
1172
|
function getDesignRules(args = {}) {
|
|
@@ -1165,20 +1175,29 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
1165
1175
|
resolution: locale === 'en-US' ? styleResolution.priorityEn : styleResolution.priorityZh,
|
|
1166
1176
|
hardRules: locale === 'en-US' ? styleResolution.hardRulesEn : styleResolution.hardRulesZh,
|
|
1167
1177
|
note: locale === 'en-US'
|
|
1168
|
-
? 'No named style-preset catalog. Follow user reference/description; ask when uncertain.'
|
|
1169
|
-
: '
|
|
1178
|
+
? '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.'
|
|
1179
|
+
: '没有命名风格预设表。跟用户参考/描述;不确定就先问。还原用 map_reference;可粘贴签名壳见 get_style_shells。',
|
|
1170
1180
|
};
|
|
1181
|
+
const shells = styleShells.map((shell) => ({
|
|
1182
|
+
id: shell.id,
|
|
1183
|
+
surface: shell.surface,
|
|
1184
|
+
title: locale === 'en-US' ? shell.titleEn : shell.titleZh,
|
|
1185
|
+
signature: locale === 'en-US' ? shell.signatureEn : shell.signatureZh,
|
|
1186
|
+
when: locale === 'en-US' ? shell.whenEn : shell.whenZh,
|
|
1187
|
+
}));
|
|
1171
1188
|
if (locale === 'zh-CN') {
|
|
1172
1189
|
return textResult({
|
|
1173
1190
|
meta: designRules.meta,
|
|
1174
1191
|
standards: designRules.standards,
|
|
1175
1192
|
styles,
|
|
1193
|
+
shells,
|
|
1176
1194
|
tokens: designRules.tokens,
|
|
1177
1195
|
composition: designRules.composition,
|
|
1178
1196
|
actions: designRules.actions,
|
|
1179
1197
|
status: designRules.status,
|
|
1180
1198
|
feedback: designRules.feedback,
|
|
1181
1199
|
global: designRules.global,
|
|
1200
|
+
themeOverride: '品牌差异优先在应用入口覆盖 --m-color-primary / 字体等主题变量,页面 CSS 只用 --m-*;见 DESIGN.md「主题覆盖」。',
|
|
1182
1201
|
});
|
|
1183
1202
|
}
|
|
1184
1203
|
return textResult({
|
|
@@ -1191,6 +1210,7 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
1191
1210
|
mcp: item.mcp,
|
|
1192
1211
|
})),
|
|
1193
1212
|
styles,
|
|
1213
|
+
shells,
|
|
1194
1214
|
tokens: {
|
|
1195
1215
|
colors: ['--m-color-primary', '--m-color-surface', '--m-color-text', '--m-color-border'],
|
|
1196
1216
|
spacing: '--m-space-*',
|
|
@@ -1225,6 +1245,7 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
1225
1245
|
},
|
|
1226
1246
|
doc: 'design-kit/.agents/skills/morya-ui-pages/references/feedback.md',
|
|
1227
1247
|
},
|
|
1248
|
+
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.',
|
|
1228
1249
|
global: [
|
|
1229
1250
|
'Prefer library components and --m-* tokens.',
|
|
1230
1251
|
'Default action feedback to message; prefer message over toast for single-line-only text.',
|
|
@@ -1233,9 +1254,81 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
1233
1254
|
'Overlays teleport to body by default; only change appendTo for a clear layout constraint.',
|
|
1234
1255
|
'Prefer documented component variants over deep CSS overrides.',
|
|
1235
1256
|
'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.',
|
|
1257
|
+
'Pass brief to map_reference / recommend_page / validate_page for fidelity.',
|
|
1236
1258
|
],
|
|
1237
1259
|
});
|
|
1238
1260
|
}
|
|
1261
|
+
function mapReference(args) {
|
|
1262
|
+
const locale = resolveLocale(args.mode);
|
|
1263
|
+
const mapped = mapReferenceBrief(args);
|
|
1264
|
+
const shell = mapped.suggestedShellId ? findStyleShell(mapped.suggestedShellId) : undefined;
|
|
1265
|
+
return textResult({
|
|
1266
|
+
surface: mapped.surface,
|
|
1267
|
+
density: mapped.density,
|
|
1268
|
+
styleSummary: mapped.styleSummary,
|
|
1269
|
+
suggestedSnippets: mapped.suggestedSnippets,
|
|
1270
|
+
suggestedShellId: mapped.suggestedShellId,
|
|
1271
|
+
mapping: mapped.mapping.map((item) => ({
|
|
1272
|
+
block: item.block,
|
|
1273
|
+
snippetId: item.snippetId,
|
|
1274
|
+
notes: locale === 'en-US' ? item.notesEn : item.notesZh,
|
|
1275
|
+
})),
|
|
1276
|
+
tokenRoles: mapped.tokenRoles.map((item) => ({
|
|
1277
|
+
role: item.role,
|
|
1278
|
+
token: item.token,
|
|
1279
|
+
notes: locale === 'en-US' ? item.notesEn : item.notesZh,
|
|
1280
|
+
})),
|
|
1281
|
+
craftCues: locale === 'en-US' ? mapped.craftCuesEn : mapped.craftCuesZh,
|
|
1282
|
+
validationHints: mapped.validationHints,
|
|
1283
|
+
styleShell: shell
|
|
1284
|
+
? {
|
|
1285
|
+
id: shell.id,
|
|
1286
|
+
title: locale === 'en-US' ? shell.titleEn : shell.titleZh,
|
|
1287
|
+
signature: locale === 'en-US' ? shell.signatureEn : shell.signatureZh,
|
|
1288
|
+
css: shell.css,
|
|
1289
|
+
htmlHint: locale === 'en-US' ? shell.htmlHintEn : shell.htmlHintZh,
|
|
1290
|
+
}
|
|
1291
|
+
: null,
|
|
1292
|
+
nextStep: locale === 'en-US'
|
|
1293
|
+
? 'Compose get_page_snippet for each mapping.snippetId; apply styleShell.css when needed; pass the same brief to validate_page.'
|
|
1294
|
+
: '按 mapping.snippetId 逐个 get_page_snippet;需要时粘贴 styleShell.css;同一 brief 交给 validate_page。',
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
function getStyleShells(args = {}) {
|
|
1298
|
+
const locale = resolveLocale(args.mode);
|
|
1299
|
+
if (args.shell) {
|
|
1300
|
+
const shell = findStyleShell(args.shell);
|
|
1301
|
+
if (!shell) {
|
|
1302
|
+
return textResult({
|
|
1303
|
+
error: `Style shell not found: ${args.shell}`,
|
|
1304
|
+
available: styleShells.map((item) => item.id),
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
return textResult({
|
|
1308
|
+
id: shell.id,
|
|
1309
|
+
surface: shell.surface,
|
|
1310
|
+
title: locale === 'en-US' ? shell.titleEn : shell.titleZh,
|
|
1311
|
+
signature: locale === 'en-US' ? shell.signatureEn : shell.signatureZh,
|
|
1312
|
+
when: locale === 'en-US' ? shell.whenEn : shell.whenZh,
|
|
1313
|
+
htmlHint: locale === 'en-US' ? shell.htmlHintEn : shell.htmlHintZh,
|
|
1314
|
+
css: shell.css,
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
const items = listStyleShells(args.surface).map((shell) => ({
|
|
1318
|
+
id: shell.id,
|
|
1319
|
+
surface: shell.surface,
|
|
1320
|
+
title: locale === 'en-US' ? shell.titleEn : shell.titleZh,
|
|
1321
|
+
signature: locale === 'en-US' ? shell.signatureEn : shell.signatureZh,
|
|
1322
|
+
when: locale === 'en-US' ? shell.whenEn : shell.whenZh,
|
|
1323
|
+
}));
|
|
1324
|
+
return textResult({
|
|
1325
|
+
total: items.length,
|
|
1326
|
+
items,
|
|
1327
|
+
nextStep: locale === 'en-US'
|
|
1328
|
+
? 'Pass shell id to get_style_shells({ shell }) for pasteable CSS. Brand color via theme --m-color-primary, not page hex.'
|
|
1329
|
+
: '传 shell id 给 get_style_shells({ shell }) 取可粘贴 CSS。品牌色走主题 --m-color-primary,不要在页面写 hex。',
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1239
1332
|
function getStyleDirection(args = {}) {
|
|
1240
1333
|
const locale = resolveLocale(args.mode);
|
|
1241
1334
|
const direction = resolveStyleDirection({ intent: args.intent, style: args.style });
|
|
@@ -1468,8 +1561,10 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
1468
1561
|
'list_patterns',
|
|
1469
1562
|
'get_pattern',
|
|
1470
1563
|
'recommend_page',
|
|
1564
|
+
'map_reference',
|
|
1471
1565
|
'get_design_rules',
|
|
1472
1566
|
'get_style_direction',
|
|
1567
|
+
'get_style_shells',
|
|
1473
1568
|
'recommend_component',
|
|
1474
1569
|
'list_golden_pages',
|
|
1475
1570
|
'get_golden_page',
|
|
@@ -1492,8 +1587,10 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
1492
1587
|
listPatterns,
|
|
1493
1588
|
getPattern,
|
|
1494
1589
|
recommendPage,
|
|
1590
|
+
mapReference,
|
|
1495
1591
|
getDesignRules,
|
|
1496
1592
|
getStyleDirection,
|
|
1593
|
+
getStyleShells,
|
|
1497
1594
|
recommendComponent,
|
|
1498
1595
|
listGoldenPageCatalog,
|
|
1499
1596
|
getGoldenPage,
|