fe-inspector-mcp 0.1.0 → 0.2.0

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 CHANGED
@@ -159,6 +159,40 @@ Same as `inspect_html` but returns structured Markdown fix suggestions that codi
159
159
 
160
160
  List available categories and rule counts.
161
161
 
162
+ ### `review_design` ✨ (hallmark can't do this)
163
+
164
+ **Pre-build design review.** Describe what you're about to build, and get a checklist of common frontend pitfalls BEFORE writing code.
165
+
166
+ **Parameters:**
167
+ - `description` (required): Describe the UI you're planning (e.g. "a login form with email and password, a navbar with dropdown menus, a pricing card grid")
168
+
169
+ **Example:**
170
+ ```
171
+ Agent: review_design description="a landing page with hero section, feature cards, testimonial carousel, and footer form"
172
+
173
+ Result:
174
+ [!] [a11y] MUST: Alt text on all images
175
+ Every <img> needs alt. Decorative images get alt="".
176
+
177
+ [!] [responsive] MUST: Viewport meta tag
178
+ Every page needs <meta name="viewport">.
179
+
180
+ [~] [responsive] SHOULD: Responsive grid breakpoints
181
+ Use grid-template-columns with auto-fit/auto-fill or add @media breakpoints.
182
+
183
+ [~] [interaction] SHOULD: Loading states
184
+ Show skeleton screens or spinners during data fetch.
185
+
186
+ [~] [semantic] SHOULD: Semantic HTML structure
187
+ Use <header>, <main>, <section> instead of generic <div>s.
188
+
189
+ ---
190
+
191
+ 3 must-fix, 4 should-fix, 7 total points to review.
192
+ ```
193
+
194
+ **Why this matters:** hallmark only checks code after it's written. `review_design` checks your intent before you write a single line — catching issues at the design stage where they're cheapest to fix.
195
+
162
196
  ## Usage with agents
163
197
 
164
198
  **Claude Code:**
