@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
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured reference brief → snippet / shell / token mapping for page fidelity.
|
|
3
|
+
* Agents describe what the reference shows; this module returns how to rebuild it with M*.
|
|
4
|
+
*/
|
|
5
|
+
export type BriefSurface = 'ops-list' | 'ops-form' | 'ops-dashboard' | 'ops-detail' | 'ops-settings' | 'account' | 'flow-empty' | 'flow-result' | 'flow-wizard' | 'express' | 'overlay-dialog' | 'overlay-drawer';
|
|
6
|
+
export type BriefDensity = 'default' | 'compact' | 'spacious';
|
|
7
|
+
export interface ReferenceBriefInput {
|
|
8
|
+
/** Free-text description of the reference (screenshot / mock / “like X”). */
|
|
9
|
+
description?: string;
|
|
10
|
+
/** Product / page intent. */
|
|
11
|
+
intent?: string;
|
|
12
|
+
surface?: BriefSurface | string;
|
|
13
|
+
density?: BriefDensity | string;
|
|
14
|
+
/** Named blocks visible in the reference (filters, table, kpi, brand-panel, …). */
|
|
15
|
+
requiredBlocks?: string[];
|
|
16
|
+
/** Component ids or export names that must appear (Table, MStatus, …). */
|
|
17
|
+
requiredComponents?: string[];
|
|
18
|
+
/** One primary CTA label if visible in the reference. */
|
|
19
|
+
primaryAction?: string;
|
|
20
|
+
/** Optional free-text style direction (same as recommend_page.style). */
|
|
21
|
+
style?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface BriefMappingItem {
|
|
24
|
+
block: string;
|
|
25
|
+
snippetId: string;
|
|
26
|
+
notesZh: string;
|
|
27
|
+
notesEn: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ReferenceBriefResult {
|
|
30
|
+
surface: BriefSurface;
|
|
31
|
+
density: BriefDensity;
|
|
32
|
+
styleSummary: string | null;
|
|
33
|
+
suggestedSnippets: string[];
|
|
34
|
+
suggestedShellId: string | null;
|
|
35
|
+
mapping: BriefMappingItem[];
|
|
36
|
+
tokenRoles: Array<{
|
|
37
|
+
role: string;
|
|
38
|
+
token: string;
|
|
39
|
+
notesZh: string;
|
|
40
|
+
notesEn: string;
|
|
41
|
+
}>;
|
|
42
|
+
craftCuesZh: string[];
|
|
43
|
+
craftCuesEn: string[];
|
|
44
|
+
validationHints: {
|
|
45
|
+
requiredBlocks: string[];
|
|
46
|
+
requiredComponents: string[];
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Map a reference brief to snippets, shell, and validation hints.
|
|
51
|
+
*/
|
|
52
|
+
export declare function mapReferenceBrief(input: ReferenceBriefInput): ReferenceBriefResult;
|
|
53
|
+
/** Detect whether generated code covers a required block (heuristic). */
|
|
54
|
+
export declare function codeCoversBlock(code: string, block: string): boolean;
|
|
55
|
+
export declare function codeCoversComponent(code: string, component: string): boolean;
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured reference brief → snippet / shell / token mapping for page fidelity.
|
|
3
|
+
* Agents describe what the reference shows; this module returns how to rebuild it with M*.
|
|
4
|
+
*/
|
|
5
|
+
const SURFACE_ALIASES = {
|
|
6
|
+
list: 'ops-list',
|
|
7
|
+
'admin-list': 'ops-list',
|
|
8
|
+
table: 'ops-list',
|
|
9
|
+
form: 'ops-form',
|
|
10
|
+
'form-page': 'ops-form',
|
|
11
|
+
dashboard: 'ops-dashboard',
|
|
12
|
+
detail: 'ops-detail',
|
|
13
|
+
settings: 'ops-settings',
|
|
14
|
+
login: 'account',
|
|
15
|
+
auth: 'account',
|
|
16
|
+
account: 'account',
|
|
17
|
+
empty: 'flow-empty',
|
|
18
|
+
'empty-state': 'flow-empty',
|
|
19
|
+
result: 'flow-result',
|
|
20
|
+
wizard: 'flow-wizard',
|
|
21
|
+
landing: 'express',
|
|
22
|
+
marketing: 'express',
|
|
23
|
+
express: 'express',
|
|
24
|
+
dialog: 'overlay-dialog',
|
|
25
|
+
'form-in-dialog': 'overlay-dialog',
|
|
26
|
+
drawer: 'overlay-drawer',
|
|
27
|
+
'form-in-drawer': 'overlay-drawer',
|
|
28
|
+
};
|
|
29
|
+
const BLOCK_TO_SNIPPET = {
|
|
30
|
+
filters: {
|
|
31
|
+
snippetId: 'list-filters-stack',
|
|
32
|
+
notesZh: '筛选区用 MPageFilters;搜索约 14rem,枚举约 10–12rem',
|
|
33
|
+
notesEn: 'Filters via MPageFilters; search ~14rem, selects ~10–12rem',
|
|
34
|
+
},
|
|
35
|
+
'filters-dense': {
|
|
36
|
+
snippetId: 'list-filters-dense',
|
|
37
|
+
notesZh: '高密列表用 list-filters-dense(plain + small)',
|
|
38
|
+
notesEn: 'Dense lists use list-filters-dense (plain + small)',
|
|
39
|
+
},
|
|
40
|
+
chips: {
|
|
41
|
+
snippetId: 'list-filter-chips',
|
|
42
|
+
notesZh: '已选条件用 MPageFilterChips + closable MTag',
|
|
43
|
+
notesEn: 'Active filters via MPageFilterChips + closable MTag',
|
|
44
|
+
},
|
|
45
|
+
header: {
|
|
46
|
+
snippetId: 'page-header-actions',
|
|
47
|
+
notesZh: '页头身份 + 视口内唯一 filled primary',
|
|
48
|
+
notesEn: 'Page identity + one filled primary in the viewport',
|
|
49
|
+
},
|
|
50
|
+
toolbar: {
|
|
51
|
+
snippetId: 'list-batch-toolbar',
|
|
52
|
+
notesZh: '批量操作放 MPageToolbar,不要塞进页头',
|
|
53
|
+
notesEn: 'Batch actions in MPageToolbar, not the page header',
|
|
54
|
+
},
|
|
55
|
+
table: {
|
|
56
|
+
snippetId: 'list-table',
|
|
57
|
+
notesZh: 'MTable + rows;状态列 MStatus;数字列 align end;空态 MEmpty',
|
|
58
|
+
notesEn: 'MTable + rows; status → MStatus; numbers align end; empty → MEmpty',
|
|
59
|
+
},
|
|
60
|
+
'row-actions': {
|
|
61
|
+
snippetId: 'list-row-actions',
|
|
62
|
+
notesZh: '行操作用 small + text',
|
|
63
|
+
notesEn: 'Row actions: size="small" + text',
|
|
64
|
+
},
|
|
65
|
+
status: {
|
|
66
|
+
snippetId: 'list-status-dot',
|
|
67
|
+
notesZh: '业务状态用 MStatus,不要默认 MTag',
|
|
68
|
+
notesEn: 'Business status uses MStatus, not default MTag',
|
|
69
|
+
},
|
|
70
|
+
empty: {
|
|
71
|
+
snippetId: 'empty-block',
|
|
72
|
+
notesZh: '空态带下一步 CTA',
|
|
73
|
+
notesEn: 'Empty state with a next-step CTA',
|
|
74
|
+
},
|
|
75
|
+
'form-header': {
|
|
76
|
+
snippetId: 'form-header',
|
|
77
|
+
notesZh: '窄宽表单页标题',
|
|
78
|
+
notesEn: 'Narrow form page header',
|
|
79
|
+
},
|
|
80
|
+
'form-body': {
|
|
81
|
+
snippetId: 'form-body',
|
|
82
|
+
notesZh: 'MPageSection form + MForm;左标签时设 label-width',
|
|
83
|
+
notesEn: 'MPageSection form + MForm; set label-width for left labels',
|
|
84
|
+
},
|
|
85
|
+
'form-actions': {
|
|
86
|
+
snippetId: 'form-actions',
|
|
87
|
+
notesZh: '保存 primary + loading;取消 secondary',
|
|
88
|
+
notesEn: 'Save primary + loading; cancel secondary',
|
|
89
|
+
},
|
|
90
|
+
kpi: {
|
|
91
|
+
snippetId: 'dashboard-kpi-grid',
|
|
92
|
+
notesZh: 'MPageStat 带 trend / trendDirection / trendSeverity',
|
|
93
|
+
notesEn: 'MPageStat with trend / trendDirection / trendSeverity',
|
|
94
|
+
},
|
|
95
|
+
chart: {
|
|
96
|
+
snippetId: 'dashboard-chart-card',
|
|
97
|
+
notesZh: '图表区 MCard + MSkeleton / MEmpty 占位',
|
|
98
|
+
notesEn: 'Chart area: MCard + MSkeleton / MEmpty placeholder',
|
|
99
|
+
},
|
|
100
|
+
recent: {
|
|
101
|
+
snippetId: 'dashboard-recent-table',
|
|
102
|
+
notesZh: '卡片内小表 size="small"',
|
|
103
|
+
notesEn: 'In-card table with size="small"',
|
|
104
|
+
},
|
|
105
|
+
detail: {
|
|
106
|
+
snippetId: 'detail-toolbar',
|
|
107
|
+
notesZh: '详情顶栏:状态 + 唯一编辑 primary',
|
|
108
|
+
notesEn: 'Detail toolbar: status + one edit primary',
|
|
109
|
+
},
|
|
110
|
+
result: {
|
|
111
|
+
snippetId: 'result-block',
|
|
112
|
+
notesZh: '流程终点用 MResult',
|
|
113
|
+
notesEn: 'Terminal outcomes use MResult',
|
|
114
|
+
},
|
|
115
|
+
wizard: {
|
|
116
|
+
snippetId: 'wizard-steps',
|
|
117
|
+
notesZh: '分步用 MStepper + steps',
|
|
118
|
+
notesEn: 'Wizard uses MStepper + steps',
|
|
119
|
+
},
|
|
120
|
+
auth: {
|
|
121
|
+
snippetId: 'auth-split-shell',
|
|
122
|
+
notesZh: '登录分栏壳;品牌面只用 --m-*',
|
|
123
|
+
notesEn: 'Auth split shell; brand panel uses --m-* only',
|
|
124
|
+
},
|
|
125
|
+
shell: {
|
|
126
|
+
snippetId: 'layout-app-shell',
|
|
127
|
+
notesZh: '后台骨架 MLayout + 菜单 icon',
|
|
128
|
+
notesEn: 'Admin shell: MLayout + menu icons',
|
|
129
|
+
},
|
|
130
|
+
dialog: {
|
|
131
|
+
snippetId: 'form-in-dialog',
|
|
132
|
+
notesZh: '短 CRUD 弹窗表单',
|
|
133
|
+
notesEn: 'Short CRUD dialog form',
|
|
134
|
+
},
|
|
135
|
+
drawer: {
|
|
136
|
+
snippetId: 'form-in-drawer',
|
|
137
|
+
notesZh: '侧栏编辑用 Drawer',
|
|
138
|
+
notesEn: 'Side editing uses Drawer',
|
|
139
|
+
},
|
|
140
|
+
confirm: {
|
|
141
|
+
snippetId: 'confirm-delete',
|
|
142
|
+
notesZh: '删除确认',
|
|
143
|
+
notesEn: 'Delete confirmation',
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
const SURFACE_DEFAULT_BLOCKS = {
|
|
147
|
+
'ops-list': ['shell', 'header', 'filters', 'table', 'status', 'row-actions'],
|
|
148
|
+
'ops-form': ['form-header', 'form-body', 'form-actions'],
|
|
149
|
+
'ops-dashboard': ['header', 'kpi', 'chart', 'recent'],
|
|
150
|
+
'ops-detail': ['detail', 'dialog'],
|
|
151
|
+
'ops-settings': ['form-header', 'form-body', 'form-actions'],
|
|
152
|
+
account: ['auth'],
|
|
153
|
+
'flow-empty': ['empty'],
|
|
154
|
+
'flow-result': ['result'],
|
|
155
|
+
'flow-wizard': ['wizard', 'form-body', 'form-actions'],
|
|
156
|
+
express: ['header'],
|
|
157
|
+
'overlay-dialog': ['dialog'],
|
|
158
|
+
'overlay-drawer': ['drawer'],
|
|
159
|
+
};
|
|
160
|
+
const SURFACE_SHELL = {
|
|
161
|
+
'ops-list': 'ops-quiet',
|
|
162
|
+
'ops-form': 'ops-quiet',
|
|
163
|
+
'ops-dashboard': 'ops-quiet',
|
|
164
|
+
'ops-detail': 'ops-quiet',
|
|
165
|
+
'ops-settings': 'ops-quiet',
|
|
166
|
+
account: 'account-split',
|
|
167
|
+
'flow-empty': 'flow-empty-frame',
|
|
168
|
+
'flow-result': 'flow-result-hero',
|
|
169
|
+
'flow-wizard': 'ops-quiet',
|
|
170
|
+
express: 'express-hero',
|
|
171
|
+
'overlay-dialog': null,
|
|
172
|
+
'overlay-drawer': null,
|
|
173
|
+
};
|
|
174
|
+
const TOKEN_ROLES = [
|
|
175
|
+
{
|
|
176
|
+
role: 'page-bg',
|
|
177
|
+
token: '--m-color-surface',
|
|
178
|
+
notesZh: '页面底',
|
|
179
|
+
notesEn: 'Page background',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
role: 'text',
|
|
183
|
+
token: '--m-color-text',
|
|
184
|
+
notesZh: '正文',
|
|
185
|
+
notesEn: 'Body text',
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
role: 'muted',
|
|
189
|
+
token: '--m-color-text-muted',
|
|
190
|
+
notesZh: '次要说明',
|
|
191
|
+
notesEn: 'Secondary copy',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
role: 'border',
|
|
195
|
+
token: '--m-color-border',
|
|
196
|
+
notesZh: '分隔 / 边框',
|
|
197
|
+
notesEn: 'Dividers / borders',
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
role: 'brand',
|
|
201
|
+
token: '--m-color-primary',
|
|
202
|
+
notesZh: '主操作与品牌强调;品牌差异优先改主题,不在页面写死色',
|
|
203
|
+
notesEn: 'Primary actions and brand accent; prefer theme overrides over page hex',
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
role: 'danger',
|
|
207
|
+
token: '--m-color-danger',
|
|
208
|
+
notesZh: '破坏性操作',
|
|
209
|
+
notesEn: 'Destructive actions',
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
role: 'space',
|
|
213
|
+
token: '--m-space-4 / --m-space-6',
|
|
214
|
+
notesZh: '区块间距',
|
|
215
|
+
notesEn: 'Section spacing',
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
role: 'radius',
|
|
219
|
+
token: '--m-radius-md',
|
|
220
|
+
notesZh: '签名面圆角',
|
|
221
|
+
notesEn: 'Signature surface radius',
|
|
222
|
+
},
|
|
223
|
+
];
|
|
224
|
+
function normalizeSurface(raw, description, intent) {
|
|
225
|
+
const key = (raw || '').trim().toLowerCase();
|
|
226
|
+
if (key && SURFACE_ALIASES[key])
|
|
227
|
+
return SURFACE_ALIASES[key];
|
|
228
|
+
if (key && Object.keys(SURFACE_DEFAULT_BLOCKS).includes(key)) {
|
|
229
|
+
return key;
|
|
230
|
+
}
|
|
231
|
+
const blob = `${description} ${intent}`.toLowerCase();
|
|
232
|
+
if (/登录|注册|auth|login|sign\s*in/.test(blob))
|
|
233
|
+
return 'account';
|
|
234
|
+
if (/落地|营销|landing|pricing|官网|hero/.test(blob))
|
|
235
|
+
return 'express';
|
|
236
|
+
if (/仪表盘|dashboard|kpi|监控|工作台/.test(blob))
|
|
237
|
+
return 'ops-dashboard';
|
|
238
|
+
if (/向导|分步|wizard|stepper/.test(blob))
|
|
239
|
+
return 'flow-wizard';
|
|
240
|
+
if (/空状态|无数据|empty/.test(blob))
|
|
241
|
+
return 'flow-empty';
|
|
242
|
+
if (/结果|403|404|500|result/.test(blob))
|
|
243
|
+
return 'flow-result';
|
|
244
|
+
if (/抽屉|drawer/.test(blob))
|
|
245
|
+
return 'overlay-drawer';
|
|
246
|
+
if (/弹窗|dialog|modal/.test(blob))
|
|
247
|
+
return 'overlay-dialog';
|
|
248
|
+
if (/详情|detail|概览/.test(blob))
|
|
249
|
+
return 'ops-detail';
|
|
250
|
+
if (/设置|settings|偏好/.test(blob))
|
|
251
|
+
return 'ops-settings';
|
|
252
|
+
if (/表单|新建|编辑|form|create|edit/.test(blob) && !/列表|list|table/.test(blob))
|
|
253
|
+
return 'ops-form';
|
|
254
|
+
return 'ops-list';
|
|
255
|
+
}
|
|
256
|
+
function normalizeDensity(raw, blob) {
|
|
257
|
+
const d = (raw || '').trim().toLowerCase();
|
|
258
|
+
if (d === 'compact' || d === 'dense' || d === '高密' || d === '紧凑')
|
|
259
|
+
return 'compact';
|
|
260
|
+
if (d === 'spacious' || d === 'loose' || d === '宽松' || d === '留白')
|
|
261
|
+
return 'spacious';
|
|
262
|
+
if (/dense|compact|高密|紧凑|工具台/.test(blob))
|
|
263
|
+
return 'compact';
|
|
264
|
+
if (/spacious|宽松|留白|宽松/.test(blob))
|
|
265
|
+
return 'spacious';
|
|
266
|
+
return 'default';
|
|
267
|
+
}
|
|
268
|
+
function normalizeBlockId(block) {
|
|
269
|
+
const b = block.trim().toLowerCase();
|
|
270
|
+
const aliases = {
|
|
271
|
+
filter: 'filters',
|
|
272
|
+
筛选: 'filters',
|
|
273
|
+
高级筛选: 'filters',
|
|
274
|
+
search: 'filters',
|
|
275
|
+
已选: 'chips',
|
|
276
|
+
'filter-chips': 'chips',
|
|
277
|
+
页头: 'header',
|
|
278
|
+
title: 'header',
|
|
279
|
+
批量: 'toolbar',
|
|
280
|
+
表格: 'table',
|
|
281
|
+
列表: 'table',
|
|
282
|
+
状态: 'status',
|
|
283
|
+
行操作: 'row-actions',
|
|
284
|
+
actions: 'row-actions',
|
|
285
|
+
空态: 'empty',
|
|
286
|
+
表单: 'form-body',
|
|
287
|
+
保存: 'form-actions',
|
|
288
|
+
指标: 'kpi',
|
|
289
|
+
stats: 'kpi',
|
|
290
|
+
图表: 'chart',
|
|
291
|
+
最近: 'recent',
|
|
292
|
+
登录: 'auth',
|
|
293
|
+
brand: 'auth',
|
|
294
|
+
品牌: 'auth',
|
|
295
|
+
骨架: 'shell',
|
|
296
|
+
layout: 'shell',
|
|
297
|
+
确认: 'confirm',
|
|
298
|
+
向导: 'wizard',
|
|
299
|
+
结果: 'result',
|
|
300
|
+
};
|
|
301
|
+
return aliases[b] || b;
|
|
302
|
+
}
|
|
303
|
+
function componentHintForBlock(block) {
|
|
304
|
+
switch (block) {
|
|
305
|
+
case 'filters':
|
|
306
|
+
case 'filters-dense':
|
|
307
|
+
return ['PageFilters', 'Input', 'Select'];
|
|
308
|
+
case 'table':
|
|
309
|
+
return ['Table', 'Empty'];
|
|
310
|
+
case 'status':
|
|
311
|
+
return ['Status'];
|
|
312
|
+
case 'kpi':
|
|
313
|
+
return ['PageStat', 'Grid'];
|
|
314
|
+
case 'chart':
|
|
315
|
+
return ['Card', 'Skeleton', 'Empty'];
|
|
316
|
+
case 'auth':
|
|
317
|
+
return ['Form', 'InputPassword', 'Button'];
|
|
318
|
+
case 'dialog':
|
|
319
|
+
return ['Dialog', 'Form'];
|
|
320
|
+
case 'drawer':
|
|
321
|
+
return ['Drawer', 'Form'];
|
|
322
|
+
case 'result':
|
|
323
|
+
return ['Result'];
|
|
324
|
+
case 'empty':
|
|
325
|
+
return ['Empty'];
|
|
326
|
+
case 'wizard':
|
|
327
|
+
return ['Stepper', 'Form'];
|
|
328
|
+
default:
|
|
329
|
+
return [];
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Map a reference brief to snippets, shell, and validation hints.
|
|
334
|
+
*/
|
|
335
|
+
export function mapReferenceBrief(input) {
|
|
336
|
+
const description = (input.description || '').trim();
|
|
337
|
+
const intent = (input.intent || '').trim();
|
|
338
|
+
const blob = `${description} ${intent} ${(input.style || '').trim()}`;
|
|
339
|
+
const surface = normalizeSurface(input.surface, description, intent);
|
|
340
|
+
const density = normalizeDensity(input.density, blob);
|
|
341
|
+
const requestedBlocks = (input.requiredBlocks || []).map(normalizeBlockId).filter(Boolean);
|
|
342
|
+
let blocks = requestedBlocks.length > 0 ? [...requestedBlocks] : [...SURFACE_DEFAULT_BLOCKS[surface]];
|
|
343
|
+
if (density === 'compact' && blocks.includes('filters')) {
|
|
344
|
+
blocks = blocks.map((b) => (b === 'filters' ? 'filters-dense' : b));
|
|
345
|
+
}
|
|
346
|
+
const mapping = [];
|
|
347
|
+
const suggestedSnippets = [];
|
|
348
|
+
for (const block of blocks) {
|
|
349
|
+
const entry = BLOCK_TO_SNIPPET[block];
|
|
350
|
+
if (!entry)
|
|
351
|
+
continue;
|
|
352
|
+
mapping.push({
|
|
353
|
+
block,
|
|
354
|
+
snippetId: entry.snippetId,
|
|
355
|
+
notesZh: entry.notesZh,
|
|
356
|
+
notesEn: entry.notesEn,
|
|
357
|
+
});
|
|
358
|
+
if (!suggestedSnippets.includes(entry.snippetId)) {
|
|
359
|
+
suggestedSnippets.push(entry.snippetId);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
const requiredComponents = new Set((input.requiredComponents || []).map((c) => c.replace(/^M/, '')));
|
|
363
|
+
for (const block of blocks) {
|
|
364
|
+
for (const c of componentHintForBlock(block))
|
|
365
|
+
requiredComponents.add(c);
|
|
366
|
+
}
|
|
367
|
+
const craftCuesZh = [
|
|
368
|
+
'按 mapping 逐块 get_page_snippet,不要整页克隆黄金样例',
|
|
369
|
+
'品牌色差异优先覆盖主题 --m-color-primary / 字体,页面 CSS 只用 token',
|
|
370
|
+
density === 'compact'
|
|
371
|
+
? '密度 compact:筛选 plain+small,列表可对照 list-page-dense'
|
|
372
|
+
: density === 'spacious'
|
|
373
|
+
? '密度 spacious:加大区块间距,避免塞满'
|
|
374
|
+
: '密度 default:对齐 Ops polish(一主按钮、菜单 icon、MStatus)',
|
|
375
|
+
];
|
|
376
|
+
if (input.primaryAction) {
|
|
377
|
+
craftCuesZh.push(`主操作文案对齐参考:「${input.primaryAction}」`);
|
|
378
|
+
}
|
|
379
|
+
const craftCuesEn = [
|
|
380
|
+
'Fill blocks via get_page_snippet from mapping — do not clone a whole golden page',
|
|
381
|
+
'Brand differences belong in theme tokens (--m-color-primary / fonts), not page hex',
|
|
382
|
+
density === 'compact'
|
|
383
|
+
? 'Density compact: plain+small filters; optional list-page-dense structure check'
|
|
384
|
+
: density === 'spacious'
|
|
385
|
+
? 'Density spacious: more section gap; avoid packing'
|
|
386
|
+
: 'Density default: Ops polish (one primary, menu icons, MStatus)',
|
|
387
|
+
];
|
|
388
|
+
if (input.primaryAction) {
|
|
389
|
+
craftCuesEn.push(`Primary CTA label from reference: “${input.primaryAction}”`);
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
surface,
|
|
393
|
+
density,
|
|
394
|
+
styleSummary: (input.style || description || null) && (input.style || description || '').trim()
|
|
395
|
+
? (input.style || description).trim()
|
|
396
|
+
: null,
|
|
397
|
+
suggestedSnippets,
|
|
398
|
+
suggestedShellId: SURFACE_SHELL[surface],
|
|
399
|
+
mapping,
|
|
400
|
+
tokenRoles: TOKEN_ROLES,
|
|
401
|
+
craftCuesZh,
|
|
402
|
+
craftCuesEn,
|
|
403
|
+
validationHints: {
|
|
404
|
+
requiredBlocks: blocks,
|
|
405
|
+
requiredComponents: [...requiredComponents],
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
/** Detect whether generated code covers a required block (heuristic). */
|
|
410
|
+
export function codeCoversBlock(code, block) {
|
|
411
|
+
const c = code;
|
|
412
|
+
switch (normalizeBlockId(block)) {
|
|
413
|
+
case 'filters':
|
|
414
|
+
case 'filters-dense':
|
|
415
|
+
return /<MPageFilters\b/i.test(c);
|
|
416
|
+
case 'chips':
|
|
417
|
+
return /<MPageFilterChips\b/i.test(c);
|
|
418
|
+
case 'header':
|
|
419
|
+
return /<MPageHeader\b/i.test(c);
|
|
420
|
+
case 'toolbar':
|
|
421
|
+
return /<MPageToolbar\b/i.test(c);
|
|
422
|
+
case 'table':
|
|
423
|
+
return /<MTable\b/i.test(c);
|
|
424
|
+
case 'status':
|
|
425
|
+
return /<MStatus\b/i.test(c);
|
|
426
|
+
case 'row-actions':
|
|
427
|
+
return /#cell-actions|<MDropdown\b/i.test(c);
|
|
428
|
+
case 'empty':
|
|
429
|
+
return /<MEmpty\b/i.test(c);
|
|
430
|
+
case 'form-header':
|
|
431
|
+
return /<MPageHeader\b/i.test(c);
|
|
432
|
+
case 'form-body':
|
|
433
|
+
return /<MForm\b/i.test(c);
|
|
434
|
+
case 'form-actions':
|
|
435
|
+
return /native-type=["']submit["']|type=["']submit["']|<MPageSection\b[^>]*variant=["']actions["']/i.test(c);
|
|
436
|
+
case 'kpi':
|
|
437
|
+
return /<MPageStat\b/i.test(c);
|
|
438
|
+
case 'chart':
|
|
439
|
+
return /<MCard\b/i.test(c) && (/<MSkeleton\b|<MEmpty\b/i.test(c) || /chart|图表/i.test(c));
|
|
440
|
+
case 'recent':
|
|
441
|
+
return /<MTable\b/i.test(c);
|
|
442
|
+
case 'detail':
|
|
443
|
+
return /<MPageHeader\b/i.test(c);
|
|
444
|
+
case 'result':
|
|
445
|
+
return /<MResult\b/i.test(c);
|
|
446
|
+
case 'wizard':
|
|
447
|
+
return /<MStepper\b/i.test(c);
|
|
448
|
+
case 'auth':
|
|
449
|
+
return /login-|auth-|<MInputPassword\b/i.test(c);
|
|
450
|
+
case 'shell':
|
|
451
|
+
return /<MLayout\b/i.test(c);
|
|
452
|
+
case 'dialog':
|
|
453
|
+
return /<MDialog\b/i.test(c);
|
|
454
|
+
case 'drawer':
|
|
455
|
+
return /<MDrawer\b/i.test(c);
|
|
456
|
+
case 'confirm':
|
|
457
|
+
return /<MConfirm(?:Dialog|Popup)\b/i.test(c);
|
|
458
|
+
default:
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
export function codeCoversComponent(code, component) {
|
|
463
|
+
const name = component.replace(/^M/, '').trim();
|
|
464
|
+
if (!name)
|
|
465
|
+
return true;
|
|
466
|
+
const re = new RegExp(`<M${name}\\b`, 'i');
|
|
467
|
+
return re.test(code);
|
|
468
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token-only signature shells for Account / Express / Flow / Ops.
|
|
3
|
+
* Pasteable CSS — no hex; brand differences belong in theme overrides.
|
|
4
|
+
*/
|
|
5
|
+
export interface StyleShell {
|
|
6
|
+
id: string;
|
|
7
|
+
surface: string;
|
|
8
|
+
titleZh: string;
|
|
9
|
+
titleEn: string;
|
|
10
|
+
signatureZh: string;
|
|
11
|
+
signatureEn: string;
|
|
12
|
+
whenZh: string;
|
|
13
|
+
whenEn: string;
|
|
14
|
+
css: string;
|
|
15
|
+
htmlHintZh: string;
|
|
16
|
+
htmlHintEn: string;
|
|
17
|
+
}
|
|
18
|
+
export declare const styleShells: StyleShell[];
|
|
19
|
+
export declare function findStyleShell(idOrSurface: string): StyleShell | undefined;
|
|
20
|
+
export declare function listStyleShells(surface?: string): StyleShell[];
|