UNPKG

apple-hig-mcp

Version:

High-performance MCP server providing instant access to Apple's Human Interface Guidelines via hybrid static/dynamic content delivery

317 lines 14 kB
/** * Comprehensive HIG Discovery Service * * Dynamic discovery service that combines multiple strategies: * 1. Known Apple HIG URL patterns and structures * 2. HTTP-based discovery when network allows * 3. Comprehensive fallback based on Apple's documented HIG structure * * This ensures we capture 100+ sections across all Apple platforms. */ export class ComprehensiveHIGDiscoveryService { cache; baseUrl = 'https://developer.apple.com/design/human-interface-guidelines'; cacheKey = 'hig:comprehensive:discovery:sections'; cacheTTL = 14400; // 4 hours constructor(cache) { this.cache = cache; } /** * Discover all HIG sections using comprehensive approach */ async discoverSections() { console.log('[ComprehensiveDiscovery] Starting comprehensive HIG section discovery...'); // Check cache first const cached = this.cache.get(this.cacheKey); if (cached && cached.length > 50) { // Expect 50+ sections minimum console.log(`[ComprehensiveDiscovery] Using cached sections: ${cached.length} sections`); return cached; } const discoveredSections = []; // Strategy 1: Use known comprehensive HIG structure console.log('[ComprehensiveDiscovery] Building sections from known HIG structure...'); const knownSections = this.buildComprehensiveKnownSections(); discoveredSections.push(...knownSections); // Strategy 2: Try HTTP discovery for additional sections (with timeout protection) console.log('[ComprehensiveDiscovery] Attempting HTTP-based discovery...'); try { const httpSections = await Promise.race([ this.tryHttpDiscovery(), new Promise((_, reject) => setTimeout(() => reject(new Error('HTTP discovery timeout')), 10000)) ]); // Merge unique sections from HTTP discovery for (const httpSection of httpSections) { if (!discoveredSections.some(s => s.url === httpSection.url)) { discoveredSections.push(httpSection); } } console.log(`[ComprehensiveDiscovery] HTTP discovery added ${httpSections.length} additional sections`); } catch (error) { console.log(`[ComprehensiveDiscovery] HTTP discovery failed, using known sections only: ${error}`); } // Remove duplicates and validate const uniqueSections = this.removeDuplicates(discoveredSections); console.log(`[ComprehensiveDiscovery] Discovery completed: ${uniqueSections.length} total sections`); // Cache the results this.cache.set(this.cacheKey, uniqueSections, this.cacheTTL); return uniqueSections; } /** * Build comprehensive list of known HIG sections based on Apple's documented structure */ buildComprehensiveKnownSections() { const sections = []; // Universal/Cross-platform sections (foundations) const universalFoundations = [ 'accessibility', 'inclusion', 'privacy', 'branding' ]; const universalLayout = [ 'layout', 'spatial-layout', 'typography', 'color', 'icons', 'images', 'motion' ]; const universalInteraction = [ 'inputs', 'gestures', 'feedback', 'loading', 'onboarding', 'launching' ]; const universalNavigation = [ 'navigation-and-search', 'searching', 'modality' ]; const universalPresentation = [ 'alerts', 'action-sheets', 'activity-views', 'sheets', 'popovers' ]; const universalComponents = [ 'buttons', 'menus', 'toolbars', 'tab-bars', 'navigation-bars', 'sliders', 'steppers', 'toggles', 'pickers', 'progress-indicators', 'labels', 'text-fields', 'text-views', 'lists-and-tables', 'collections', 'scroll-views', 'split-views', 'boxes', 'gauges', 'charts', 'rating-indicators' ]; const universalTechnologies = [ 'app-clips', 'app-shortcuts', 'apple-pay', 'carplay', 'healthkit', 'homekit', 'icloud', 'in-app-purchase', 'machine-learning', 'maps', 'nfc', 'siri', 'wallet', 'augmented-reality', 'game-center', 'live-activities', 'live-photos', 'notifications', 'shareplay', 'sign-in-with-apple', 'tap-to-pay-on-iphone', 'widgets' ]; // Add universal sections this.addSectionsForPlatform(sections, 'universal', universalFoundations, 'foundations'); this.addSectionsForPlatform(sections, 'universal', universalLayout, 'layout'); this.addSectionsForPlatform(sections, 'universal', universalInteraction, 'selection-and-input'); this.addSectionsForPlatform(sections, 'universal', universalNavigation, 'navigation'); this.addSectionsForPlatform(sections, 'universal', universalPresentation, 'presentation'); this.addSectionsForPlatform(sections, 'universal', universalComponents, 'visual-design'); this.addSectionsForPlatform(sections, 'universal', universalTechnologies, 'technologies'); // iOS-specific sections const iosSpecific = [ 'designing-for-ios', 'app-icons', 'home-screen-quick-actions', 'multitasking', 'requesting-permission', 'settings' ]; this.addSectionsForPlatform(sections, 'iOS', iosSpecific, 'foundations'); // macOS-specific sections const macosSpecific = [ 'designing-for-macos', 'the-menu-bar', 'dock-menus', 'column-views', 'outline-views', 'combo-boxes', 'disclosure-controls', 'image-wells', 'path-controls', 'pop-up-buttons', 'pull-down-buttons', 'token-fields', 'color-wells', 'panels', 'going-full-screen', 'printing' ]; this.addSectionsForPlatform(sections, 'macOS', macosSpecific, 'foundations'); // watchOS-specific sections const watchosSpecific = [ 'designing-for-watchos', 'complications', 'watch-faces', 'digital-crown', 'digit-entry-views', 'always-on', 'workouts' ]; this.addSectionsForPlatform(sections, 'watchOS', watchosSpecific, 'foundations'); // tvOS-specific sections const tvosSpecific = [ 'designing-for-tvos', 'focus-and-selection', 'remotes', 'top-shelf' ]; this.addSectionsForPlatform(sections, 'tvOS', tvosSpecific, 'foundations'); // visionOS-specific sections const visionosSpecific = [ 'designing-for-visionos', 'immersive-experiences', 'spatial-layout', 'eyes', 'ornaments', 'materials' ]; this.addSectionsForPlatform(sections, 'visionOS', visionosSpecific, 'foundations'); // Add platform overview pages const platforms = ['ios', 'macos', 'watchos', 'tvos', 'visionos']; for (const platform of platforms) { sections.push({ id: `${platform}-overview`, title: `Designing for ${platform}`, url: `${this.baseUrl}/${platform === 'ios' ? 'platforms/ios' : platform === 'macos' ? 'platforms/macos' : platform === 'watchos' ? 'platforms/watchos' : platform === 'tvos' ? 'platforms/tvos' : 'platforms/visionos'}`, platform: this.normalizePlatform(platform), category: 'foundations', lastUpdated: new Date() }); } return sections; } /** * Helper to add sections for a specific platform */ addSectionsForPlatform(sections, platform, sectionNames, category) { for (const sectionName of sectionNames) { sections.push({ id: `${sectionName}-${platform.toLowerCase()}`, title: this.formatTitle(sectionName), url: `${this.baseUrl}/${sectionName}`, platform: this.normalizePlatform(platform), category, lastUpdated: new Date() }); } } /** * Try HTTP-based discovery as supplementary strategy */ async tryHttpDiscovery() { const sections = []; const platformPages = [ { url: `${this.baseUrl}`, platform: 'universal' }, { url: `${this.baseUrl}/platforms/ios`, platform: 'iOS' }, { url: `${this.baseUrl}/platforms/macos`, platform: 'macOS' } ]; for (const platformPage of platformPages) { try { const response = await fetch(platformPage.url, { headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', 'Accept': 'text/html,application/xhtml+xml', }, signal: globalThis.AbortSignal.timeout(8000) }); if (response.ok) { const html = await response.text(); const discoveredSections = this.parseLinksFromHTML(html, platformPage.platform); sections.push(...discoveredSections); } } catch (error) { console.warn(`[ComprehensiveDiscovery] Failed to fetch ${platformPage.platform}: ${error}`); } } return sections; } /** * Parse links from HTML (simplified version) */ parseLinksFromHTML(html, platform) { const sections = []; const linkPattern = /href="([^"]*\/human-interface-guidelines\/[^"]*)"[^>]*>([^<]+)/g; let match; while ((match = linkPattern.exec(html)) !== null) { const [, url, title] = match; const cleanUrl = url.startsWith('http') ? url : `https://developer.apple.com${url}`; if (!this.shouldSkipUrl(cleanUrl) && title && title.trim()) { const section = this.createSection(cleanUrl, title.trim(), platform); if (section) { sections.push(section); } } } return sections; } /** * Create a HIGSection from URL and title */ createSection(url, title, platform) { try { const urlMatch = url.match(/\/human-interface-guidelines\/(.+)$/); if (!urlMatch) return null; const pathPart = urlMatch[1]; const category = this.inferCategory(pathPart, title); const id = `${pathPart.replace(/[^a-z0-9]/gi, '-')}-${platform.toLowerCase()}`; return { id, title: this.formatTitle(title), url, platform, category, lastUpdated: new Date() }; } catch { return null; } } /** * Infer category from path and title */ inferCategory(path, title) { const combined = (path + ' ' + title).toLowerCase(); if (combined.includes('color') || combined.includes('material')) return 'color-and-materials'; if (combined.includes('typography') || combined.includes('font')) return 'typography'; if (combined.includes('layout') || combined.includes('spatial')) return 'layout'; if (combined.includes('accessibility') || combined.includes('inclusion')) return 'foundations'; if (combined.includes('button') || combined.includes('control') || combined.includes('visual')) return 'visual-design'; if (combined.includes('navigation') || combined.includes('menu') || combined.includes('tab')) return 'navigation'; if (combined.includes('alert') || combined.includes('sheet') || combined.includes('modal')) return 'presentation'; if (combined.includes('text') || combined.includes('input') || combined.includes('field')) return 'selection-and-input'; if (combined.includes('icon') || combined.includes('image') || combined.includes('symbol')) return 'icons-and-images'; if (combined.includes('motion') || combined.includes('animation')) return 'motion'; if (combined.includes('app') || combined.includes('technology') || combined.includes('api')) return 'technologies'; return 'foundations'; } /** * Normalize platform names */ normalizePlatform(platform) { const platformLower = platform.toLowerCase(); switch (platformLower) { case 'ios': return 'iOS'; case 'macos': return 'macOS'; case 'watchos': return 'watchOS'; case 'tvos': return 'tvOS'; case 'visionos': return 'visionOS'; default: return 'universal'; } } /** * Format titles consistently */ formatTitle(title) { return title .replace(/[-_]/g, ' ') .split(' ') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' ') .replace(/\s+/g, ' ') .trim(); } /** * Check if URL should be skipped */ shouldSkipUrl(url) { const skipPatterns = [ '/whats-new', '/changelog', '#', 'javascript:', 'mailto:', '/platforms/', '/design/human-interface-guidelines/$', '/design/human-interface-guidelines$' ]; return skipPatterns.some(pattern => url.includes(pattern)); } /** * Remove duplicate sections */ removeDuplicates(sections) { const seen = new Set(); return sections.filter(section => { const key = `${section.url}-${section.platform}`; if (seen.has(key)) { return false; } seen.add(key); return true; }); } } //# sourceMappingURL=comprehensive-hig-discovery.service.js.map