@@ -0,0 +1,11 @@
1
+ export interface DesignReviewRequest {
2
+ description: string;
3
+ }
4
+ export interface DesignPoint {
5
+ category: 'a11y' | 'responsive' | 'interaction' | 'performance' | 'semantic';
6
+ severity: 'must' | 'should' | 'consider';
7
+ point: string;
8
+ detail: string;
9
+ }
10
+ export declare function reviewDesign(request: DesignReviewRequest): DesignPoint[];
11
+ export declare function formatReview(points: DesignPoint[]): string;
@@ -0,0 +1,133 @@
1
+ // ── Pre-build design review
2
+ // hallmark checks code AFTER it's written.
3
+ // This checks BEFORE — given a description, it predicts common pitfalls.
4
+ const designPatterns = [
5
+ // ── Forms ──
6
+ {
7
+ triggers: ['form', 'input', 'signup', 'login', 'register', 'contact', 'search box', 'enter', 'submit'],
8
+ points: [
9
+ { category: 'a11y', severity: 'must', point: 'Label every input', detail: 'Use <label for="id"> or aria-label. Placeholder is not a label.' },
10
+ { category: 'interaction', severity: 'must', point: 'Show validation inline', detail: 'Don\'t rely on alert() for errors. Show messages next to the field.' },
11
+ { category: 'a11y', severity: 'must', point: 'Error messages need aria-live', detail: 'Use aria-live="polite" so screen readers announce errors.' },
12
+ { category: 'responsive', severity: 'should', point: 'Inputs fill width on mobile', detail: 'Use width: 100% on small screens so inputs aren\'t tiny.' },
13
+ { category: 'interaction', severity: 'should', point: 'Disable submit while loading', detail: 'Prevent double-submission with disabled state + spinner.' },
14
+ ],
15
+ },
16
+ // ── Navigation / menus ──
17
+ {
18
+ triggers: ['nav', 'menu', 'sidebar', 'header', 'navigation', 'navbar', 'tabs', 'breadcrumb'],
19
+ points: [
20
+ { category: 'a11y', severity: 'must', point: 'Add skip-to-content link', detail: 'First focusable element should be "Skip to main content".' },
21
+ { category: 'a11y', severity: 'must', point: 'Keyboard navigation for menus', detail: 'Use arrow keys for submenu navigation. Escape closes.' },
22
+ { category: 'a11y', severity: 'should', point: 'Current page indicator', detail: 'Mark current page with aria-current="page" on the active link.' },
23
+ { category: 'responsive', severity: 'must', point: 'Mobile hamburger menu', detail: 'On screens < 768px, collapse nav into a toggleable menu.' },
24
+ { category: 'interaction', severity: 'should', point: 'Click outside closes menu', detail: 'Clicking outside an open dropdown/modal should close it.' },
25
+ ],
26
+ },
27
+ // ── Cards / grids / lists ──
28
+ {
29
+ triggers: ['card', 'grid', 'list', 'feed', 'gallery', 'features', 'pricing', 'showcase'],
30
+ points: [
31
+ { category: 'responsive', severity: 'must', point: 'Responsive grid breakpoints', detail: 'Use grid-template-columns with auto-fit/auto-fill or add @media breakpoints for mobile.' },
32
+ { category: 'interaction', severity: 'should', point: 'Clickable cards need visual feedback', detail: 'Add :hover transform/scale and :focus outline to card interactions.' },
33
+ { category: 'a11y', severity: 'should', point: 'Card text contrast', detail: 'Ensure text on colored card backgrounds meets WCAG AA (4.5:1).' },
34
+ { category: 'performance', severity: 'consider', point: 'Lazy-load images', detail: 'Use loading="lazy" on images below the fold.' },
35
+ ],
36
+ },
37
+ // ── Tables / data ──
38
+ {
39
+ triggers: ['table', 'data', 'spreadsheet', 'grid', 'report', 'chart'],
40
+ points: [
41
+ { category: 'semantic', severity: 'must', point: 'Use <th> for headers', detail: 'Header cells must use <th> with scope="col" or scope="row".' },
42
+ { category: 'a11y', severity: 'must', point: 'Add <caption> or aria-label', detail: 'Tables need a caption or aria-label describing their purpose.' },
43
+ { category: 'responsive', severity: 'must', point: 'Horizontally scrollable on mobile', detail: 'Wrap wide tables in overflow-x: auto container.' },
44
+ { category: 'interaction', severity: 'should', point: 'Row hover highlight', detail: 'Add :hover background to table rows for readability.' },
45
+ ],
46
+ },
47
+ // ── Modals / dialogs ──
48
+ {
49
+ triggers: ['modal', 'dialog', 'popup', 'overlay', 'lightbox', 'drawer'],
50
+ points: [
51
+ { category: 'a11y', severity: 'must', point: 'Trap focus inside modal', detail: 'When modal is open, Tab should cycle within it, not behind it.' },
52
+ { category: 'a11y', severity: 'must', point: 'Escape key closes modal', detail: 'Add keydown listener for Escape to close.' },
53
+ { category: 'interaction', severity: 'must', point: 'Backdrop click behavior', detail: 'Clicking the overlay should either close the modal or be clearly NOT clickable.' },
54
+ { category: 'a11y', severity: 'must', point: 'Restore focus on close', detail: 'When modal closes, return focus to the element that opened it.' },
55
+ { category: 'responsive', severity: 'should', point: 'Modal fits small screens', detail: 'On mobile, use full-screen or near-full-screen modal layout.' },
56
+ ],
57
+ },
58
+ // ── Images / media ──
59
+ {
60
+ triggers: ['image', 'photo', 'picture', 'avatar', 'thumbnail', 'carousel', 'slider'],
61
+ points: [
62
+ { category: 'a11y', severity: 'must', point: 'Alt text on all images', detail: 'Every <img> needs alt. Decorative images get alt="".' },
63
+ { category: 'responsive', severity: 'must', point: 'Responsive images', detail: 'Use max-width: 100% or srcset for responsive images.' },
64
+ { category: 'performance', severity: 'should', point: 'Optimize image loading', detail: 'Use width/height attributes to prevent layout shift.' },
65
+ ],
66
+ },
67
+ // ── Buttons / CTAs ──
68
+ {
69
+ triggers: ['button', 'cta', 'action', 'call to action', 'submit', 'click'],
70
+ points: [
71
+ { category: 'interaction', severity: 'must', point: 'Cursor pointer on buttons', detail: 'All clickable elements need cursor: pointer.' },
72
+ { category: 'interaction', severity: 'must', point: 'Hover and focus states', detail: 'Every interactive element needs :hover and :focus visual feedback.' },
73
+ { category: 'a11y', severity: 'must', point: 'Descriptive button text', detail: '"Click here" is bad. "Submit form" or "Open menu" is good.' },
74
+ { category: 'responsive', severity: 'should', point: 'Touch-friendly tap targets', detail: 'Minimum 44x44px tap target size for mobile.' },
75
+ ],
76
+ },
77
+ // ── Text content ──
78
+ {
79
+ triggers: ['article', 'blog', 'content', 'text', 'paragraph', 'typography', 'reading'],
80
+ points: [
81
+ { category: 'a11y', severity: 'must', point: 'Heading hierarchy', detail: 'Use h1 → h2 → h3 in order. Don\'t skip levels.' },
82
+ { category: 'responsive', severity: 'should', point: 'Readable line length', detail: 'Keep text containers at 60-75 characters max-width for readability.' },
83
+ { category: 'a11y', severity: 'should', point: 'Font size minimum', detail: 'Body text should be at least 16px to prevent mobile zoom issues.' },
84
+ ],
85
+ },
86
+ // ── Layout / page structure ──
87
+ {
88
+ triggers: ['landing', 'page', 'layout', 'homepage', 'dashboard', 'app'],
89
+ points: [
90
+ { category: 'responsive', severity: 'must', point: 'Viewport meta tag', detail: 'Every page needs <meta name="viewport" content="width=device-width, initial-scale=1">.' },
91
+ { category: 'semantic', severity: 'should', point: 'Semantic HTML structure', detail: 'Use <header>, <main>, <nav>, <section>, <article>, <footer> instead of generic <div>s.' },
92
+ { category: 'a11y', severity: 'should', point: 'Landmark regions', detail: 'Add aria-label to <nav> and <section> elements when multiple exist.' },
93
+ { category: 'interaction', severity: 'consider', point: 'Loading states', detail: 'Show skeleton screens or spinners during data fetch — don\'t leave users staring at blank space.' },
94
+ ],
95
+ },
96
+ ];
97
+ export function reviewDesign(request) {
98
+ const desc = request.description.toLowerCase();
99
+ const matchedPoints = new Set();
100
+ const results = [];
101
+ for (const pattern of designPatterns) {
102
+ const matches = pattern.triggers.some(t => desc.includes(t));
103
+ if (matches) {
104
+ for (const point of pattern.points) {
105
+ if (!matchedPoints.has(point.point)) {
106
+ matchedPoints.add(point.point);
107
+ results.push(point);
108
+ }
109
+ }
110
+ }
111
+ }
112
+ return results;
113
+ }
114
+ export function formatReview(points) {
115
+ if (points.length === 0) {
116
+ return 'No pre-build review points matched. Describe more UI details for better suggestions.';
117
+ }
118
+ const lines = [];
119
+ lines.push('# Design Pre-Review\n');
120
+ const severityOrder = { must: 0, should: 1, consider: 2 };
121
+ const sorted = [...points].sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
122
+ for (const p of sorted) {
123
+ const icon = p.severity === 'must' ? '[!]' : p.severity === 'should' ? '[~]' : '[i]';
124
+ const tag = `[${p.category}]`;
125
+ lines.push(`${icon} ${tag} ${p.severity.toUpperCase()}: ${p.point}`);
126
+ lines.push(` ${p.detail}`);
127
+ lines.push('');
128
+ }
129
+ const must = points.filter(p => p.severity === 'must').length;
130
+ const should = points.filter(p => p.severity === 'should').length;
131
+ lines.push(`---\n${must} must-fix, ${should} should-fix, ${points.length} total points to review.`);
132
+ return lines.join('\n');
133
+ }
package/dist/index.js CHANGED
@@ -4,7 +4,8 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
4
4
  import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
