@depthark/css-first
Version:
Advanced MCP server for intelligent, context-aware CSS suggestions with logical units, container queries, and automated feature discovery
656 lines (539 loc) • 17.1 kB
text/typescript
/**
* Automated CSS Feature Discovery using context7 MCP
* Discovers and categorizes recent CSS features from MDN documentation
*/
/* eslint-disable no-console */
import { CSSFeature, CSSFeatureCategory } from './types.js';
/** Context7 MCP integration for MDN documentation */
interface Context7Response {
content: string;
metadata?: {
title?: string;
lastModified?: string;
browserSupport?: any;
};
}
/** Recent CSS features discovered from MDN (2021-2025) */
interface DiscoveredFeature {
name: string;
properties: string[];
description: string;
category: CSSFeatureCategory;
support_level: 'excellent' | 'good' | 'moderate' | 'limited' | 'experimental';
mdn_url: string;
year_introduced?: number;
specification?: string;
}
/**
* CSS feature discovery patterns for categorization
*/
const DISCOVERY_PATTERNS = {
[CSSFeatureCategory.LAYOUT]: {
keywords: ['layout', 'grid', 'flex', 'container', 'subgrid', 'masonry'],
properties: ['display', 'grid-', 'flex-', 'container-', 'subgrid', 'masonry']
},
[CSSFeatureCategory.ANIMATION]: {
keywords: ['animation', 'transition', 'transform', 'scroll', 'timeline', 'view'],
properties: ['animation-', 'transition-', 'transform-', 'scroll-timeline', 'view-timeline']
},
[CSSFeatureCategory.RESPONSIVE]: {
keywords: ['responsive', 'container', 'query', 'viewport', 'dynamic'],
properties: ['container-', '@container', 'cq-', 'dvh', 'svh', 'lvh']
},
[CSSFeatureCategory.VISUAL]: {
keywords: ['color', 'gradient', 'shadow', 'filter', 'backdrop', 'accent'],
properties: ['color-', 'gradient-', 'shadow-', 'filter-', 'backdrop-', 'accent-color']
},
[CSSFeatureCategory.INTERACTION]: {
keywords: ['focus', 'hover', 'user', 'pointer', 'touch', 'selection'],
properties: ['focus-', 'user-', 'pointer-', 'touch-', 'selection-']
},
[CSSFeatureCategory.LOGICAL]: {
keywords: ['logical', 'inline', 'block', 'start', 'end', 'writing'],
properties: ['inline-', 'block-', '-inline-', '-block-', 'writing-mode']
}
};
/**
* Recent CSS features to discover and integrate
*/
const RECENT_CSS_FEATURES_TO_DISCOVER = [
// Container Queries (2022)
'CSS_Container_Queries',
'container-type',
'container-name',
'@container',
// CSS Cascade Layers (2022)
'CSS_Cascade_Layers',
'@layer',
// CSS Grid Level 2 (2021-2023)
'CSS_Grid_Layout_2',
'subgrid',
'masonry',
// CSS Color Level 4 & 5 (2021-2024)
'CSS_Color_4',
'CSS_Color_5',
'color-mix',
'color-contrast',
'accent-color',
'color-scheme',
// CSS Viewport Units (2022-2023)
'CSS_Values_4',
'dvh',
'dvw',
'svh',
'svw',
'lvh',
'lvw',
// CSS Scroll-driven Animations (2023-2024)
'CSS_Scroll_driven_Animations',
'scroll-timeline',
'view-timeline',
'animation-timeline',
// CSS Nesting (2023)
'CSS_Nesting',
// CSS :has() selector (2022)
':has',
// CSS Math Functions (2021-2023)
'CSS_Functions',
'clamp',
'min',
'max',
'round',
'mod',
'rem',
// CSS Logical Properties Level 1 (2021-2022)
'CSS_Logical_Properties',
'margin-inline',
'margin-block',
'padding-inline',
'padding-block',
'border-inline',
'border-block',
// CSS Flexbox Gap (2021)
'gap',
'row-gap',
'column-gap',
// CSS aspect-ratio (2021)
'aspect-ratio',
// CSS text-decoration-thickness (2021)
'text-decoration-thickness',
'text-underline-offset',
// CSS backdrop-filter wider support (2021-2022)
'backdrop-filter',
// CSS font-variant-* properties (2021-2023)
'font-variant-alternates',
'font-variant-east-asian',
'font-variant-ligatures',
// CSS overscroll-behavior (2021)
'overscroll-behavior',
'overscroll-behavior-x',
'overscroll-behavior-y',
// CSS scroll-behavior: smooth (2021)
'scroll-behavior',
// CSS conic-gradient (2021)
'conic-gradient',
// CSS image-set() (2021-2022)
'image-set',
// CSS env() function (2021-2022)
'env',
// CSS prefers-* media queries (2021-2023)
'prefers-color-scheme',
'prefers-reduced-motion',
'prefers-contrast',
'prefers-reduced-transparency',
// CSS forced-colors (2022)
'forced-colors',
// CSS light-dark() function (2024)
'light-dark',
// CSS anchor positioning (2024)
'CSS_Anchor_Positioning',
'anchor',
'position-anchor',
'anchor-name',
// CSS view-transition-name (2023-2024)
'view-transition-name',
'view-transition-class'
];
/**
* Fetches MDN documentation using context7 MCP
*/
async function fetchMDNWithContext7(topic: string): Promise<Context7Response | null> {
try {
// This would be the actual context7 integration
// For now, we'll simulate the response structure
const _mdnUrl = `https://developer.mozilla.org/en-US/docs/Web/CSS/${topic}`;
// Simulate context7 response with structured MDN data
// In real implementation, this would use the actual context7 MCP
const simulatedResponse: Context7Response = {
content: await simulateContext7Response(topic),
metadata: {
title: `CSS: ${topic}`,
lastModified: new Date().toISOString(),
browserSupport: {}
}
};
return simulatedResponse;
} catch (error) {
console.warn(`Failed to fetch ${topic} from context7:`, error);
return null;
}
}
/**
* Simulates context7 response for development
* In production, this would be replaced with actual context7 integration
*/
async function simulateContext7Response(topic: string): Promise<string> {
// This simulates structured MDN content that context7 would provide
const mockResponses: Record<string, string> = {
'CSS_Container_Queries': `
Container queries allow you to apply styles based on the size of a containing element rather than the viewport.
## Properties:
- container-type: Sets the type of containment for container queries
- container-name: Establishes container query names
- container: Shorthand for container-type and container-name
## Browser Support:
Chrome 105+, Firefox 110+, Safari 16+
## Examples:
.card {
container-type: inline-size;
container-name: card;
}
@container card (min-width: 400px) {
.card-content {
display: grid;
grid-template-columns: 1fr 1fr;
}
}
`,
'subgrid': `
CSS Subgrid allows grid items to participate in the sizing of their parent grid.
## Properties:
- grid-template-columns: subgrid
- grid-template-rows: subgrid
## Browser Support:
Firefox 71+, Safari 16+, Chrome 117+
## Examples:
.parent {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
.child {
display: grid;
grid-template-columns: subgrid;
grid-column: span 3;
}
`,
'color-mix': `
The color-mix() function mixes two colors in a specified color space.
## Properties:
- color-mix()
## Browser Support:
Chrome 111+, Firefox 113+, Safari 16.2+
## Examples:
.element {
background-color: color-mix(in srgb, red 50%, blue);
color: color-mix(in oklch, var(--primary) 80%, white);
}
`,
'dvh': `
Dynamic viewport units adjust to the actual viewport size, accounting for mobile browser UI.
## Properties:
- dvh (dynamic viewport height)
- dvw (dynamic viewport width)
- svh (small viewport height)
- svw (small viewport width)
- lvh (large viewport height)
- lvw (large viewport width)
## Browser Support:
Chrome 108+, Firefox 101+, Safari 15.4+
## Examples:
.hero {
height: 100dvh; /* Adjusts for mobile browser UI */
}
.sidebar {
min-height: 100svh; /* Small viewport height */
}
`,
'scroll-timeline': `
Scroll-driven animations allow CSS animations to be driven by scroll progress.
## Properties:
- scroll-timeline
- scroll-timeline-name
- scroll-timeline-axis
- animation-timeline
## Browser Support:
Chrome 115+ (experimental), Firefox (experimental)
## Examples:
.progress {
scroll-timeline: --page-scroll block;
}
.indicator {
animation: grow 1s linear;
animation-timeline: --page-scroll;
}
@keyframes grow {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
`,
':has': `
The :has() pseudo-class represents elements that contain specific descendants.
## Selectors:
- :has()
## Browser Support:
Chrome 105+, Firefox 121+, Safari 15.4+
## Examples:
/* Style cards that contain images */
.card:has(img) {
border: 2px solid blue;
}
/* Style forms with invalid inputs */
form:has(input:invalid) {
border-color: red;
}
/* Parent styling based on child state */
article:has(h2) {
margin-top: 2rem;
}
`,
'CSS_Nesting': `
CSS Nesting allows writing nested CSS rules, similar to preprocessors.
## Browser Support:
Chrome 112+, Firefox 117+, Safari 16.5+
## Examples:
.card {
padding: 1rem;
border: 1px solid #ccc;
& h2 {
margin-top: 0;
color: #333;
}
& .content {
margin: 1rem 0;
& p {
line-height: 1.6;
}
}
&:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
}
`,
'accent-color': `
The accent-color property allows customizing the accent color of form controls.
## Properties:
- accent-color
## Browser Support:
Chrome 93+, Firefox 92+, Safari 15.4+
## Examples:
:root {
accent-color: #007acc;
}
input[type="checkbox"] {
accent-color: green;
}
input[type="radio"] {
accent-color: red;
}
`,
'aspect-ratio': `
The aspect-ratio property sets the preferred aspect ratio for an element.
## Properties:
- aspect-ratio
## Browser Support:
Chrome 88+, Firefox 89+, Safari 15+
## Examples:
.video-container {
aspect-ratio: 16 / 9;
width: 100%;
}
.square {
aspect-ratio: 1;
width: 200px;
}
.golden-ratio {
aspect-ratio: 1.618;
}
`,
'backdrop-filter': `
The backdrop-filter property applies graphical effects to the area behind an element.
## Properties:
- backdrop-filter
## Browser Support:
Chrome 76+, Firefox 103+, Safari 9+
## Examples:
.glass {
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.1);
}
.frosted {
backdrop-filter: blur(5px) saturate(180%);
}
`
};
return mockResponses[topic] || `No documentation found for ${topic}`;
}
/**
* Categorizes a CSS feature based on its content and properties
*/
function categorizeCSSFeature(name: string, content: string, properties: string[]): CSSFeatureCategory {
const lowerContent = content.toLowerCase();
const lowerName = name.toLowerCase();
// Check each category pattern
for (const [category, patterns] of Object.entries(DISCOVERY_PATTERNS)) {
const keywordMatch = patterns.keywords.some(keyword =>
lowerContent.includes(keyword) || lowerName.includes(keyword)
);
const propertyMatch = patterns.properties.some(prop =>
properties.some(p => p.toLowerCase().includes(prop.toLowerCase()))
);
if (keywordMatch || propertyMatch) {
return category as CSSFeatureCategory;
}
}
// Default categorization based on common patterns
if (lowerContent.includes('container') || lowerContent.includes('query')) {
return CSSFeatureCategory.RESPONSIVE;
}
if (lowerContent.includes('animation') || lowerContent.includes('scroll')) {
return CSSFeatureCategory.ANIMATION;
}
if (lowerContent.includes('color') || lowerContent.includes('backdrop')) {
return CSSFeatureCategory.VISUAL;
}
if (lowerContent.includes('grid') || lowerContent.includes('flex') || lowerContent.includes('layout')) {
return CSSFeatureCategory.LAYOUT;
}
return CSSFeatureCategory.VISUAL; // Default fallback
}
/**
* Extracts CSS properties from MDN content
*/
function extractCSSProperties(content: string): string[] {
const properties: string[] = [];
// Extract from ## Properties: sections
const propertySection = content.match(/## Properties:?\s*([\s\S]*?)(?=\n##|\n\n|$)/i);
if (propertySection) {
const propertyMatches = propertySection[1].match(/^\s*-\s*([a-zA-Z-]+)/gm);
if (propertyMatches) {
properties.push(...propertyMatches.map(match => match.replace(/^\s*-\s*/, '')));
}
}
// Extract from ## Selectors: sections
const selectorSection = content.match(/## Selectors:?\s*([\s\S]*?)(?=\n##|\n\n|$)/i);
if (selectorSection) {
const selectorMatches = selectorSection[1].match(/^\s*-\s*([a-zA-Z:()_-]+)/gm);
if (selectorMatches) {
properties.push(...selectorMatches.map(match => match.replace(/^\s*-\s*/, '')));
}
}
// Extract from code examples
const codeBlocks = content.match(/```[\s\S]*?```/g) || [];
for (const block of codeBlocks) {
const cssProps = block.match(/([a-zA-Z-]+)\s*:/g);
if (cssProps) {
properties.push(...cssProps.map(prop => prop.replace(':', '').trim()));
}
}
return [...new Set(properties)]; // Remove duplicates
}
/**
* Determines browser support level from MDN content
*/
function determineSupportLevel(content: string): 'excellent' | 'good' | 'moderate' | 'limited' | 'experimental' {
const supportSection = content.match(/## Browser Support:?\s*([\s\S]*?)(?=\n##|\n\n|$)/i);
if (!supportSection) return 'experimental';
const support = supportSection[1].toLowerCase();
// Check for experimental indicators
if (support.includes('experimental') || support.includes('behind flag') || support.includes('draft')) {
return 'experimental';
}
// Count major browsers with support
const browsers = ['chrome', 'firefox', 'safari', 'edge'];
const supportedBrowsers = browsers.filter(browser => {
const regex = new RegExp(`${browser}\\s*\\d+`, 'i');
return regex.test(support);
});
const supportRatio = supportedBrowsers.length / browsers.length;
if (supportRatio >= 0.75) return 'excellent';
if (supportRatio >= 0.5) return 'good';
if (supportRatio >= 0.25) return 'moderate';
return 'limited';
}
/**
* Discovers and categorizes recent CSS features from MDN
*/
export async function discoverRecentCSSFeatures(): Promise<DiscoveredFeature[]> {
const discoveredFeatures: DiscoveredFeature[] = [];
console.log('🔍 Discovering recent CSS features from MDN...');
for (const feature of RECENT_CSS_FEATURES_TO_DISCOVER) {
try {
console.log(`📡 Fetching ${feature}...`);
const response = await fetchMDNWithContext7(feature);
if (!response) {
console.warn(`⚠️ No data found for ${feature}`);
continue;
}
const properties = extractCSSProperties(response.content);
const category = categorizeCSSFeature(feature, response.content, properties);
const supportLevel = determineSupportLevel(response.content);
const discoveredFeature: DiscoveredFeature = {
name: feature.replace(/_/g, ' '),
properties: properties.length > 0 ? properties : [feature],
description: response.content.split('\n')[0] || `CSS feature: ${feature}`,
category,
support_level: supportLevel,
mdn_url: `https://developer.mozilla.org/en-US/docs/Web/CSS/${feature}`,
year_introduced: 2021 // We could extract this from MDN content
};
discoveredFeatures.push(discoveredFeature);
console.log(`✅ Discovered ${feature} -> ${category} (${supportLevel})`);
} catch {
console.error(`❌ Error discovering ${feature}`);
}
}
console.log(`🎉 Discovered ${discoveredFeatures.length} CSS features`);
return discoveredFeatures;
}
/**
* Converts discovered features to the standard CSSFeature format
*/
export function convertToStandardFeatures(discoveredFeatures: DiscoveredFeature[]): Record<string, CSSFeature> {
const features: Record<string, CSSFeature> = {};
for (const discovered of discoveredFeatures) {
const key = discovered.name.toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_]/g, '');
features[key] = {
name: discovered.name,
category: discovered.category,
properties: discovered.properties,
description: discovered.description,
support_level: discovered.support_level,
mdn_url: discovered.mdn_url
};
}
return features;
}
/**
* Main function to discover and integrate recent CSS features
*/
export async function updateCSSFeaturesFromMDN(): Promise<Record<string, CSSFeature>> {
try {
console.log('🚀 Starting automated CSS feature discovery...');
const discoveredFeatures = await discoverRecentCSSFeatures();
const standardFeatures = convertToStandardFeatures(discoveredFeatures);
console.log(`📊 Successfully integrated ${Object.keys(standardFeatures).length} features`);
// Log categorization summary
const categorySummary: Record<string, number> = {};
Object.values(standardFeatures).forEach(feature => {
categorySummary[feature.category] = (categorySummary[feature.category] || 0) + 1;
});
console.log('📋 Feature categorization summary:');
Object.entries(categorySummary).forEach(([category, count]) => {
console.log(` ${category}: ${count} features`);
});
return standardFeatures;
} catch (error) {
console.error('❌ Failed to discover CSS features:', error);
return {};
}
}