UNPKG

@depthark/css-first

Version:

Advanced MCP server for intelligent, context-aware CSS suggestions with logical units, container queries, and automated feature discovery

686 lines (596 loc) 20.1 kB
/** * Enhanced MDN client with context7 integration for CSS documentation */ import { BrowserSupportInfo, CSSPropertyDetails } from "./types.js"; /** Base URL for MDN CSS documentation */ const MDN_CSS_BASE_URL = "https://developer.mozilla.org/en-US/docs/Web/CSS"; /** Cache for MDN data to improve performance */ const mdnCache = new Map<string, { data: any; timestamp: number }>(); const CACHE_TTL = 1000 * 60 * 60; // 1 hour /** * Checks if cached data is still valid */ function isCacheValid(cacheEntry: { data: any; timestamp: number }): boolean { return Date.now() - cacheEntry.timestamp < CACHE_TTL; } /** * Fetches MDN data using context7 tool with fallback to direct API * @param cssProperty - The CSS property name to fetch documentation for * @returns Promise that resolves to structured MDN data */ export async function fetchMDNData(cssProperty: string): Promise<any> { const cacheKey = `mdn_${cssProperty}`; const cached = mdnCache.get(cacheKey); if (cached && isCacheValid(cached)) { return cached.data; } try { // Try to use context7 tool for MDN data (this would be implemented with actual context7 integration) const mdnData = await fetchFromContext7(cssProperty); // Cache the result mdnCache.set(cacheKey, { data: mdnData, timestamp: Date.now() }); return mdnData; } catch (error) { // Fallback to direct MDN scraping if context7 fails console.warn( `Context7 failed for ${cssProperty}, falling back to direct fetch:`, error ); return await fetchMDNPageDirect(cssProperty); } } /** * Fetches MDN data using context7 MCP tool */ async function fetchFromContext7(cssProperty: string): Promise<any> { try { // Check if we're in an environment where context7 MCP is available if ( typeof globalThis !== "undefined" && (globalThis as any).mcpTools?.context7 ) { // Use actual context7 MCP tool const context7 = (globalThis as any).mcpTools.context7; const mdnUrl = `https://developer.mozilla.org/en-US/docs/Web/CSS/${cssProperty}`; const response = await context7.fetch({ url: mdnUrl, format: "structured", }); if (response && response.content) { return parseContext7Response(response.content, cssProperty); } } // If context7 is not available, try to call it as an external MCP const response = await callContext7MCP(cssProperty); if (response) { return response; } // Fallback to simulated response for development return await simulateContext7Response(cssProperty); } catch (error) { console.warn(`Context7 fetch failed for ${cssProperty}:`, error); throw error; } } /** * Call context7 as external MCP service */ async function callContext7MCP(cssProperty: string): Promise<any> { try { // This would be the actual implementation to call context7 MCP // For now, we simulate the call const _mdnUrl = `https://developer.mozilla.org/en-US/docs/Web/CSS/${cssProperty}`; // Simulate context7 MCP response structure const mockResponse = { content: await getActualMDNContent(cssProperty), metadata: { source: "MDN", lastModified: new Date().toISOString(), url: _mdnUrl, }, }; return parseContext7Response(mockResponse.content, cssProperty); } catch (error) { console.warn(`External context7 MCP call failed:`, error); return null; } } /** * Parse context7 response into our expected format */ function parseContext7Response(content: string, cssProperty: string): any { const parsed = { property: cssProperty, description: extractDescription(content), syntax: extractSyntax(content, cssProperty), browserSupport: extractBrowserSupport(content), examples: extractExamples(content), relatedProperties: extractRelatedProperties(content), }; return parsed; } /** * Extract description from MDN content */ function extractDescription(content: string): string { // Look for the first paragraph after the title const descMatch = content.match(/^([^.]+\.)/m); if (descMatch) { return descMatch[1].trim(); } // Fallback to first sentence const sentences = content.split(/[.!?]+/); return sentences[0]?.trim() + "." || "CSS property description"; } /** * Extract syntax information from MDN content */ function extractSyntax(content: string, property: string): string { // Look for syntax section const syntaxMatch = content.match( /(?:Syntax|Formal syntax)[:\s]*([\s\S]*?)(?:\n\n|\n#|$)/i ); if (syntaxMatch) { const syntax = syntaxMatch[1].trim(); // Clean up common formatting return syntax .replace(/```\w*\n?/g, "") .replace(/\n+/g, " ") .trim(); } return `${property}: <value>`; } /** * Extract browser support from MDN content */ function extractBrowserSupport(content: string): any { const support: any = { overall_support: 80, modern_browsers: true, legacy_support: "good", }; // Look for browser compatibility section const browserMatch = content.match( /(?:Browser compatibility|Browser support)[:\s]*([\s\S]*?)(?:\n\n|\n#|$)/i ); if (browserMatch) { const browserSection = browserMatch[1].toLowerCase(); // Extract version numbers const chromeMatch = browserSection.match(/chrome[:\s]*(\d+)/); const firefoxMatch = browserSection.match(/firefox[:\s]*(\d+)/); const safariMatch = browserSection.match(/safari[:\s]*(\d+)/); if (chromeMatch && firefoxMatch && safariMatch) { // Calculate rough support percentage based on version numbers const chromeVersion = parseInt(chromeMatch[1]); const firefoxVersion = parseInt(firefoxMatch[1]); const safariVersion = parseInt(safariMatch[1]); // Modern versions have better support if ( chromeVersion <= 100 && firefoxVersion <= 100 && safariVersion <= 15 ) { support.overall_support = 95; } else if ( chromeVersion <= 110 && firefoxVersion <= 110 && safariVersion <= 16 ) { support.overall_support = 90; } } } return support; } /** * Extract code examples from MDN content */ function extractExamples(content: string): string[] { const examples: string[] = []; // Extract code blocks const codeBlocks = content.match(/```[\s\S]*?```/g); if (codeBlocks) { examples.push( ...codeBlocks.map((block) => block.replace(/```\w*\n?/g, "").trim()) ); } // Extract inline code examples const inlineCode = content.match(/`[^`]+`/g); if (inlineCode) { examples.push(...inlineCode.map((code) => code.replace(/`/g, "").trim())); } return examples.length > 0 ? examples : ["/* Example usage */"]; } /** * Extract related properties from MDN content */ function extractRelatedProperties(content: string): string[] { const related: string[] = []; // Look for "See also" or "Related" sections const relatedMatch = content.match( /(?:See also|Related|Related properties)[:\s]*([\s\S]*?)(?:\n\n|\n#|$)/i ); if (relatedMatch) { const relatedSection = relatedMatch[1]; const propertyMatches = relatedSection.match(/[a-z]+(?:-[a-z]+)*/g); if (propertyMatches) { related.push( ...propertyMatches.filter( (prop) => prop.length > 2 && prop.includes("-") ) ); } } return [...new Set(related)]; } /** * Get actual MDN content (simplified version for development) */ async function getActualMDNContent(cssProperty: string): Promise<string> { // This would fetch actual MDN content // For now, return enhanced mock content based on the property const enhancedMockContent = await simulateContext7Response(cssProperty); return enhancedMockContent; } /** * Simulate context7 response for development */ async function simulateContext7Response(topic: string): Promise<string> { const mockResponses: Record<string, string> = { "container-type": ` The container-type CSS property establishes the element as a containment context for container queries. ## Syntax container-type: normal | size | inline-size ## Browser Support: Chrome 105+, Firefox 110+, Safari 16+ ## Examples: .sidebar { container-type: inline-size; } .card { container-type: size; } `, "color-mix": ` The color-mix() CSS function takes two color values and returns the result of mixing them in a given colorspace by a given amount. ## Syntax color-mix(in <colorspace>, <color> [<percentage>], <color> [<percentage>]) ## Browser Support: Chrome 111+, Firefox 113+, Safari 16.2+ ## Examples: background: color-mix(in srgb, red 50%, blue); color: color-mix(in oklch, var(--primary) 80%, white); `, dvh: ` Dynamic viewport height (dvh) represents 1% of the dynamic viewport's height. ## Syntax <length-percentage> ## Browser Support: Chrome 108+, Firefox 101+, Safari 15.4+ ## Examples: .hero { height: 100dvh; } `, ":has": ` The :has() CSS pseudo-class represents an element if any of the selectors passed as parameters match at least one element. ## Syntax :has(<relative-selector-list>) ## Browser Support: Chrome 105+, Firefox 121+, Safari 15.4+ ## Examples: .card:has(img) { padding: 0; } `, "corner-shape": ` The corner-shape CSS property defines the shape of element corners using superellipse curves for more organic, rounded designs beyond traditional border-radius. ## Syntax corner-shape: round | angle ## Description The corner-shape property allows developers to create more organic, Apple-like rounded corners using superellipse curves instead of traditional circular arcs. This creates smoother, more visually pleasing corner shapes that feel more natural and modern. ## Values - round: Creates superellipse curves for organic, smooth corners (default behavior with border-radius) - angle: Creates sharp, geometric corners that override border-radius rounding ## Browser Support: Experimental - No current browser support (CSS Borders Level 4 specification) ## Examples: /* Organic Apple-like corners */ .organic-card { corner-shape: round; border-radius: 16px; } /* Sharp geometric corners */ .sharp-button { corner-shape: angle; border-radius: 8px; /* Will be overridden to sharp corners */ } /* Mixed corner shapes */ .mixed-design { corner-shape: round angle round angle; border-radius: 12px 8px 12px 8px; } ## Fallback Strategy: Since corner-shape is experimental, use border-radius as fallback: @supports not (corner-shape: round) { .organic-card { border-radius: 16px; /* Additional styling to approximate superellipse */ } } ## Related Properties: - border-radius - border-top-left-radius - border-top-right-radius - border-bottom-left-radius - border-bottom-right-radius ## Specification: CSS Borders and Box Decoration Module Level 4 https://drafts.csswg.org/css-borders-4/#corner-shape `, }; return ( mockResponses[topic] || ` CSS property: ${topic} ## Description ${topic} is a CSS property or feature. ## Browser Support: Check MDN and caniuse.com for current support information. ## Examples: /* Example usage for ${topic} */ .example { ${topic}: value; } ` ); } /** * Direct MDN page fetching as fallback */ async function fetchMDNPageDirect(cssProperty: string): Promise<string> { const url = `${MDN_CSS_BASE_URL}/${cssProperty}`; const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to fetch MDN page for ${cssProperty}`); } return await response.text(); } /** * Context7 helper functions (placeholders for actual integration) */ async function getBrowserSupportFromContext7( cssProperty: string ): Promise<BrowserSupportInfo> { // This would query context7's MDN data for browser support return { overall_support: getBrowserSupportPercentage(cssProperty), browsers: { chrome: { version: "90+", support: "full" }, firefox: { version: "88+", support: "full" }, safari: { version: "14+", support: "full" }, edge: { version: "90+", support: "full" }, }, experimental_features: [], }; } async function getExamplesFromContext7(cssProperty: string): Promise<string[]> { // This would query context7's MDN data for examples return getPropertyExamples(cssProperty); } async function getRelatedPropertiesFromContext7( cssProperty: string ): Promise<string[]> { // This would query context7's MDN data for related properties return getRelatedProperties(cssProperty); } // These functions are currently unused but kept for future context7 integration void getBrowserSupportFromContext7; void getExamplesFromContext7; void getRelatedPropertiesFromContext7; /** * Enhanced browser support fetching with context7 integration * @param cssProperty - The CSS property to check support for * @param includeExperimental - Whether to include experimental features * @returns Promise that resolves to browser support information */ export async function fetchBrowserSupportFromMDN( cssProperty: string, includeExperimental: boolean = false ): Promise<BrowserSupportInfo> { try { const mdnData = await fetchMDNData(cssProperty); const supportInfo: BrowserSupportInfo = { overall_support: mdnData.browserSupport?.overall_support || getBrowserSupportPercentage(cssProperty), browsers: mdnData.browserSupport?.browsers || { chrome: { version: "90+", support: "full" }, firefox: { version: "88+", support: "full" }, safari: { version: "14+", support: "full" }, edge: { version: "90+", support: "full" }, }, experimental_features: includeExperimental ? getExperimentalFeatures(cssProperty) : [], }; return supportInfo; } catch (error) { // Fallback to static data return { overall_support: getBrowserSupportPercentage(cssProperty), browsers: { chrome: { version: "90+", support: "full" }, firefox: { version: "88+", support: "full" }, safari: { version: "14+", support: "full" }, edge: { version: "90+", support: "full" }, }, experimental_features: includeExperimental ? getExperimentalFeatures(cssProperty) : [], }; } } /** * Fetches comprehensive CSS property details from MDN * @param cssProperty - The CSS property to get details for * @param includeExamples - Whether to include code examples * @returns Promise that resolves to CSS property details */ export async function fetchCSSPropertyDetailsFromMDN( cssProperty: string, includeExamples: boolean = true ): Promise<CSSPropertyDetails> { // Mock implementation - in real scenario, this would parse MDN documentation const mockDetails: CSSPropertyDetails = { description: getPropertyDescription(cssProperty), syntax: getPropertySyntax(cssProperty), values: getPropertyValues(cssProperty), examples: includeExamples ? getPropertyExamples(cssProperty) : [], related_properties: getRelatedProperties(cssProperty), }; return mockDetails; } /** * Helper function to get browser support percentage for a CSS property */ function getBrowserSupportPercentage(cssProperty: string): number { const supportMap: Record<string, number> = { display: 98, flex: 95, grid: 92, "scroll-snap-type": 85, "scroll-snap-align": 85, "overflow-x": 98, transition: 96, transform: 94, "container-type": 75, "container-name": 75, "@container": 75, "::scroll-button()": 15, "::scroll-marker-group": 15, "::scroll-marker": 15, "::column": 15, ":target-current": 20, "corner-shape": 5, // Experimental, very limited support }; return supportMap[cssProperty] || 80; } /** * Helper function to get experimental features for a CSS property */ function getExperimentalFeatures(cssProperty: string): string[] { const experimentalMap: Record<string, string[]> = { "container-type": ["container-query-length", "container-query-inline-size"], "scroll-snap-type": ["scroll-snap-stop"], transform: ["transform-style", "perspective"], "corner-shape": [ "superellipse curves", "organic corner shapes", "CSS Borders Level 4", ], }; return experimentalMap[cssProperty] || []; } /** * Helper function to get property description */ function getPropertyDescription(cssProperty: string): string { const descriptions: Record<string, string> = { display: "Sets the display type of an element", "overflow-x": "Controls horizontal overflow behavior", "scroll-snap-type": "Defines scroll snap behavior", "scroll-snap-align": "Specifies snap alignment", flex: "Defines flexible item properties", grid: "Creates a grid container", transition: "Defines property transitions", transform: "Applies 2D/3D transformations", "corner-shape": "Define the shape of element corners using superellipse curves for more organic, rounded designs beyond traditional border-radius", }; return descriptions[cssProperty] || `CSS property: ${cssProperty}`; } /** * Helper function to get property syntax */ function getPropertySyntax(cssProperty: string): string { const syntax: Record<string, string> = { display: "none | block | inline | flex | grid | ...", "overflow-x": "visible | hidden | scroll | auto", "scroll-snap-type": "none | [ x | y | block | inline | both ] [ mandatory | proximity ]", "scroll-snap-align": "none | start | end | center", flex: "<flex-grow> <flex-shrink> <flex-basis>", transition: "<property> <duration> <timing-function> <delay>", "corner-shape": "round | angle", }; return syntax[cssProperty] || `${cssProperty}: <value>`; } /** * Helper function to get property values */ function getPropertyValues(cssProperty: string): string[] { const values: Record<string, string[]> = { display: ["none", "block", "inline", "flex", "grid", "inline-block"], "overflow-x": ["visible", "hidden", "scroll", "auto"], "scroll-snap-type": [ "none", "x mandatory", "y mandatory", "both mandatory", ], "scroll-snap-align": ["none", "start", "end", "center"], "corner-shape": ["round", "angle"], }; return values[cssProperty] || ["auto", "initial", "inherit"]; } /** * Helper function to get property examples */ function getPropertyExamples(cssProperty: string): string[] { const examples: Record<string, string[]> = { display: [".container { display: flex; }", ".grid { display: grid; }"], "overflow-x": [ ".carousel { overflow-x: scroll; }", ".hidden { overflow-x: hidden; }", ], "scroll-snap-type": [ ".carousel { scroll-snap-type: x mandatory; }", ".gallery { scroll-snap-type: both proximity; }", ], "scroll-snap-align": [ ".carousel-item { scroll-snap-align: center; }", ".slide { scroll-snap-align: start; }", ], "corner-shape": [ ".organic-card { corner-shape: round; border-radius: 16px; }", ".sharp-button { corner-shape: angle; border-radius: 8px; }", ], }; return examples[cssProperty] || [`${cssProperty}: example-value;`]; } /** * Helper function to get related properties */ function getRelatedProperties(cssProperty: string): string[] { const related: Record<string, string[]> = { display: ["position", "float", "clear"], "overflow-x": ["overflow-y", "overflow", "scroll-behavior"], "scroll-snap-type": ["scroll-snap-align", "scroll-behavior", "overflow"], "scroll-snap-align": [ "scroll-snap-type", "scroll-margin", "scroll-padding", ], flex: ["flex-grow", "flex-shrink", "flex-basis", "display"], grid: ["grid-template-columns", "grid-template-rows", "gap", "display"], "corner-shape": [ "border-radius", "border-top-left-radius", "border-top-right-radius", "border-bottom-left-radius", "border-bottom-right-radius", ], }; return related[cssProperty] || []; }