5
  import { runInspection, getInspectorMeta } from './inspector/index.js';
6
6
  import { generateTextReport, generateJsonReport } from './reporter/index.js';
7
- const { version, name } = { version: '0.1.0', name: 'fe-inspector-mcp' };
7
+ import { reviewDesign, formatReview } from './design-review.js';
8
+ const { version, name } = { version: '0.2.0', name: 'fe-inspector-mcp' };
8
9
  const server = new Server({ name, version }, { capabilities: { tools: {} } });
9
10
  // ── Tool definitions ──
10
11
  server.setRequestHandler(ListToolsRequestSchema, async () => {
@@ -70,6 +71,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
70
71
  required: ['html'],
71
72
  },
72
73
  },
74
+ {
75
+ name: 'review_design',
76
+ description: 'Pre-build design review. Given a description of what you\'re about to build, returns a checklist of common frontend pitfalls to avoid. Use this BEFORE writing code to catch issues early — hallmark only checks after.',
77
+ inputSchema: {
78
+ type: 'object',
79
+ properties: {
80
+ description: {
81
+ type: 'string',
82
+ description: 'Describe what you\'re about to build (e.g. "a login form with email and password, a navbar with dropdown menus, a pricing card grid")',
83
+ },
84
+ },
85
+ required: ['description'],
86
+ },
87
+ },
73
88
  ],
