@xferops/design-guide 0.2.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.
- package/README.md +30 -0
- package/dist/chunk-R4AEJOIP.js +161 -0
- package/dist/chunk-R4AEJOIP.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.js +246 -0
- package/dist/server.js.map +1 -0
- package/guides/feedback-status-states.md +107 -0
- package/guides/forms-field-input.md +124 -0
- package/guides/foundations-app-setup.md +83 -0
- package/guides/foundations-token-theming.md +125 -0
- package/guides/navigation-shell.md +124 -0
- package/guides/overlays-actions.md +134 -0
- package/guides/primitives-icons-actions.md +77 -0
- package/guides/primitives-layout-content.md +96 -0
- package/guides/table-sorting.md +156 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# @xferops/design-guide
|
|
2
|
+
|
|
3
|
+
MCP server and query helpers for XferOps frontend design guidance.
|
|
4
|
+
|
|
5
|
+
## What It Provides
|
|
6
|
+
|
|
7
|
+
- MCP tools for searching and reading guides
|
|
8
|
+
- MCP resources with stable URIs like `guide://table/sorting`
|
|
9
|
+
- Bundled markdown guide content that ships with the package
|
|
10
|
+
- Coverage for app setup, token theming, layout/content primitives, forms, navigation, overlays, feedback states, and table patterns
|
|
11
|
+
|
|
12
|
+
## Local Usage
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm --filter @xferops/design-guide build
|
|
16
|
+
node packages/design-guide/dist/server.js
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## MCP Client Example
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcpServers": {
|
|
24
|
+
"xferops-design-guide": {
|
|
25
|
+
"command": "npx",
|
|
26
|
+
"args": ["-y", "@xferops/design-guide"]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/guides/frontmatter.ts
|
|
4
|
+
function parseFrontmatter(markdown) {
|
|
5
|
+
const match = markdown.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
6
|
+
if (!match) {
|
|
7
|
+
throw new Error("Guide file is missing frontmatter.");
|
|
8
|
+
}
|
|
9
|
+
const [, frontmatterBlock, content] = match;
|
|
10
|
+
const metadata = parseFrontmatterBlock(frontmatterBlock);
|
|
11
|
+
validateMetadata(metadata);
|
|
12
|
+
return {
|
|
13
|
+
metadata,
|
|
14
|
+
content: content.trimStart()
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function parseFrontmatterBlock(block) {
|
|
18
|
+
const result = {};
|
|
19
|
+
const lines = block.split("\n");
|
|
20
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
21
|
+
const line = lines[index];
|
|
22
|
+
if (!line.trim()) continue;
|
|
23
|
+
const keyMatch = line.match(/^([A-Za-z0-9]+):(?:\s*(.*))?$/);
|
|
24
|
+
if (!keyMatch) {
|
|
25
|
+
throw new Error(`Unsupported frontmatter line: ${line}`);
|
|
26
|
+
}
|
|
27
|
+
const [, key, rawValue = ""] = keyMatch;
|
|
28
|
+
if (rawValue.trim()) {
|
|
29
|
+
result[key] = rawValue.trim();
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const values = [];
|
|
33
|
+
while (index + 1 < lines.length && /^\s*-\s+/.test(lines[index + 1])) {
|
|
34
|
+
index += 1;
|
|
35
|
+
values.push(lines[index].replace(/^\s*-\s+/, "").trim());
|
|
36
|
+
}
|
|
37
|
+
result[key] = values;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
id: getString(result.id, "id"),
|
|
41
|
+
title: getString(result.title, "title"),
|
|
42
|
+
summary: getString(result.summary, "summary"),
|
|
43
|
+
category: getString(result.category, "category"),
|
|
44
|
+
slug: getString(result.slug, "slug"),
|
|
45
|
+
tags: getArray(result.tags, "tags"),
|
|
46
|
+
sourcePath: typeof result.sourcePath === "string" ? result.sourcePath : void 0,
|
|
47
|
+
relatedPaths: getArray(result.relatedPaths, "relatedPaths")
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function getString(value, key) {
|
|
51
|
+
if (typeof value === "string" && value.length > 0) {
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
if (value == null) {
|
|
55
|
+
return void 0;
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`Frontmatter key "${key}" must be a string.`);
|
|
58
|
+
}
|
|
59
|
+
function getArray(value, key) {
|
|
60
|
+
if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
if (value == null) {
|
|
64
|
+
return void 0;
|
|
65
|
+
}
|
|
66
|
+
throw new Error(`Frontmatter key "${key}" must be a string array.`);
|
|
67
|
+
}
|
|
68
|
+
function validateMetadata(metadata) {
|
|
69
|
+
const requiredKeys = ["id", "title", "summary", "category", "slug", "tags"];
|
|
70
|
+
for (const key of requiredKeys) {
|
|
71
|
+
if (metadata[key] == null || Array.isArray(metadata[key]) && metadata[key].length === 0) {
|
|
72
|
+
throw new Error(`Guide frontmatter is missing "${key}".`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/guides/catalog.ts
|
|
78
|
+
import { readdir, readFile } from "fs/promises";
|
|
79
|
+
import { dirname, resolve } from "path";
|
|
80
|
+
import { fileURLToPath } from "url";
|
|
81
|
+
var currentDir = dirname(fileURLToPath(import.meta.url));
|
|
82
|
+
var guidesDir = resolve(currentDir, "../../guides");
|
|
83
|
+
var guideCache = null;
|
|
84
|
+
async function listGuides() {
|
|
85
|
+
if (!guideCache) {
|
|
86
|
+
guideCache = loadGuides();
|
|
87
|
+
}
|
|
88
|
+
return guideCache;
|
|
89
|
+
}
|
|
90
|
+
async function getGuideById(id) {
|
|
91
|
+
const guides = await listGuides();
|
|
92
|
+
return guides.find((guide) => guide.id === id);
|
|
93
|
+
}
|
|
94
|
+
async function getGuideByUri(uri) {
|
|
95
|
+
const guides = await listGuides();
|
|
96
|
+
return guides.find((guide) => guide.uri === uri);
|
|
97
|
+
}
|
|
98
|
+
async function loadGuides() {
|
|
99
|
+
const fileNames = (await readdir(guidesDir)).filter((name) => name.endsWith(".md")).sort((left, right) => left.localeCompare(right));
|
|
100
|
+
return Promise.all(
|
|
101
|
+
fileNames.map(async (fileName) => {
|
|
102
|
+
const relativePath = fileName;
|
|
103
|
+
const markdown = await readFile(resolve(guidesDir, relativePath), "utf8");
|
|
104
|
+
const { metadata, content } = parseFrontmatter(markdown);
|
|
105
|
+
return {
|
|
106
|
+
...metadata,
|
|
107
|
+
uri: `guide://${metadata.category}/${metadata.slug}`,
|
|
108
|
+
relativePath,
|
|
109
|
+
content
|
|
110
|
+
};
|
|
111
|
+
})
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/guides/search.ts
|
|
116
|
+
async function searchGuides(query) {
|
|
117
|
+
const guides = await listGuides();
|
|
118
|
+
const normalized = query.trim().toLowerCase();
|
|
119
|
+
if (!normalized) {
|
|
120
|
+
return guides;
|
|
121
|
+
}
|
|
122
|
+
const terms = normalized.split(/\s+/).filter(Boolean);
|
|
123
|
+
return guides.map((guide) => ({
|
|
124
|
+
guide,
|
|
125
|
+
score: scoreGuide(guide, terms)
|
|
126
|
+
})).filter((entry) => entry.score > 0).sort((left, right) => right.score - left.score || left.guide.title.localeCompare(right.guide.title)).map((entry) => entry.guide);
|
|
127
|
+
}
|
|
128
|
+
function scoreGuide(guide, terms) {
|
|
129
|
+
const haystacks = [
|
|
130
|
+
guide.title.toLowerCase(),
|
|
131
|
+
guide.summary.toLowerCase(),
|
|
132
|
+
guide.category.toLowerCase(),
|
|
133
|
+
guide.slug.toLowerCase(),
|
|
134
|
+
guide.id.toLowerCase(),
|
|
135
|
+
guide.uri.toLowerCase(),
|
|
136
|
+
guide.tags.join(" ").toLowerCase(),
|
|
137
|
+
guide.content.toLowerCase()
|
|
138
|
+
];
|
|
139
|
+
return terms.reduce((score, term) => {
|
|
140
|
+
let termScore = 0;
|
|
141
|
+
for (const haystack of haystacks) {
|
|
142
|
+
if (haystack === term) {
|
|
143
|
+
termScore = Math.max(termScore, 8);
|
|
144
|
+
} else if (haystack.startsWith(term)) {
|
|
145
|
+
termScore = Math.max(termScore, 5);
|
|
146
|
+
} else if (haystack.includes(term)) {
|
|
147
|
+
termScore = Math.max(termScore, 2);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return score + termScore;
|
|
151
|
+
}, 0);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export {
|
|
155
|
+
parseFrontmatter,
|
|
156
|
+
listGuides,
|
|
157
|
+
getGuideById,
|
|
158
|
+
getGuideByUri,
|
|
159
|
+
searchGuides
|
|
160
|
+
};
|
|
161
|
+
//# sourceMappingURL=chunk-R4AEJOIP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/guides/frontmatter.ts","../src/guides/catalog.ts","../src/guides/search.ts"],"sourcesContent":["export type GuideMetadata = {\n id: string;\n title: string;\n summary: string;\n category: string;\n slug: string;\n tags: string[];\n sourcePath?: string;\n relatedPaths?: string[];\n};\n\nexport function parseFrontmatter(markdown: string): {\n metadata: GuideMetadata;\n content: string;\n} {\n const match = markdown.match(/^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/);\n\n if (!match) {\n throw new Error(\"Guide file is missing frontmatter.\");\n }\n\n const [, frontmatterBlock, content] = match;\n const metadata = parseFrontmatterBlock(frontmatterBlock);\n\n validateMetadata(metadata);\n\n return {\n metadata: metadata as GuideMetadata,\n content: content.trimStart()\n };\n}\n\nfunction parseFrontmatterBlock(block: string): Partial<GuideMetadata> {\n const result: Record<string, string | string[]> = {};\n const lines = block.split(\"\\n\");\n\n for (let index = 0; index < lines.length; index += 1) {\n const line = lines[index];\n\n if (!line.trim()) continue;\n\n const keyMatch = line.match(/^([A-Za-z0-9]+):(?:\\s*(.*))?$/);\n\n if (!keyMatch) {\n throw new Error(`Unsupported frontmatter line: ${line}`);\n }\n\n const [, key, rawValue = \"\"] = keyMatch;\n\n if (rawValue.trim()) {\n result[key] = rawValue.trim();\n continue;\n }\n\n const values: string[] = [];\n\n while (index + 1 < lines.length && /^\\s*-\\s+/.test(lines[index + 1])) {\n index += 1;\n values.push(lines[index].replace(/^\\s*-\\s+/, \"\").trim());\n }\n\n result[key] = values;\n }\n\n return {\n id: getString(result.id, \"id\"),\n title: getString(result.title, \"title\"),\n summary: getString(result.summary, \"summary\"),\n category: getString(result.category, \"category\"),\n slug: getString(result.slug, \"slug\"),\n tags: getArray(result.tags, \"tags\"),\n sourcePath: typeof result.sourcePath === \"string\" ? result.sourcePath : undefined,\n relatedPaths: getArray(result.relatedPaths, \"relatedPaths\")\n };\n}\n\nfunction getString(value: unknown, key: string): string | undefined {\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n\n if (value == null) {\n return undefined;\n }\n\n throw new Error(`Frontmatter key \"${key}\" must be a string.`);\n}\n\nfunction getArray(value: unknown, key: string): string[] | undefined {\n if (Array.isArray(value) && value.every((item) => typeof item === \"string\")) {\n return value;\n }\n\n if (value == null) {\n return undefined;\n }\n\n throw new Error(`Frontmatter key \"${key}\" must be a string array.`);\n}\n\nfunction validateMetadata(metadata: Partial<GuideMetadata>): asserts metadata is GuideMetadata {\n const requiredKeys = [\"id\", \"title\", \"summary\", \"category\", \"slug\", \"tags\"] as const;\n\n for (const key of requiredKeys) {\n if (metadata[key] == null || (Array.isArray(metadata[key]) && metadata[key].length === 0)) {\n throw new Error(`Guide frontmatter is missing \"${key}\".`);\n }\n }\n}\n","import { readdir, readFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { parseFrontmatter, type GuideMetadata } from \"./frontmatter\";\n\nexport type GuideRecord = GuideMetadata & {\n uri: string;\n relativePath: string;\n content: string;\n};\n\nconst currentDir = dirname(fileURLToPath(import.meta.url));\nconst guidesDir = resolve(currentDir, \"../../guides\");\n\nlet guideCache: Promise<GuideRecord[]> | null = null;\n\nexport async function listGuides(): Promise<GuideRecord[]> {\n if (!guideCache) {\n guideCache = loadGuides();\n }\n\n return guideCache;\n}\n\nexport async function getGuideById(id: string): Promise<GuideRecord | undefined> {\n const guides = await listGuides();\n return guides.find((guide) => guide.id === id);\n}\n\nexport async function getGuideByUri(uri: string): Promise<GuideRecord | undefined> {\n const guides = await listGuides();\n return guides.find((guide) => guide.uri === uri);\n}\n\nasync function loadGuides(): Promise<GuideRecord[]> {\n const fileNames = (await readdir(guidesDir))\n .filter((name) => name.endsWith(\".md\"))\n .sort((left, right) => left.localeCompare(right));\n\n return Promise.all(\n fileNames.map(async (fileName) => {\n const relativePath = fileName;\n const markdown = await readFile(resolve(guidesDir, relativePath), \"utf8\");\n const { metadata, content } = parseFrontmatter(markdown);\n\n return {\n ...metadata,\n uri: `guide://${metadata.category}/${metadata.slug}`,\n relativePath,\n content\n };\n })\n );\n}\n","import type { GuideRecord } from \"./catalog\";\nimport { listGuides } from \"./catalog\";\n\nexport async function searchGuides(query: string): Promise<GuideRecord[]> {\n const guides = await listGuides();\n const normalized = query.trim().toLowerCase();\n\n if (!normalized) {\n return guides;\n }\n\n const terms = normalized.split(/\\s+/).filter(Boolean);\n\n return guides\n .map((guide) => ({\n guide,\n score: scoreGuide(guide, terms)\n }))\n .filter((entry) => entry.score > 0)\n .sort((left, right) => right.score - left.score || left.guide.title.localeCompare(right.guide.title))\n .map((entry) => entry.guide);\n}\n\nfunction scoreGuide(guide: GuideRecord, terms: string[]): number {\n const haystacks = [\n guide.title.toLowerCase(),\n guide.summary.toLowerCase(),\n guide.category.toLowerCase(),\n guide.slug.toLowerCase(),\n guide.id.toLowerCase(),\n guide.uri.toLowerCase(),\n guide.tags.join(\" \").toLowerCase(),\n guide.content.toLowerCase()\n ];\n\n return terms.reduce((score, term) => {\n let termScore = 0;\n\n for (const haystack of haystacks) {\n if (haystack === term) {\n termScore = Math.max(termScore, 8);\n } else if (haystack.startsWith(term)) {\n termScore = Math.max(termScore, 5);\n } else if (haystack.includes(term)) {\n termScore = Math.max(termScore, 2);\n }\n }\n\n return score + termScore;\n }, 0);\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
type GuideMetadata = {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
summary: string;
|
|
5
|
+
category: string;
|
|
6
|
+
slug: string;
|
|
7
|
+
tags: string[];
|
|
8
|
+
sourcePath?: string;
|
|
9
|
+
relatedPaths?: string[];
|
|
10
|
+
};
|
|
11
|
+
declare function parseFrontmatter(markdown: string): {
|
|
12
|
+
metadata: GuideMetadata;
|
|
13
|
+
content: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type GuideRecord = GuideMetadata & {
|
|
17
|
+
uri: string;
|
|
18
|
+
relativePath: string;
|
|
19
|
+
content: string;
|
|
20
|
+
};
|
|
21
|
+
declare function listGuides(): Promise<GuideRecord[]>;
|
|
22
|
+
declare function getGuideById(id: string): Promise<GuideRecord | undefined>;
|
|
23
|
+
declare function getGuideByUri(uri: string): Promise<GuideRecord | undefined>;
|
|
24
|
+
|
|
25
|
+
declare function searchGuides(query: string): Promise<GuideRecord[]>;
|
|
26
|
+
|
|
27
|
+
export { type GuideMetadata, type GuideRecord, getGuideById, getGuideByUri, listGuides, parseFrontmatter, searchGuides };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getGuideById,
|
|
4
|
+
getGuideByUri,
|
|
5
|
+
listGuides,
|
|
6
|
+
parseFrontmatter,
|
|
7
|
+
searchGuides
|
|
8
|
+
} from "./chunk-R4AEJOIP.js";
|
|
9
|
+
export {
|
|
10
|
+
getGuideById,
|
|
11
|
+
getGuideByUri,
|
|
12
|
+
listGuides,
|
|
13
|
+
parseFrontmatter,
|
|
14
|
+
searchGuides
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getGuideById,
|
|
4
|
+
listGuides,
|
|
5
|
+
searchGuides
|
|
6
|
+
} from "./chunk-R4AEJOIP.js";
|
|
7
|
+
|
|
8
|
+
// src/server.ts
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
|
|
14
|
+
// package.json
|
|
15
|
+
var package_default = {
|
|
16
|
+
name: "@xferops/design-guide",
|
|
17
|
+
version: "0.1.0",
|
|
18
|
+
private: false,
|
|
19
|
+
type: "module",
|
|
20
|
+
main: "./dist/index.js",
|
|
21
|
+
types: "./dist/index.d.ts",
|
|
22
|
+
bin: {
|
|
23
|
+
"xferops-design-guide-mcp": "./dist/server.js"
|
|
24
|
+
},
|
|
25
|
+
files: [
|
|
26
|
+
"dist",
|
|
27
|
+
"guides"
|
|
28
|
+
],
|
|
29
|
+
publishConfig: {
|
|
30
|
+
access: "public",
|
|
31
|
+
registry: "https://registry.npmjs.org/"
|
|
32
|
+
},
|
|
33
|
+
exports: {
|
|
34
|
+
".": {
|
|
35
|
+
types: "./dist/index.d.ts",
|
|
36
|
+
default: "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./server": {
|
|
39
|
+
types: "./dist/server.d.ts",
|
|
40
|
+
default: "./dist/server.js"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
scripts: {
|
|
44
|
+
build: "tsup",
|
|
45
|
+
typecheck: "tsc -p tsconfig.json --noEmit",
|
|
46
|
+
test: "vitest run"
|
|
47
|
+
},
|
|
48
|
+
dependencies: {
|
|
49
|
+
"@modelcontextprotocol/sdk": "^1.20.2",
|
|
50
|
+
zod: "^4.1.11"
|
|
51
|
+
},
|
|
52
|
+
devDependencies: {
|
|
53
|
+
"@types/node": "^24.5.2",
|
|
54
|
+
tsx: "^4.20.6"
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// src/server.ts
|
|
59
|
+
function createServer() {
|
|
60
|
+
const server = new McpServer(
|
|
61
|
+
{
|
|
62
|
+
name: "@xferops/design-guide",
|
|
63
|
+
version: package_default.version
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
capabilities: {
|
|
67
|
+
logging: {}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
server.registerTool(
|
|
72
|
+
"search_guides",
|
|
73
|
+
{
|
|
74
|
+
title: "Search Design Guides",
|
|
75
|
+
description: "Search the XferOps frontend design guide catalog by keyword, tag, or area.",
|
|
76
|
+
inputSchema: z.object({
|
|
77
|
+
query: z.string().describe("Keywords such as table sorting, forms, overlays, or navigation.")
|
|
78
|
+
}),
|
|
79
|
+
outputSchema: z.object({
|
|
80
|
+
guides: z.array(
|
|
81
|
+
z.object({
|
|
82
|
+
id: z.string(),
|
|
83
|
+
title: z.string(),
|
|
84
|
+
summary: z.string(),
|
|
85
|
+
tags: z.array(z.string()),
|
|
86
|
+
uri: z.string(),
|
|
87
|
+
sourcePath: z.string().optional()
|
|
88
|
+
})
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
},
|
|
92
|
+
async ({ query }) => {
|
|
93
|
+
const guides = (await searchGuides(query)).map((guide) => ({
|
|
94
|
+
id: guide.id,
|
|
95
|
+
title: guide.title,
|
|
96
|
+
summary: guide.summary,
|
|
97
|
+
tags: guide.tags,
|
|
98
|
+
uri: guide.uri,
|
|
99
|
+
sourcePath: guide.sourcePath
|
|
100
|
+
}));
|
|
101
|
+
return {
|
|
102
|
+
content: [
|
|
103
|
+
{
|
|
104
|
+
type: "text",
|
|
105
|
+
text: guides.length ? guides.map((guide) => `${guide.title} (${guide.uri})`).join("\n") : "No guides matched the query."
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
structuredContent: { guides }
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
server.registerTool(
|
|
113
|
+
"get_guide",
|
|
114
|
+
{
|
|
115
|
+
title: "Get Design Guide",
|
|
116
|
+
description: "Read a guide by id such as table/sorting or by URI such as guide://table/sorting.",
|
|
117
|
+
inputSchema: z.object({
|
|
118
|
+
identifier: z.string().describe("Guide id or guide URI.")
|
|
119
|
+
}),
|
|
120
|
+
outputSchema: z.object({
|
|
121
|
+
id: z.string(),
|
|
122
|
+
title: z.string(),
|
|
123
|
+
summary: z.string(),
|
|
124
|
+
uri: z.string(),
|
|
125
|
+
content: z.string(),
|
|
126
|
+
sourcePath: z.string().optional()
|
|
127
|
+
})
|
|
128
|
+
},
|
|
129
|
+
async ({ identifier }) => {
|
|
130
|
+
const guideId = identifier.startsWith("guide://") ? identifier.replace("guide://", "") : identifier.replace(/^\/+/, "");
|
|
131
|
+
const guide = await getGuideById(guideId);
|
|
132
|
+
if (!guide) {
|
|
133
|
+
return {
|
|
134
|
+
content: [
|
|
135
|
+
{
|
|
136
|
+
type: "text",
|
|
137
|
+
text: `Guide not found: ${identifier}`
|
|
138
|
+
}
|
|
139
|
+
],
|
|
140
|
+
isError: true
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
content: [
|
|
145
|
+
{
|
|
146
|
+
type: "text",
|
|
147
|
+
text: guide.content
|
|
148
|
+
}
|
|
149
|
+
],
|
|
150
|
+
structuredContent: {
|
|
151
|
+
id: guide.id,
|
|
152
|
+
title: guide.title,
|
|
153
|
+
summary: guide.summary,
|
|
154
|
+
uri: guide.uri,
|
|
155
|
+
content: guide.content,
|
|
156
|
+
sourcePath: guide.sourcePath
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
server.registerResource(
|
|
162
|
+
"guide-catalog",
|
|
163
|
+
"guide://catalog",
|
|
164
|
+
{
|
|
165
|
+
title: "Guide Catalog",
|
|
166
|
+
description: "Catalog of available XferOps frontend design guides.",
|
|
167
|
+
mimeType: "application/json"
|
|
168
|
+
},
|
|
169
|
+
async (uri) => {
|
|
170
|
+
const guides = await listGuides();
|
|
171
|
+
return {
|
|
172
|
+
contents: [
|
|
173
|
+
{
|
|
174
|
+
uri: uri.href,
|
|
175
|
+
text: JSON.stringify(
|
|
176
|
+
guides.map((guide) => ({
|
|
177
|
+
id: guide.id,
|
|
178
|
+
title: guide.title,
|
|
179
|
+
summary: guide.summary,
|
|
180
|
+
tags: guide.tags,
|
|
181
|
+
uri: guide.uri,
|
|
182
|
+
sourcePath: guide.sourcePath
|
|
183
|
+
})),
|
|
184
|
+
null,
|
|
185
|
+
2
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
server.registerResource(
|
|
193
|
+
"guide-document",
|
|
194
|
+
new ResourceTemplate("guide://{category}/{slug}", {
|
|
195
|
+
list: async () => {
|
|
196
|
+
const guides = await listGuides();
|
|
197
|
+
return {
|
|
198
|
+
resources: guides.map((guide) => ({
|
|
199
|
+
uri: guide.uri,
|
|
200
|
+
name: guide.title,
|
|
201
|
+
description: guide.summary,
|
|
202
|
+
mimeType: "text/markdown"
|
|
203
|
+
}))
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}),
|
|
207
|
+
{
|
|
208
|
+
title: "Guide Document",
|
|
209
|
+
description: "A markdown guide for a specific frontend topic.",
|
|
210
|
+
mimeType: "text/markdown"
|
|
211
|
+
},
|
|
212
|
+
async (uri, { category, slug }) => {
|
|
213
|
+
const guide = await getGuideById(`${category}/${slug}`);
|
|
214
|
+
if (!guide) {
|
|
215
|
+
throw new Error(`Guide not found for URI: ${uri.href}`);
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
contents: [
|
|
219
|
+
{
|
|
220
|
+
uri: uri.href,
|
|
221
|
+
text: guide.content
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
return server;
|
|
228
|
+
}
|
|
229
|
+
async function startServer() {
|
|
230
|
+
const server = createServer();
|
|
231
|
+
const transport = new StdioServerTransport();
|
|
232
|
+
await server.connect(transport);
|
|
233
|
+
console.error("@xferops/design-guide MCP server running on stdio");
|
|
234
|
+
}
|
|
235
|
+
var entryPath = process.argv[1];
|
|
236
|
+
if (entryPath && fileURLToPath(import.meta.url) === entryPath) {
|
|
237
|
+
void startServer().catch((error) => {
|
|
238
|
+
console.error("Fatal error in @xferops/design-guide:", error);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
export {
|
|
243
|
+
createServer,
|
|
244
|
+
startServer
|
|
245
|
+
};
|
|
246
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../package.json"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport { McpServer, ResourceTemplate } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { z } from \"zod\";\nimport packageJson from \"../package.json\";\nimport { getGuideById, listGuides } from \"./guides/catalog\";\nimport { searchGuides } from \"./guides/search\";\n\nexport function createServer() {\n const server = new McpServer(\n {\n name: \"@xferops/design-guide\",\n version: packageJson.version\n },\n {\n capabilities: {\n logging: {}\n }\n }\n );\n\n server.registerTool(\n \"search_guides\",\n {\n title: \"Search Design Guides\",\n description: \"Search the XferOps frontend design guide catalog by keyword, tag, or area.\",\n inputSchema: z.object({\n query: z.string().describe(\"Keywords such as table sorting, forms, overlays, or navigation.\")\n }),\n outputSchema: z.object({\n guides: z.array(\n z.object({\n id: z.string(),\n title: z.string(),\n summary: z.string(),\n tags: z.array(z.string()),\n uri: z.string(),\n sourcePath: z.string().optional()\n })\n )\n })\n },\n async ({ query }) => {\n const guides = (await searchGuides(query)).map((guide) => ({\n id: guide.id,\n title: guide.title,\n summary: guide.summary,\n tags: guide.tags,\n uri: guide.uri,\n sourcePath: guide.sourcePath\n }));\n\n return {\n content: [\n {\n type: \"text\",\n text: guides.length\n ? guides.map((guide) => `${guide.title} (${guide.uri})`).join(\"\\n\")\n : \"No guides matched the query.\"\n }\n ],\n structuredContent: { guides }\n };\n }\n );\n\n server.registerTool(\n \"get_guide\",\n {\n title: \"Get Design Guide\",\n description: \"Read a guide by id such as table/sorting or by URI such as guide://table/sorting.\",\n inputSchema: z.object({\n identifier: z.string().describe(\"Guide id or guide URI.\")\n }),\n outputSchema: z.object({\n id: z.string(),\n title: z.string(),\n summary: z.string(),\n uri: z.string(),\n content: z.string(),\n sourcePath: z.string().optional()\n })\n },\n async ({ identifier }) => {\n const guideId = identifier.startsWith(\"guide://\")\n ? identifier.replace(\"guide://\", \"\")\n : identifier.replace(/^\\/+/, \"\");\n const guide = await getGuideById(guideId);\n\n if (!guide) {\n return {\n content: [\n {\n type: \"text\",\n text: `Guide not found: ${identifier}`\n }\n ],\n isError: true\n };\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: guide.content\n }\n ],\n structuredContent: {\n id: guide.id,\n title: guide.title,\n summary: guide.summary,\n uri: guide.uri,\n content: guide.content,\n sourcePath: guide.sourcePath\n }\n };\n }\n );\n\n server.registerResource(\n \"guide-catalog\",\n \"guide://catalog\",\n {\n title: \"Guide Catalog\",\n description: \"Catalog of available XferOps frontend design guides.\",\n mimeType: \"application/json\"\n },\n async (uri) => {\n const guides = await listGuides();\n\n return {\n contents: [\n {\n uri: uri.href,\n text: JSON.stringify(\n guides.map((guide) => ({\n id: guide.id,\n title: guide.title,\n summary: guide.summary,\n tags: guide.tags,\n uri: guide.uri,\n sourcePath: guide.sourcePath\n })),\n null,\n 2\n )\n }\n ]\n };\n }\n );\n\n server.registerResource(\n \"guide-document\",\n new ResourceTemplate(\"guide://{category}/{slug}\", {\n list: async () => {\n const guides = await listGuides();\n\n return {\n resources: guides.map((guide) => ({\n uri: guide.uri,\n name: guide.title,\n description: guide.summary,\n mimeType: \"text/markdown\"\n }))\n };\n }\n }),\n {\n title: \"Guide Document\",\n description: \"A markdown guide for a specific frontend topic.\",\n mimeType: \"text/markdown\"\n },\n async (uri, { category, slug }) => {\n const guide = await getGuideById(`${category}/${slug}`);\n\n if (!guide) {\n throw new Error(`Guide not found for URI: ${uri.href}`);\n }\n\n return {\n contents: [\n {\n uri: uri.href,\n text: guide.content\n }\n ]\n };\n }\n );\n\n return server;\n}\n\nexport async function startServer() {\n const server = createServer();\n const transport = new StdioServerTransport();\n\n await server.connect(transport);\n console.error(\"@xferops/design-guide MCP server running on stdio\");\n}\n\nconst entryPath = process.argv[1];\n\nif (entryPath && fileURLToPath(import.meta.url) === entryPath) {\n void startServer().catch((error) => {\n console.error(\"Fatal error in @xferops/design-guide:\", error);\n process.exit(1);\n });\n}\n","{\n \"name\": \"@xferops/design-guide\",\n \"version\": \"0.1.0\",\n \"private\": false,\n \"type\": \"module\",\n \"main\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"bin\": {\n \"xferops-design-guide-mcp\": \"./dist/server.js\"\n },\n \"files\": [\n \"dist\",\n \"guides\"\n ],\n \"publishConfig\": {\n \"access\": \"public\",\n \"registry\": \"https://registry.npmjs.org/\"\n },\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"./server\": {\n \"types\": \"./dist/server.d.ts\",\n \"default\": \"./dist/server.js\"\n }\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"typecheck\": \"tsc -p tsconfig.json --noEmit\",\n \"test\": \"vitest run\"\n },\n \"dependencies\": {\n \"@modelcontextprotocol/sdk\": \"^1.20.2\",\n \"zod\": \"^4.1.11\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^24.5.2\",\n \"tsx\": \"^4.20.6\"\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: feedback/status-states
|
|
3
|
+
title: Status, Empty, and Loading States
|
|
4
|
+
summary: Use Alert, Badge, Spinner, EmptyState, and Skeleton for user feedback and progressive loading.
|
|
5
|
+
category: feedback
|
|
6
|
+
slug: status-states
|
|
7
|
+
tags:
|
|
8
|
+
- feedback
|
|
9
|
+
- alert
|
|
10
|
+
- badge
|
|
11
|
+
- empty-state
|
|
12
|
+
- skeleton
|
|
13
|
+
- loading
|
|
14
|
+
sourcePath: apps/docs/src/App.tsx
|
|
15
|
+
relatedPaths:
|
|
16
|
+
- packages/ui/src/components/Alert
|
|
17
|
+
- packages/ui/src/components/Badge
|
|
18
|
+
- packages/ui/src/components/Spinner
|
|
19
|
+
- packages/ui/src/components/EmptyState
|
|
20
|
+
- packages/ui/src/components/Skeleton
|
|
21
|
+
- packages/ui/src/components/Button
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# Status, Empty, and Loading States
|
|
25
|
+
|
|
26
|
+
These primitives cover the common feedback cases around status messaging, loading, and empty datasets.
|
|
27
|
+
|
|
28
|
+
## Import the Feedback Components
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import {
|
|
32
|
+
Alert,
|
|
33
|
+
Badge,
|
|
34
|
+
Button,
|
|
35
|
+
EmptyState,
|
|
36
|
+
Skeleton,
|
|
37
|
+
Spinner,
|
|
38
|
+
Stack,
|
|
39
|
+
Text
|
|
40
|
+
} from "@xferops/ui";
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Use `Alert` and `Badge` for Status
|
|
44
|
+
|
|
45
|
+
Use `Alert` for explanatory messaging and `Badge` for compact labels inside other UI.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
<Stack gap="sm">
|
|
49
|
+
<Alert
|
|
50
|
+
tone="warning"
|
|
51
|
+
title="API key expires soon"
|
|
52
|
+
description="Rotate the credential before Friday to avoid sync failures."
|
|
53
|
+
/>
|
|
54
|
+
|
|
55
|
+
<Stack direction="row" gap="sm" wrap>
|
|
56
|
+
<Badge>Draft</Badge>
|
|
57
|
+
<Badge tone="accent">Live</Badge>
|
|
58
|
+
<Badge tone="success">Paid</Badge>
|
|
59
|
+
<Badge tone="danger">Overdue</Badge>
|
|
60
|
+
</Stack>
|
|
61
|
+
</Stack>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Use `Spinner` and `Skeleton` for Loading
|
|
65
|
+
|
|
66
|
+
Choose `Spinner` when work is in progress and `Skeleton` when the shape of incoming content is already known.
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
<Stack gap="md">
|
|
70
|
+
<Stack direction="row" align="center" gap="sm">
|
|
71
|
+
<Spinner size="sm" />
|
|
72
|
+
<Text as="span" size="sm" tone="muted">
|
|
73
|
+
Syncing workspace data
|
|
74
|
+
</Text>
|
|
75
|
+
</Stack>
|
|
76
|
+
|
|
77
|
+
<Stack gap="xs">
|
|
78
|
+
<Skeleton height={14} width="35%" />
|
|
79
|
+
<Skeleton height={12} width="80%" />
|
|
80
|
+
<Skeleton height={12} width="72%" />
|
|
81
|
+
</Stack>
|
|
82
|
+
</Stack>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Use `EmptyState` for No-Data Screens
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
<EmptyState
|
|
89
|
+
iconText="[]"
|
|
90
|
+
titleText="No invoices yet"
|
|
91
|
+
descriptionText="Create your first invoice to get paid faster."
|
|
92
|
+
actionsContent={
|
|
93
|
+
<>
|
|
94
|
+
<Button size="sm">Create invoice</Button>
|
|
95
|
+
<Button size="sm" variant="secondary">
|
|
96
|
+
View docs
|
|
97
|
+
</Button>
|
|
98
|
+
</>
|
|
99
|
+
}
|
|
100
|
+
/>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Notes
|
|
104
|
+
|
|
105
|
+
- Prefer the narrowest primitive that matches the job: `Badge` for inline state, `Alert` for message blocks, `EmptyState` for whole-section fallback.
|
|
106
|
+
- `Skeleton` is decorative and already `aria-hidden`, so pair it with real loading announcements when the wait matters.
|
|
107
|
+
- Keep action content inside `EmptyState` short and task-oriented.
|