@morya-ui/mcp 0.5.0 → 0.5.1

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.
@@ -116,7 +116,7 @@ const rows = [
116
116
 
117
117
  <MLayoutContent>
118
118
  <MPageContent fill>
119
- <MPageHeader title="用户管理" description="维护账号、角色与权限。">
119
+ <MPageHeader title="{{页面标题}}" description="{{页面说明}}">
120
120
  <template #actions>
121
121
  <MButton type="primary">
122
122
  新建用户
@@ -124,13 +124,14 @@ const rows = [
124
124
  </template>
125
125
  </MPageHeader>
126
126
 
127
+ <!-- 筛选区:四选一(list-filters / collapsible / stack / dense)。variant 跟密度,不默认 filled。 -->
127
128
  <MPageFilters
128
129
  v-model:expanded="filtersExpanded"
129
130
  aria-label="筛选"
130
- variant="filled"
131
131
  collapsible
132
132
  >
133
133
  <MSpace wrap>
134
+ <!-- 控件宽度可局部 inline;其余视觉跟 styleDirection -->
134
135
  <MInput v-model="keyword" placeholder="搜索名称" clearable style="width: 14rem" />
135
136
  <MSelect
136
137
  v-model="status"
@@ -161,6 +162,7 @@ const rows = [
161
162
  </template>
162
163
  </MPageFilters>
163
164
 
165
+ <!-- 用折叠筛选栈时通常不再叠加独立 chips;以下为演示已选摘要的独立用法 -->
164
166
  <MPageFilterChips v-if="activeFilters.length" label="已选" aria-label="已选筛选">
165
167
  <MTag
166
168
  v-for="item in activeFilters"
@@ -173,14 +175,13 @@ const rows = [
173
175
  />
174
176
  </MPageFilterChips>
175
177
 
178
+ <!-- 全视口主列表:fill + paginator 成对;striped/bordered 跟密度,非默认 -->
176
179
  <MTable
177
180
  :columns="columns"
178
181
  :rows="rows"
179
182
  :rows-per-page="5"
180
183
  fill
181
184
  paginator
182
- striped
183
- bordered
184
185
  row-key="id"
185
186
  aria-label="用户列表"
186
187
  >
@@ -201,14 +202,15 @@ const rows = [
201
202
  </MSpace>
202
203
  </template>
203
204
  <template #empty>
205
+ <!-- 空态说明“接下来做什么” + 唯一主操作 -->
204
206
  <MEmpty
205
- title="还没有用户"
206
- description="创建第一个用户后,即可分配角色与权限。"
207
+ title="{{空态标题}}"
208
+ description="{{空态说明}}"
207
209
  icon="user"
208
210
  >
209
211
  <template #extra>
210
212
  <MButton type="primary">
211
- 新建用户
213
+ {{空态主操作}}
212
214
  </MButton>
213
215
  </template>
214
216
  </MEmpty>
@@ -219,4 +221,4 @@ const rows = [
219
221
  </MLayout>
220
222
  </MLayout>
221
223
  </MConfigProvider>
222
- </template>
224
+ </template>
@@ -25,9 +25,9 @@ const model = reactive({
25
25
  password: '',
26
26
  })
27
27
 
28
- /** Brand seed for Account surfaces — prefer themeConfig over page-level hex. */
28
+ /** Brand seed for Account surfaces — prefer themeConfig over page-level hex. 品牌色改主题覆盖,不写页面 hex。 */
29
29
  const themeConfig = {
30
- seed: { colorPrimary: '#0b6e4f' },
30
+ seed: { colorPrimary: '#0b6e4f' }, // 演示:实际项目用 --m-color-primary 主题覆盖
31
31
  }
32
32
 
33
33
  async function onSubmit() {
@@ -51,12 +51,13 @@ async function onSubmit() {
51
51
  input-variant="filled"
52
52
  :theme-config="themeConfig"
53
53
  >
54
+ <!-- 品牌区渐变/氛围跟 styleDirection;结构:品牌区 + 表单面板 -->
54
55
  <div class="login-shell">
55
56
  <aside class="login-brand" aria-label="品牌">
56
- <p class="login-brand__mark">青禾书房</p>
57
- <h1 class="login-brand__title">把好书留在手边</h1>
57
+ <p class="login-brand__mark">{{品牌名}}</p>
58
+ <h1 class="login-brand__title">{{品牌主张}}</h1>
58
59
  <p class="login-brand__lead">
59
- 店员与会员共用同一套账户。登录后继续进货、上架与会员服务。
60
+ {{品牌导语}}
60
61
  </p>
61
62
  </aside>
62
63
 
@@ -64,10 +65,10 @@ async function onSubmit() {
64
65
  <div class="login-panel">
65
66
  <header class="login-panel__header">
66
67
  <MTitle :level="3">
67
- 登录
68
+ {{登录标题}}
68
69
  </MTitle>
69
70
  <MParagraph type="secondary">
70
- 使用工作邮箱进入后台。
71
+ {{登录说明}}
71
72
  </MParagraph>
72
73
  </header>
73
74
 
@@ -123,31 +124,11 @@ async function onSubmit() {
123
124
  gap: var(--m-space-4);
124
125
  padding: clamp(2rem, 6vw, 4.5rem);
125
126
  overflow: hidden;
126
- background:
127
- radial-gradient(
128
- 80% 60% at 10% 20%,
129
- color-mix(in srgb, var(--m-color-primary) 22%, transparent),
130
- transparent 55%
131
- ),
132
- linear-gradient(
133
- 165deg,
134
- color-mix(in srgb, var(--m-color-primary) 16%, var(--m-color-surface)) 0%,
135
- var(--m-color-surface) 55%,
136
- color-mix(in srgb, var(--m-color-border) 35%, var(--m-color-surface)) 100%
137
- );
127
+ /* 品牌氛围(渐变/插画)跟 styleDirection;默认 token-only 平壳 */
128
+ background: var(--m-color-surface);
138
129
  border-right: 1px solid var(--m-color-border);
139
130
  }
140
131
 
141
- .login-brand::after {
142
- content: '';
143
- position: absolute;
144
- inset: auto -10% -20% 40%;
145
- height: 55%;
146
- border-radius: 50%;
147
- background: color-mix(in srgb, var(--m-color-primary) 12%, transparent);
148
- pointer-events: none;
149
- }
150
-
151
132
  .login-brand__mark,
152
133
  .login-brand__title,
153
134
  .login-brand__lead {
@@ -34,20 +34,21 @@ import {
34
34
 
35
35
  <MLayoutContent>
36
36
  <MPageContent>
37
+ <!-- 结果壳:视觉(渐变/虚线边框)跟 styleDirection;此处保留 token-only 极简壳 -->
37
38
  <div class="result-shell">
38
39
  <MResult
39
40
  status="403"
40
- title="没有权限查看该项目"
41
- description="当前账号无权访问「青禾书房」工作区。可返回上一页,或联系管理员开通权限。"
41
+ title="{{结果标题}}"
42
+ description="{{结果说明}}"
42
43
  size="large"
43
44
  >
44
45
  <template #footer>
45
46
  <MSpace>
46
47
  <MButton type="primary">
47
- 返回上一页
48
+ {{主操作}}
48
49
  </MButton>
49
50
  <MButton type="text">
50
- 联系管理员
51
+ {{次操作}}
51
52
  </MButton>
52
53
  </MSpace>
53
54
  </template>
@@ -66,19 +67,7 @@ import {
66
67
  min-height: min(28rem, 70vh);
67
68
  padding: var(--m-space-8) var(--m-space-4);
68
69
  border-radius: var(--m-radius-lg);
69
- background:
70
- radial-gradient(
71
- 70% 55% at 50% 0%,
72
- color-mix(in srgb, var(--m-color-primary) 10%, transparent),
73
- transparent 60%
74
- ),
75
- var(--m-color-surface);
76
- border: 1px dashed color-mix(in srgb, var(--m-color-border) 80%, transparent);
77
- }
78
-
79
- @media (prefers-reduced-motion: reduce) {
80
- .result-shell {
81
- background: var(--m-color-surface);
82
- }
70
+ background: var(--m-color-surface);
71
+ border: 1px solid var(--m-color-border);
83
72
  }
84
73
  </style>
@@ -8,6 +8,7 @@ import {
8
8
  MBreadcrumb,
9
9
  MButton,
10
10
  MConfigProvider,
11
+ message,
11
12
  MForm,
12
13
  MFormItem,
13
14
  MInput,
@@ -21,7 +22,6 @@ import {
21
22
  MSpace,
22
23
  MSwitch,
23
24
  MTabs,
24
- message,
25
25
  zhCN,
26
26
  } from 'morya-ui'
27
27
  import { reactive, ref } from 'vue'
@@ -82,8 +82,8 @@ async function saveNotify() {
82
82
  <MLayoutContent>
83
83
  <MPageContent width="narrow">
84
84
  <MPageHeader
85
- title="系统设置"
86
- description="按域分组配置;切换 Tab 时保留未保存输入,保存成功用 message 一句话回执。"
85
+ title="{{设置页标题}}"
86
+ description="{{设置页说明}}"
87
87
  />
88
88
 
89
89
  <MTabs v-model="activeTab" :tabs="tabs">
@@ -89,8 +89,8 @@ async function finish() {
89
89
  <MLayoutContent>
90
90
  <MPageContent width="narrow">
91
91
  <MPageHeader
92
- title="创建工作空间"
93
- description="三步完成;每步只做一件事。Stepper 使用 steps(不是 items)。"
92
+ title="{{向导标题}}"
93
+ description="{{向导说明}}"
94
94
  />
95
95
 
96
96
  <MStepper v-model="activeStep" :steps="steps" linear />
@@ -251,7 +251,9 @@ describe('@morya-ui/mcp handlers', () => {
251
251
  const rail = read(handlers.getGoldenPage({ page: 'list-page-rail' }));
252
252
  expect(rail.id).toBe('list-page-rail');
253
253
  expect(rail.style).toBe('rail');
254
- expect(rail.source).toContain('color-mix');
254
+ // rail 变体差异集中在侧栏品牌 chrome;token-only,不再默认渐变
255
+ expect(rail.source).toContain('gp-rail-sider');
256
+ expect(rail.source).toContain('--m-color-primary');
255
257
  });
256
258
  it('exposes style direction rules without a preset catalog', () => {
257
259
  const result = read(handlers.getStyleDirection({ mode: 'zh-CN' }));
@@ -343,6 +345,27 @@ describe('@morya-ui/mcp handlers', () => {
343
345
  expect(result.ok).toBe(true);
344
346
  expect(result.suggestions.some((item) => item.type === 'status-cell-tag')).toBe(true);
345
347
  });
348
+ it('suggests a single filter region when multiple MPageFilters appear', () => {
349
+ const result = read(handlers.validatePage({
350
+ code: '<MPageFilters /><MPageFilters collapsible />',
351
+ }));
352
+ expect(result.ok).toBe(true);
353
+ expect(result.craft.some((item) => item.type === 'multiple-filter-regions' && item.severity === 'craft')).toBe(true);
354
+ });
355
+ it('suggests dropping standalone chips when the collapsible stack already renders them', () => {
356
+ const result = read(handlers.validatePage({
357
+ code: '<MPageFilters collapsible><MInput /></MPageFilters><MPageFilterChips />',
358
+ }));
359
+ expect(result.ok).toBe(true);
360
+ expect(result.craft.some((item) => item.type === 'redundant-filter-chips')).toBe(true);
361
+ });
362
+ it('suggests consistent density when compact and spacious are mixed', () => {
363
+ const result = read(handlers.validatePage({
364
+ code: '<MTable density="compact" /><MCard density="spacious" />',
365
+ }));
366
+ expect(result.ok).toBe(true);
367
+ expect(result.craft.some((item) => item.type === 'mixed-density')).toBe(true);
368
+ });
346
369
  it('returns detail-page and form-in-dialog golden samples', () => {
347
370
  const detail = read(handlers.getGoldenPage({ page: 'detail-page' }));
348
371
  expect(detail.id).toBe('detail-page');
package/dist/index.js CHANGED
@@ -86,7 +86,7 @@ register('get_pattern', 'Read the structure, component composition, layout, and
86
86
  pattern: z.string().min(1),
87
87
  mode: z.string().optional(),
88
88
  }, async (args) => handlers.getPattern(args));
89
- register('recommend_page', 'Recommend a page pattern and component composition from product intent. Pass style as free-text look; density=compact|default|spacious; brief for reference fidelity mapping; includeScaffold for optional golden structure.', {
89
+ register('recommend_page', 'Recommend a page pattern and component composition from product intent. Default path composes suggestedSnippets + get_page_snippet (golden pages are optional block-order checks, not a default look). Pass style as free-text look; density=compact|default|spacious; brief for reference fidelity mapping; includeScaffold only when you explicitly need the golden structure source.', {
90
90
  intent: z.string().min(1),
91
91
  pageType: z.string().optional(),
92
92
  features: z.array(z.string()).max(20).optional(),
@@ -14,6 +14,10 @@ export interface PageSnippet {
14
14
  rulesEn: string[];
15
15
  avoid: string[];
16
16
  avoidEn: string[];
17
+ /** Mutually exclusive sibling snippets (pick one for the same block). */
18
+ excludes?: string[];
19
+ /** Snippets commonly composed together (weak pointer for polish/validate). */
20
+ pairsWith?: string[];
17
21
  }
18
22
  export declare const pageSnippets: PageSnippet[];
19
23
  export declare function findPageSnippet(id: string): PageSnippet | undefined;
@@ -1,6 +1,7 @@
1
1
  export const pageSnippets = [
2
2
  {
3
3
  id: 'list-filters',
4
+ excludes: ['list-filters-collapsible', 'list-filters-stack', 'list-filters-dense'],
4
5
  title: '列表页基础筛选栏',
5
6
  titleEn: 'Basic list filter bar',
6
7
  description: '关键词 + 1–2 个枚举 + 查询/重置。条件少、无需已选摘要时用本片段;有次要条件用 list-filters-collapsible;默认列表推荐 list-filters-stack。',
@@ -61,6 +62,7 @@ function resetFilters() {
61
62
  },
62
63
  {
63
64
  id: 'list-filters-collapsible',
65
+ excludes: ['list-filters', 'list-filters-stack', 'list-filters-dense'],
64
66
  title: '列表页折叠高级筛选',
65
67
  titleEn: 'Collapsible advanced list filters',
66
68
  description: '常用条件在 default,次要条件在 #advanced;对齐 list-page 黄金结构。通常再接 list-filter-chips,或直接用 list-filters-stack。',
@@ -215,6 +217,8 @@ function clearFilter(key: string) {
215
217
  },
216
218
  {
217
219
  id: 'list-filters-stack',
220
+ excludes: ['list-filters', 'list-filters-collapsible', 'list-filters-dense'],
221
+ pairsWith: ['list-filter-chips'],
218
222
  title: '列表筛选完整栈(折叠 + 已选)',
219
223
  titleEn: 'Full list filter stack (collapsible + chips)',
220
224
  description: '默认推荐的列表筛选组合:MPageFilters collapsible + #advanced + MPageFilterChips。对齐 list-page / list-page-rail 黄金结构;dense 无高级条件时可改用 list-filters(plain/small)。',
@@ -352,6 +356,7 @@ function resetFilters() {
352
356
  },
353
357
  {
354
358
  id: 'list-filters-dense',
359
+ excludes: ['list-filters', 'list-filters-collapsible', 'list-filters-stack'],
355
360
  title: '列表页紧凑筛选栏',
356
361
  titleEn: 'Dense list filter bar',
357
362
  description: 'list-page-dense craft:variant="plain" + size="small",字段少、无折叠/Chips。监控/告警等高密度列表用。',
package/dist/tools.js CHANGED
@@ -1125,6 +1125,22 @@ export function createToolHandlers(catalog = loadCatalog()) {
1125
1125
  m = iconOnlyRe.exec(code);
1126
1126
  }
1127
1127
  }
1128
+ // Craft: composition relations — multiple filter regions on one page
1129
+ {
1130
+ const filterCount = (code.match(/<MPageFilters\b/gi) || []).length;
1131
+ if (filterCount > 1) {
1132
+ push('craft', 'composition', 'multiple-filter-regions', '组合建议:一个列表视图通常只保留一个 MPageFilters 筛选区(list-filters / collapsible / stack / dense 四选一)。', 'Composition: keep a single MPageFilters region per list view (pick one of list-filters / collapsible / stack / dense).');
1133
+ }
1134
+ }
1135
+ // Craft: composition relations — filter stack already carries chips; avoid stacking a standalone chip row
1136
+ if (/<MPageFilters\b[^>]*\bcollapsible\b[\s\S]*?<\/MPageFilters>/i.test(code) &&
1137
+ /<MPageFilterChips\b/i.test(code)) {
1138
+ push('craft', 'composition', 'redundant-filter-chips', '组合建议:使用折叠筛选栈(list-filters-stack / collapsible 已含已选摘要)时,通常不再叠加独立 MPageFilterChips。', 'Composition: when the collapsible filter stack already renders active chips, avoid adding a standalone MPageFilterChips row.');
1139
+ }
1140
+ // Craft: composition relations — density mixed within one page
1141
+ if (/density\s*=\s*["']compact["']/i.test(code) && /density\s*=\s*["']spacious["']/i.test(code)) {
1142
+ push('craft', 'composition', 'mixed-density', '组合建议:同一页面内保持密度一致,不要混用 compact 与 spacious。', 'Composition: keep density consistent within one page — do not mix compact and spacious.');
1143
+ }
1128
1144
  // Brief fidelity: required blocks / components
1129
1145
  const mapped = args.brief
1130
1146
  ? mapReferenceBrief(args.brief)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@morya-ui/mcp",
3
3
  "type": "module",
4
- "version": "0.5.0",
4
+ "version": "0.5.1",
5
5
  "description": "MCP server for morya-ui — component docs, examples, and usage guidance for AI coding agents.",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/morya-space/morya-ui/tree/main/packages/ui-mcp#readme",