74
89
  };
75
90
  });
@@ -124,6 +139,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
124
139
  content: [{ type: 'text', text: fixLines.join('\n') }],
125
140
  };
126
141
  }
142
+ case 'review_design': {
143
+ const description = String(args?.description || '');
144
+ const points = reviewDesign({ description });
145
+ const output = formatReview(points);
146
+ return {
147
+ content: [{ type: 'text', text: output }],
148
+ };
149
+ }
127
150
  default:
128
151
  throw new Error(`Unknown tool: ${toolName}`);
129
152
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fe-inspector-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "MCP Server for frontend quality inspection — catches aesthetic flaws, a11y issues, responsive breaks, and interaction bugs in AI-generated HTML.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,46 @@
1
+ # Community Rules
2
+
3
+ This directory contains community-contributed inspection rules for FE Inspector MCP.
4
+
5
+ ## How to contribute
6
+
7
+ 1. Create a new `.ts` file in this directory following the rule template below
8
+ 2. Each file should export a `rules` array and a `meta` object
9
+ 3. Submit a PR to the [GitHub repo](https://github.com/Erich956389473/fe-inspector-mcp)
10
+
11
+ ## Rule template
12
+
13
+ ```typescript
14
+ import type { Detection } from '../../src/types.js';
15
+ import { load } from 'cheerio';
16
+
17
+ export const meta = {
18
+ category: 'aesthetic', // aesthetic | a11y | responsive | interaction
19
+ label: 'Your Rule Category',
20
+ description: 'Short description',
21
+ };
22
+
23
+ export const rules = [{
24
+ id: 'community-001', // community-NNN format
25
+ title: 'Your rule title',
26
+ severity: 'warning', // error | warning | info
27
+ check: ($: ReturnType<typeof load>): Detection[] => {
28
+ const results: Detection[] = [];
29
+ // Your detection logic here
30
+ return results;
31
+ },
32
+ }];
33
+ ```
34
+
35
+ ## Guidelines
36
+
37
+ - Use `community-NNN` rule ID format (NNN = 3-digit number)
38
+ - Each rule must have a clear fix recommendation
39
+ - Test your rule against real HTML before submitting
40
+ - Keep rules focused — one check per rule
41
+
42
+ ## Review process
43
+
44
+ 1. PR received → automated test run
45
+ 2. Manual review within 7 days
46
+ 3. Approved rules merged into next release
@@ -35,9 +35,11 @@ Run fe-inspect on the most recent HTML output and report issues.
35
35
  - `inspect_html` — run all checks on HTML, get a full report
36
36
  - `inspect_html_fix` — run all checks, get structured fix suggestions the agent can apply
37
37
  - `inspect_categories` — list available categories and rule counts
38
+ - `review_design` — **before writing code**, describe what you're building and get a checklist of common pitfalls. hallmark can't do this.
38
39
 
39
40
  ## Design principles
40
41
 
41
42
  1. **hallmark covered aesthetic only.** This skill extends to behavior-level checks that hallmark ignores.
42
43
  2. **Report is actionable.** Every detection includes a specific fix recommendation.
43
- 3. **Not a style guide.** It detects anti-patterns, not taste choices.
44
+ 3. **Pre-check before code.** Use `review_design` before writing any HTML to catch issues at design stage.
45
+ 4. **Not a style guide.** It detects anti-patterns, not taste choices.