/** * Design System Server (DSS) - App Shell * * Main application controller. * Handles routing, state, and integration with services. */ import store from '../stores/app-store.js'; import figmaService from '../services/figma-service.js'; import discoveryService from '../services/discovery-service.js'; import teamService from '../services/team-service.js'; import toolsService from '../services/tools-service.js'; import claudeService from '../services/claude-service.js'; import DashboardService from '../services/dashboard-service.js'; import pluginService from '../services/plugin-service.js'; import { initializePlugins } from '../plugins/index.js'; import logger from './logger.js'; import themeManager from './theme.js'; import { UITeamDashboard, UXTeamDashboard, QATeamDashboard } from './team-dashboards.js'; // Enterprise Architecture Modules import router from './router.js'; import { subscribe as subscribeToNotifications, notifySuccess, notifyError, notifyInfo, ErrorCode } from './messaging.js'; import { createProjectWorkflow } from './workflows.js'; import { handleError } from './error-handler.js'; import { loadConfig, getStorybookUrl, getDssHost } from './config-loader.js'; import { getEnabledComponents, getComponentSetting, setComponentSetting } from './component-config.js'; import { sanitizeHtml, setSafeHtml, sanitizeText, escapeHtml } from './sanitizer.js'; import LandingPage from './landing-page.js'; import themeLoader from './theme-loader.js'; class App { constructor() { this.store = store; this.services = { figma: figmaService, discovery: discoveryService, team: teamService }; // Track listener setup to prevent duplicates this.listeners = { teamContextChanged: null, hasTeamContextListener: false }; } // === Initialization === async init() { logger.info('App', 'Initializing application...'); // FIRST: Load server configuration (blocking operation) // This must complete before any component uses the config try { await loadConfig(); this.store.set({ isConfigLoading: false }); logger.info('App', 'Server configuration loaded'); } catch (error) { this.store.set({ isConfigLoading: false, configError: error.message }); logger.error('App', 'Failed to load server configuration', { error: error.message }); notifyError('Failed to load server configuration. Some features may not work correctly.', ErrorCode.SYSTEM_STARTUP_FAILED); } // Initialize DSS Theme Loader (validates CSS layers) try { await themeLoader.init(); this.themeLoader = themeLoader; logger.info('App', 'DSS Theme Loader initialized'); } catch (error) { logger.warn('App', 'Theme loader initialization failed, using fallback styles', { error: error.message }); } // Initialize plugins initializePlugins(); logger.info('App', 'Plugins initialized'); // Initialize enterprise messaging system this.initializeMessaging(); // Initialize router with route definitions this.initializeRouter(); // Subscribe to state changes this.store.subscribe('currentPage', () => this.render()); this.store.subscribe('notifications', (notifs) => this.renderNotifications(notifs)); this.store.subscribe('currentProject', (projectId) => { if (projectId) { this.loadDashboardData(projectId); } }); // Listen for team context changes from chat sidebar (only add listener once) if (!this.listeners.hasTeamContextListener) { this.listeners.teamContextChanged = (e) => { logger.info('App', 'Team context changed', { team: e.detail.team }); this.store.set({ teamContext: e.detail.team }); this.render(); }; window.addEventListener('team-context-changed', this.listeners.teamContextChanged); this.listeners.hasTeamContextListener = true; } // Load initial data await this.loadInitialData(); // Update Storybook link with configured URL this.updateStorybookLink(); // Initialize landing page with dashboard cards const appElement = document.getElementById('app'); if (appElement) { this.landingPage = new LandingPage(appElement); logger.info('App', 'Landing page initialized'); } // Render the app this.render(); // Set up polling setInterval(() => this.refreshHealth(), 30000); logger.info('App', 'Application initialized successfully'); } /** * Initialize enterprise messaging system */ initializeMessaging() { // Subscribe to notification events subscribeToNotifications((notification) => { // Use the existing store notification system for UI display const duration = notification.duration || 5000; this.store.notify(notification.message, notification.type, duration); // Log to console for debugging logger.info('Notification', notification.message, { code: notification.code, correlationId: notification.correlationId, }); }); logger.info('App', 'Messaging system initialized'); } /** * Initialize router with route definitions */ initializeRouter() { // Register all application routes router.registerAll([ { path: 'dashboard', name: 'dashboard', handler: ({ router }) => { this.store.set({ currentPage: 'dashboard' }); this.render(); }, }, { path: 'projects', name: 'projects', handler: ({ router }) => { this.store.set({ currentPage: 'projects' }); this.render(); }, }, { path: 'tokens', name: 'tokens', handler: ({ router }) => { this.store.set({ currentPage: 'tokens' }); this.render(); }, }, { path: 'components', name: 'components', handler: ({ router }) => { this.store.set({ currentPage: 'components' }); this.render(); }, }, { path: 'figma', name: 'figma', handler: ({ router }) => { this.store.set({ currentPage: 'figma' }); this.render(); }, }, { path: 'docs', name: 'docs', handler: ({ router }) => { this.store.set({ currentPage: 'docs' }); this.render(); }, }, { path: 'teams', name: 'teams', handler: ({ router }) => { this.store.set({ currentPage: 'teams' }); this.render(); }, }, { path: 'audit', name: 'audit', handler: ({ router }) => { this.store.set({ currentPage: 'audit' }); this.render(); }, }, { path: 'settings', name: 'settings', handler: ({ router }) => { this.store.set({ currentPage: 'settings' }); this.render(); }, }, { path: 'services', name: 'services', handler: ({ router }) => { this.store.set({ currentPage: 'services' }); this.render(); }, }, { path: 'quick-wins', name: 'quick-wins', handler: ({ router }) => { this.store.set({ currentPage: 'quick-wins' }); this.render(); }, }, { path: 'chat', name: 'chat', handler: ({ router }) => { this.store.set({ currentPage: 'chat' }); this.render(); }, }, { path: 'plugins', name: 'plugins', handler: ({ router }) => { this.store.set({ currentPage: 'plugins' }); this.render(); }, }, { path: 'workdesk', name: 'workdesk', handler: async ({ router }) => { logger.info('App', 'Mounting workdesk UI'); // Get app container const appRoot = document.getElementById('app'); if (!appRoot) { logger.error('App', 'Cannot mount workdesk: #app element not found'); return; } // Full page takeover - clear legacy layout appRoot.innerHTML = ''; // Dynamically import and mount ds-shell try { await import('../components/layout/ds-shell.js'); const shell = document.createElement('ds-shell'); shell.setAttribute('mode', 'production'); appRoot.appendChild(shell); logger.info('App', 'Workdesk mounted successfully'); } catch (error) { logger.error('App', 'Failed to mount workdesk', { error: error.message }); appRoot.innerHTML = `

Failed to Load Workdesk

Error: ${error.message}

`; } }, }, ]); // Set default route router.setDefaultRoute('dashboard'); // Initialize router router.init(); logger.info('App', 'Router initialized with 13 routes'); } async loadInitialData() { this.store.setLoading('init', true); try { // Check API connection first const health = await this.services.discovery.getHealth(); this.store.setHealth(health); this.store.set({ apiConnected: true }); // Load runtime configuration and services await this.loadConfig(); // Load discovery data const discovery = await this.services.discovery.discover('.', false); this.store.setDiscovery(discovery); // Load stats const stats = await this.services.discovery.getProjectStats(); this.store.setStats(stats); // Load recent activity const activity = await this.services.discovery.getRecentActivity(); this.store.setActivity(activity.items || []); // Load projects await this.loadProjects(); // Set user from session or default this.store.setUser( { id: '1', name: 'User', email: 'user@local' }, 'team-1', 'TEAM_LEAD' ); } catch (error) { logger.error('App', 'Failed to load initial data', error); this.store.set({ apiConnected: false }); this.store.setError('init', 'Cannot connect to DSS server. Run: python tools/api/server.py'); this.store.notify('Server offline. Start DSS API first.', 'error', 0); } this.store.setLoading('init', false); } async refreshHealth() { try { const health = await this.services.discovery.getHealth(); this.store.setHealth(health); } catch (error) { logger.error('App', 'Health check failed', error); } } // === Navigation === navigate(page) { window.location.hash = page; } // === Figma Actions === async extractTokens() { const fileKey = this.store.get('figmaFileKey') || 'demo'; this.store.setLoading('extractTokens', true); try { const result = await this.services.figma.extractVariables(fileKey, 'css'); this.store.setTokens(result.tokens); notifySuccess('Tokens extracted successfully!', ErrorCode.SUCCESS_OPERATION, { fileKey, tokenCount: result.tokens?.length || 0, }); return result; } catch (error) { // Use enterprise error handler for user-friendly messages handleError(error, { operation: 'extract tokens', service: 'figma', fileKey, }); throw error; } finally { this.store.setLoading('extractTokens', false); } } async extractComponents() { const fileKey = this.store.get('figmaFileKey') || 'demo'; this.store.setLoading('extractComponents', true); try { const result = await this.services.figma.extractComponents(fileKey); this.store.setComponents(result.components); this.store.notify('Components extracted successfully', 'success'); return result; } catch (error) { this.store.notify(`Failed to extract components: ${error.message}`, 'error'); throw error; } finally { this.store.setLoading('extractComponents', false); } } async syncTokens() { const fileKey = this.store.get('figmaFileKey') || 'demo'; this.store.setLoading('syncTokens', true); try { const result = await this.services.figma.syncTokens(fileKey, './admin-ui/css/tokens.css'); this.store.setLastSync(new Date().toISOString()); const count = result?.tokens_synced ?? 0; this.store.notify(count > 0 ? `Synced ${count} tokens` : 'Sync complete', 'success'); return result; } catch (error) { this.store.notify(`Sync failed: ${error.message}`, 'error'); throw error; } finally { this.store.setLoading('syncTokens', false); } } async runVisualDiff() { const fileKey = this.store.get('figmaFileKey') || 'demo'; this.store.setLoading('visualDiff', true); try { const result = await this.services.figma.visualDiff(fileKey); if (result.changes_detected) { this.store.notify(`Visual diff: ${result.summary.changed} changes detected`, 'warning'); } else { this.store.notify('No visual changes detected', 'success'); } return result; } catch (error) { this.store.notify(`Visual diff failed: ${error.message}`, 'error'); throw error; } finally { this.store.setLoading('visualDiff', false); } } async validateComponents() { const fileKey = this.store.get('figmaFileKey') || 'demo'; this.store.setLoading('validate', true); try { const result = await this.services.figma.validateComponents(fileKey); const { errors, warnings } = result.summary; if (errors > 0) { this.store.notify(`Validation: ${errors} errors, ${warnings} warnings`, 'error'); } else if (warnings > 0) { this.store.notify(`Validation passed with ${warnings} warnings`, 'warning'); } else { this.store.notify('All components valid', 'success'); } return result; } catch (error) { this.store.notify(`Validation failed: ${error.message}`, 'error'); throw error; } finally { this.store.setLoading('validate', false); } } async generateCode(componentName, framework = null) { const fileKey = this.store.get('figmaFileKey') || 'demo'; const targetFramework = framework || this.store.get('componentFramework') || 'react'; this.store.setLoading('generateCode', true); try { const result = await this.services.figma.generateCode(fileKey, componentName, targetFramework); this.store.set({ generatedCode: { component: componentName, framework: targetFramework, code: result.code || result.generated_code || '// Code generation in progress...' } }); this.store.notify(`Generated ${componentName} for ${targetFramework}`, 'success'); this.render(); return result; } catch (error) { this.store.notify(`Code generation failed: ${error.message}`, 'error'); throw error; } finally { this.store.setLoading('generateCode', false); } } // === Discovery Actions === getProjectRoot() { const project = this.store.get('selectedProject'); return project?.root_path || project?.path || '.'; } async runDiscovery(fullScan = false) { this.store.setLoading('discovery', true); const projectRoot = this.getProjectRoot(); try { const result = await this.services.discovery.discover(projectRoot, fullScan); this.store.setDiscovery(result); const pathInfo = projectRoot !== '.' ? ` (${projectRoot})` : ''; this.store.notify(`Discovery complete${pathInfo}`, 'success'); return result; } catch (error) { this.store.notify(`Discovery failed: ${error.message}`, 'error'); throw error; } finally { this.store.setLoading('discovery', false); } } // === Rendering === render() { const contentArea = document.getElementById('page-content'); if (!contentArea) return; const page = this.store.get('currentPage'); setSafeHtml(contentArea, this.getPageContent(page)); // Update active nav item document.querySelectorAll('.nav-item[data-page]').forEach(item => { item.classList.toggle('active', item.dataset.page === page); }); // Attach event handlers this.attachEventHandlers(); } getPageContent(page) { switch (page) { case 'dashboard': return this.renderDashboard(); case 'services': return this.renderServices(); case 'quick-wins': return this.renderQuickWins(); case 'chat': return this.renderChat(); case 'projects': return this.renderProjects(); case 'tokens': return this.renderTokens(); case 'components': return this.renderComponents(); case 'figma': return this.renderFigma(); case 'docs': return this.renderDocs(); case 'teams': return this.renderTeams(); case 'plugins': return this.renderPlugins(); case 'audit': return this.renderAuditLog(); case 'settings': return this.renderSettings(); default: return this.renderDashboard(); } } renderDashboard() { const teamContext = this.store.get('teamContext') || localStorage.getItem('dss_team_context') || 'all'; const health = this.store.get('health') || {}; const stats = this.store.get('stats') || {}; const discovery = this.store.get('discovery') || {}; const activity = this.store.get('activity') || []; const dashboardData = this.store.get('dashboardData') || { ux: { figma_files_count: 0, figma_files: [] }, ui: { token_drift: { total: 0, by_severity: {} }, code_metrics: {} }, qa: { esre_count: 0, test_summary: {} } }; const healthScore = discovery.health?.score || 0; const healthGrade = discovery.health?.grade || '-'; const teamNames = { 'all': 'All Teams', 'ui': 'UI Team', 'ux': 'UX Team', 'qa': 'QA Team' }; // Get current project const currentProjectId = this.store.get('currentProject'); const projects = this.store.get('projects') || []; const currentProject = projects.find(p => p.id === currentProjectId); const projectName = currentProject?.name || 'No Project Selected'; // Render team-specific dashboard switch(teamContext) { case 'ui': return UITeamDashboard(health, stats, discovery, activity, projectName, dashboardData, currentProjectId, this); case 'ux': return UXTeamDashboard(health, stats, discovery, activity, projectName, dashboardData, currentProjectId, this); case 'qa': return QATeamDashboard(health, stats, discovery, activity, projectName, dashboardData, currentProjectId, this); default: return this.renderAllTeamsDashboard(health, stats, discovery, activity, healthScore, healthGrade); } } renderAllTeamsDashboard(health, stats, discovery, activity, healthScore, healthGrade) { return `
Health Score
${healthScore}% (${healthGrade})
Design Tokens
${stats.tokens?.total || 0}
Components
${stats.components?.total || discovery.files?.components || 0}
Syncs Today
${stats.syncs?.today || 0}
Ingest Design System Add a design system using natural language
Ingest Browse
${this.renderIngestionResult()}
Quick Actions Common operations
Extract Tokens Sync Tokens Validate Visual Diff Re-scan Project
Project Info Discovered configuration
Project Types: ${(discovery.project?.types || []).join(', ') || 'Unknown'}
Frameworks: ${(discovery.project?.frameworks || []).join(', ') || 'None detected'}
Total Files: ${discovery.files?.total || 0}
CSS Files: ${discovery.files?.css || 0}
Git Branch: ${discovery.git?.branch || 'N/A'}
Uncommitted: ${discovery.git?.uncommitted_changes || 0} changes
Recent Activity ${activity.length > 0 ? `
${activity.slice(0, 5).map(item => `
${item.message} ${this.formatTime(item.timestamp)}
`).join('')}
` : '

No recent activity

'}
System Health ${health.checks ? `
${health.checks.map(check => `
${check.name} ${check.message || (check.latency ? `${check.latency}ms` : 'OK')}
`).join('')}
` : '

Loading health status...

'}
${discovery.health?.issues?.length > 0 ? ` Improvement Suggestions
${discovery.health.issues.map(issue => `
Suggestion ${issue}
`).join('')}
` : ''} `; } renderTokens() { const tokens = this.store.get('tokens') || []; const selectedProject = this.store.get('selectedProject'); const exportFormat = this.store.get('tokenExportFormat') || 'css'; const byCategory = tokens.reduce((acc, t) => { const cat = t.category || 'other'; if (!acc[cat]) acc[cat] = []; acc[cat].push(t); return acc; }, {}); const categoryOrder = ['color', 'spacing', 'sizing', 'typography', 'radius', 'shadow', 'other']; const sortedCategories = Object.keys(byCategory).sort((a, b) => { return categoryOrder.indexOf(a) - categoryOrder.indexOf(b); }); const stats = { total: tokens.length, colors: byCategory.color?.length || 0, spacing: byCategory.spacing?.length || 0, typography: byCategory.typography?.length || 0 }; return `
Total Tokens
${stats.total}
Colors
${stats.colors}
Spacing
${stats.spacing}
Typography
${stats.typography}
Extract from Figma Sync to Files
Export as: CSS SCSS JSON TypeScript
${tokens.length > 0 ? sortedCategories.map(category => { const categoryTokens = byCategory[category]; const categoryName = category.charAt(0).toUpperCase() + category.slice(1); const isColor = category === 'color'; return ` ${categoryName} ${categoryTokens.length} tokens ${isColor ? `
${categoryTokens.map(token => `
--${token.name}
${token.value}
`).join('')}
` : `
${categoryTokens.map(token => `
${category === 'spacing' || category === 'sizing' ? `
` : category === 'radius' ? `
` : category === 'shadow' ? `
` : ''}
--${token.name} ${token.value}
`).join('')}
`}
`}).join('') : `

No tokens extracted yet.

Connect to Figma and extract design tokens to see them here.

Extract Tokens from Figma
`} `; } renderComponents() { const components = this.store.get('components') || []; const selectedFramework = this.store.get('componentFramework') || 'react'; const generatedCode = this.store.get('generatedCode'); const frameworks = [ { id: 'react', name: 'React', icon: 'R' }, { id: 'vue', name: 'Vue', icon: 'V' }, { id: 'webcomponent', name: 'Web Component', icon: 'W' }, { id: 'svelte', name: 'Svelte', icon: 'S' } ]; return `
Total Components
${components.length}
With Variants
${components.filter(c => c.variants?.length > 0).length}
With Properties
${components.filter(c => c.properties?.length > 0).length}
Target Framework
${frameworks.find(f => f.id === selectedFramework)?.name || 'React'}
Extract from Figma Validate All
Generate for: ${frameworks.map(fw => ` ${fw.icon} `).join('')}
${generatedCode ? ` Generated Code: ${generatedCode.component} Close
${this.escapeHtml(generatedCode.code)}
Copy to Clipboard
` : ''} ${components.length > 0 ? `
${components.map(comp => `
${comp.name.charAt(0).toUpperCase()}
${comp.name}

${comp.description || 'Component from Figma'}

${comp.variants?.length > 0 ? `
Variants:
${comp.variants.slice(0, 5).map(v => `${v}`).join('')} ${comp.variants.length > 5 ? `+${comp.variants.length - 5}` : ''}
` : ''} ${comp.properties?.length > 0 ? `
Properties:
${comp.properties.slice(0, 4).map(p => `${p.name}: ${p.type}`).join('')} ${comp.properties.length > 4 ? `+${comp.properties.length - 4}` : ''}
` : ''}
Key: ${comp.key?.slice(0, 8) || 'N/A'}...
Generate ${frameworks.find(f => f.id === selectedFramework)?.name || 'Code'} Details
`).join('')}
` : `

No components extracted yet.

Extract components from your Figma file to generate code.

Extract Components from Figma
`} `; } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } renderFigma() { const figmaFileKey = this.store.get('figmaFileKey') || ''; const selectedProject = this.store.get('selectedProject'); const projectDescription = selectedProject?.description || ''; const lastSync = this.store.get('lastSync'); return `
Project Details Describe your design system project Save Description Figma Connection Configure your Figma file

Find this in your Figma URL: figma.com/file/[FILE_KEY]/...

Save & Connect
Sync Status
Connection: ${figmaFileKey ? 'Connected' : 'Not configured'}
Last Sync: ${lastSync ? this.formatTime(lastSync) : 'Never'}
Available Tools Figma integration capabilities

Extract Variables

Pull design tokens from Figma variables

Extract Components

Get component definitions and variants

Extract Styles

Pull text, color, and effect styles

Sync Tokens

Sync tokens to CSS/SCSS/JSON files

Visual Diff

Compare visual changes between versions

Validate Components

Check components against naming rules

Generate Code

Create component code from designs

`; } renderTeams() { const user = this.store.get('user'); const role = this.store.get('role'); return `
${this.store.hasPermission('manage_team_members') ? ` Create Team ` : ''}
Design System Core Active
Members: 5
Projects: 3
Your Role: ${role}
View Details
Product Team A Member
Members: 8
Projects: 2
View Details
Role Permissions

Super Admin

Full system access

Team Lead

Manage team, sync, generate

Developer

Read, write, sync

Viewer

Read-only access

`; } renderProjects() { const projects = this.store.get('projects') || []; const showCreateForm = this.store.get('showCreateProjectForm'); return `
${showCreateForm ? 'Cancel' : 'New Project'}
${showCreateForm ? ` Create New Project Create Project ` : ''} ${projects.length > 0 ? `
${projects.map(p => ` ${p.name} ${p.status || 'active'}

${p.description || 'No description'}

Figma Key: ${p.figma_file_key || 'Not configured'}
Last Sync: ${p.last_sync ? this.formatTime(p.last_sync) : 'Never'}
Created: ${p.created_at ? this.formatTime(p.created_at) : 'Unknown'}
Open Sync Tokens Delete
`).join('')}
` : `

No projects yet. Create your first project to get started.

Create Your First Project
`} `; } renderSettings() { const config = this.store.get('runtimeConfig') || {}; const services = this.store.get('services') || {}; const figmaConfig = this.store.get('figmaConfig') || {}; const mode = config.mode || 'local'; return `
Server Mode Choose how DSS operates

Local Dev Companion

Run alongside your project, provides UI dev assistance, component preview, and local services.

Remote Server

Deployed centrally, serves design systems to teams, multi-project management.

Figma Integration Connect to Figma API ${figmaConfig.configured ? 'Connected' : 'Not configured'}

Get your token from Figma Settings → Personal Access Tokens

Save Token Test Connection
${this.store.get('figmaTestResult') ? `
${this.store.get('figmaTestResult').success ? `Connected as ${this.store.get('figmaTestResult').user}` : `Error: ${this.store.get('figmaTestResult').error}`}
` : ''}
External Tools & Integrations Configure connected tools and services
${this.renderComponentSettings()}
Companion Services Discovered and configured services
${this.renderServiceCard('storybook', 'Storybook', services)} ${this.renderServiceCard('vite', 'Vite Dev Server', services)} ${this.renderServiceCard('nextjs', 'Next.js', services)}
Refresh Services
Features Enable or disable DSS features
${this.renderFeatureToggle('visual_qa', 'Visual QA', 'Compare Figma designs with implementation', config.features)} ${this.renderFeatureToggle('token_sync', 'Token Sync', 'Sync design tokens to code', config.features)} ${this.renderFeatureToggle('code_gen', 'Code Generation', 'Generate component code from Figma', config.features)} ${this.renderFeatureToggle('ai_advisor', 'AI Advisor', 'Get AI suggestions for design system improvements', config.features)}
Appearance Customize the interface
Dark Mode Toggle Theme
Output Configuration Token and component generation settings
⚠️ Danger Zone Irreversible operations - use with caution

Reset DSS to Fresh State

This will delete all user-created themes, cached data, and project databases. The DSS structure and default themes will be preserved.

Reset DSS
API Status
API Mode: ${this.store.get('useMock') ? 'Mock (Backend unavailable)' : 'Live'}
Base URL: ${window.location.hostname === 'localhost' ? 'http://localhost:3456/api' : '/api'}
`; } renderPlugins() { const plugins = pluginService.getAll(); const enabledCount = plugins.filter(p => p.enabled).length; return `
${plugins.map(plugin => this.renderPluginCard(plugin)).join('')}
`; } renderPluginCard(plugin) { const settings = pluginService.getPluginSettings(plugin.id); return `
${plugin.icon}
${plugin.name}
v${plugin.version}
${plugin.description}
${plugin.enabled && plugin.settings.length > 0 ? `
${plugin.settings.map(setting => this.renderPluginSetting(plugin.id, setting, settings)).join('')}
` : ''}
by ${plugin.author} ${plugin.enabled ? 'Enabled' : 'Disabled'}
`; } renderPluginSetting(pluginId, setting, currentSettings) { const value = currentSettings[setting.key] ?? setting.default; if (setting.type === 'select') { return `
${setting.label}
`; } if (setting.type === 'boolean') { return `
${setting.label}
`; } return ''; } renderDocs() { return `
${this.getDocContent('overview')}
`; } getDocContent(docId) { const docs = { overview: `

What is DSS?

Design System Server (DSS) is a platform that helps teams manage, sync, and evolve their design systems by connecting Figma designs to code.

Core Features

Architecture

`, quickstart: `

Quick Start

1. Start the Server

cd apps/dss
python tools/api/server.py

2. Create a Project

Go to Projects → Create Project

3. Add Figma File

In your project settings, paste your Figma file key:

https://www.figma.com/file/FILE_KEY/...

4. Extract Tokens

Use the dashboard or CLI:

dss ingest figma YOUR_FILE_KEY

5. Sync to Code

Click "Sync Tokens" or:

dss sync tokens --output ./tokens.css
`, concepts: `

Key Concepts

Design Tokens

Tokens are the atomic values of your design system: colors, spacing, typography. DSS extracts these from Figma variables and converts them to CSS custom properties or JSON.

Translation Dictionaries

DSS uses a canonical internal structure. When importing from external sources, translation dictionaries map external names to DSS standard names.

Token Drift

When code diverges from Figma designs, that's "drift". The UI Team dashboard tracks drift issues and helps resolve them.

ESRE (Expected State, Real State, Evidence)

QA team uses ESRE to define test cases: what should happen, what actually happens, and proof.

`, 'ui-team': `

UI Team Guide

As a UI developer, you'll use DSS to keep code in sync with designs.

Daily Workflow

  1. Check the Dashboard for token drift alerts
  2. Run token sync when Figma updates
  3. Generate component code from new Figma components
  4. Review and resolve drift issues

Key Tools

CLI Commands

dss ingest figma FILE_KEY
dss sync tokens -o ./tokens.css
dss analyze ./src
`, 'ux-team': `

UX Team Guide

As a UX designer, DSS helps you maintain design consistency and validate implementations.

Daily Workflow

  1. Add Figma files to projects
  2. Run visual diff after design changes
  3. Review token consistency reports
  4. Validate component implementations

Key Tools

Best Practices

`, 'qa-team': `

QA Team Guide

DSS helps QA teams validate design system implementations.

Daily Workflow

  1. Review visual regression reports
  2. Define ESRE test cases for components
  3. Run component validation
  4. Export audit logs for compliance

ESRE Testing

Define expected behaviors:

Key Tools

`, tokens: `

Design Tokens

Token Categories

Export Formats

Token Naming

--{category}-{name}[-{variant}]

Examples:
--color-primary
--space-4
--text-lg
--radius-md
`, figma: `

Figma Integration

Setup

  1. Get a Personal Access Token from Figma Settings
  2. Go to Settings → Figma Integration
  3. Paste and save your token
  4. Test the connection

File Key

Extract from your Figma URL:

https://www.figma.com/file/ABC123/My-Design
                              ↑ This is your file key

What Gets Extracted

`, components: `

Components

Component Analysis

DSS scans your codebase to find React and Web Components:

dss analyze ./src

Code Generation

Generate component code from Figma:

Storybook Integration

Generate stories for your components:

dss storybook generate ./src/components
`, 'ai-chat': `

AI Chat

The AI assistant helps with design system tasks.

What It Can Do

Example Prompts

Tool Execution

The AI can run DSS tools directly. Look for tool suggestions in responses and click to execute.

`, cli: `

CLI Commands

Installation

# Add to PATH
export PATH="$PATH:/path/to/dss/bin"

# Or use directly
./bin/dss --help

Commands

dss init
  Initialize DSS in current directory

dss ingest figma FILE_KEY
  Extract tokens from Figma file

dss sync tokens -o PATH
  Sync tokens to CSS file

dss analyze PATH
  Analyze codebase for components

dss storybook generate PATH
  Generate Storybook stories

dss start
  Start DSS server
`, troubleshooting: `

Troubleshooting

Server Won't Start

# Check Python version (need 3.10+)
python --version

# Install dependencies
pip install -r requirements.txt

# Check port availability
lsof -i :3456

Figma Connection Failed

Tokens Not Syncing

API Errors

Getting Help

` }; return docs[docId] || docs.overview; } renderServiceCard(serviceId, serviceName, services) { const discovered = services.discovered?.[serviceId] || {}; const configured = services.configured?.[serviceId] || {}; const running = discovered.running || false; return `

${serviceName}

${running ? `Running on :${discovered.port}` : 'Not detected'}

${running ? ` Open ` : ''}
`; } /** * Render component settings from the registry * Generates dynamic UI for all enabled components */ renderComponentSettings() { let dssHost = 'localhost'; try { dssHost = getDssHost(); } catch { // Config not loaded yet } const components = getEnabledComponents(); return components.map(component => `
${component.name} ${component.category}
${component.getUrl() ? ` Open ` : ''}

${component.description}

${component.id === 'storybook' ? `
URL: ${component.getUrl() || 'Not configured'}
Host from server config: ${dssHost}
Initialize Storybook Clear Stories
` : ''} ${component.id === 'figma' ? `
Token status: Check connection above
` : ''}
`).join(''); } renderFeatureToggle(featureId, name, description, features = {}) { const enabled = features?.[featureId] !== false; return `
${name}
${description}
${enabled ? 'Enabled' : 'Disabled'}
`; } /** * Render ingestion result panel (after parsing a prompt) */ renderIngestionResult() { const result = this.store.get('ingestionResult'); const browsing = this.store.get('browsingDesignSystems'); const systems = this.store.get('designSystems') || []; // If browsing design systems if (browsing) { return `

Available Design Systems

Close
${systems.map(sys => `
${sys.name}
${sys.description?.slice(0, 60) || ''}...
${sys.category || 'library'} ${sys.framework ? `${sys.framework}` : ''}
`).join('')}
`; } if (!result) return ''; const { intent, sources, next_steps, suggestions } = result; // Found a design system to ingest if (sources?.length > 0 && sources[0].matched_system) { const system = sources[0].matched_system; return `

${system.name}

${system.description}

${system.category || 'library'}
npm packages
${(system.npm_packages || []).join(', ') || 'N/A'}
Primary method
${system.primary_ingestion || 'npm'}
${system.figma_community_url ? `
View Figma Community Kit
` : ''}
Ingest ${system.name} Other Methods Cancel
`; } // Show next steps or suggestions if (next_steps?.length > 0) { const step = next_steps[0]; if (step.action === 'request_source') { return `

${step.message}

${(step.alternatives || []).map(alt => `
${alt.name}
${alt.description}
`).join('')}
Cancel
`; } if (step.action === 'search_npm') { return `
Not found in registry

${step.message}

Search npm Cancel
`; } } // Show suggestions if (suggestions?.length > 0) { return `
`; } return ''; } // === Event Handlers === attachEventHandlers() { // Use event delegation for all [data-action] buttons // Only attach the listener once to prevent accumulation if (!this.listeners.hasActionListener) { document.addEventListener('click', (e) => { const btn = e.target.closest('[data-action]'); if (!btn) return; e.preventDefault(); e.stopPropagation(); const action = btn.dataset.action; const component = btn.dataset.component; switch (action) { case 'extractTokens': this.extractTokens(); break; case 'extractComponents': this.extractComponents(); break; case 'syncTokens': this.syncTokens(); break; case 'runVisualDiff': this.runVisualDiff(); break; case 'validateComponents': this.validateComponents(); break; case 'runDiscovery': this.runDiscovery(true); break; case 'generateCode': if (component) this.generateCode(component); break; case 'saveFigmaKey': const input = document.getElementById('figma-file-key'); if (input) { this.store.set({ figmaFileKey: input.value }); this.store.notify('Figma connection saved', 'success'); } break; case 'saveProjectDescription': const descInput = document.getElementById('project-description'); const selectedProject = this.store.get('selectedProject'); if (descInput && selectedProject) { this.updateProjectDescription(selectedProject.id, descInput.value); } break; // Settings actions case 'saveFigmaToken': this.saveFigmaToken(); break; case 'testFigmaConnection': this.testFigmaConnection(); break; case 'saveDssHost': this.saveDssHost(); break; case 'setMode': const mode = btn.dataset.mode; if (mode) this.setServerMode(mode); break; case 'toggleFeature': const feature = btn.dataset.feature; if (feature) this.toggleFeature(feature); break; case 'refreshServices': this.loadServices(); break; case 'initStorybook': this.initStorybook(); break; case 'clearStorybook': this.clearStorybook(); break; case 'resetDSS': this.resetDSS(); break; // Ingestion actions case 'parseIngestion': this.parseIngestionPrompt(); break; case 'browseDesignSystems': this.browseDesignSystems(); break; case 'closeBrowse': this.store.set({ browsingDesignSystems: false }); this.render(); break; case 'selectDesignSystem': const systemId = btn.dataset.systemId; if (systemId) this.selectDesignSystem(systemId); break; case 'confirmIngestion': const confirmSystemId = btn.dataset.systemId; if (confirmSystemId) this.confirmIngestion(confirmSystemId); break; case 'showAlternatives': const altSystemId = btn.dataset.systemId; if (altSystemId) this.showIngestionAlternatives(altSystemId); break; case 'clearIngestion': this.store.set({ ingestionResult: null, browsingDesignSystems: false }); this.render(); break; case 'navigate-quick-wins': window.location.hash = 'quick-wins'; break; case 'searchNpm': const npmQuery = btn.dataset.query; if (npmQuery) this.searchNpmPackages(npmQuery); break; case 'selectIngestionMethod': const methodType = btn.dataset.method; if (methodType) this.selectIngestionMethod(methodType); break; // Project actions case 'toggleCreateProject': this.store.set({ showCreateProjectForm: !this.store.get('showCreateProjectForm') }); this.render(); break; case 'submitCreateProject': const nameInput = document.getElementById('new-project-name'); if (nameInput && nameInput.value) { this.createProject( nameInput.value, '', // description '' // figma key ); } else { this.store.notify('Please enter a project name', 'warning'); } break; case 'selectProject': const selectProjectId = btn.dataset.projectId; if (selectProjectId) this.selectProject(selectProjectId); break; case 'syncProject': const syncProjectId = btn.dataset.projectId; const syncFigmaKey = btn.dataset.figmaKey; if (syncProjectId) this.syncProjectTokens(syncProjectId, syncFigmaKey); break; case 'deleteProject': const deleteProjectId = btn.dataset.projectId; if (deleteProjectId) this.deleteProject(deleteProjectId); break; // Token export format case 'setExportFormat': const format = btn.dataset.format; if (format) { this.store.set({ tokenExportFormat: format }); this.render(); } break; // Component actions case 'viewComponentCode': const compName = btn.dataset.component; const compFramework = btn.dataset.framework || 'react'; if (compName) this.showComponentCode(compName, compFramework); break; case 'setFramework': const fw = btn.dataset.framework; if (fw) { this.store.set({ componentFramework: fw }); this.render(); } break; case 'closeGeneratedCode': this.store.set({ generatedCode: null }); this.render(); break; case 'copyCode': const code = this.store.get('generatedCode')?.code; if (code) { navigator.clipboard.writeText(code); this.store.notify('Code copied to clipboard', 'success'); } break; // New services and quick wins actions case 'executeTool': const toolName = btn.dataset.tool; if (toolName) this.executeToolWithParams(toolName); break; case 'loadQuickWins': this.loadQuickWins(); break; case 'investigateWin': const winId = btn.dataset.winId; if (winId) this.investigateWin(winId); break; case 'markDone': const doneWinId = btn.dataset.winId; if (doneWinId) this.markWinDone(doneWinId); break; // Chat actions case 'sendChatMessage': this.sendChatMessage({ target: btn }); break; case 'clearChat': claudeService.clearHistory(); this.render(); break; case 'exportChat': claudeService.exportConversation(); break; // Team Dashboard actions case 'sync-figma-file': const fileId = btn.dataset.fileId; if (fileId) this.syncFigmaFile(fileId); break; case 'delete-figma-file': const deleteFileId = btn.dataset.fileId; if (deleteFileId && confirm('Delete this Figma file?')) { this.deleteFigmaFile(deleteFileId); } break; // Plugin actions case 'togglePlugin': const pluginId = btn.dataset.plugin; if (pluginId) this.togglePlugin(pluginId); break; case 'setPluginSetting': const settingPluginId = btn.dataset.plugin; const settingKey = btn.dataset.key; const settingType = btn.dataset.type; if (settingPluginId && settingKey) { let settingValue; if (settingType === 'boolean') { const current = pluginService.getPluginSettings(settingPluginId)[settingKey]; settingValue = !current; } else if (btn.tagName === 'SELECT') { settingValue = btn.value; } this.setPluginSetting(settingPluginId, settingKey, settingValue); } break; // Theme actions case 'toggle-theme': themeManager.toggle(); this.render(); break; // Audit actions case 'load-audit': this.loadAuditLog(); break; case 'clear-audit-filters': this.clearAuditFilters(); break; case 'export-audit': const exportFormat = btn.dataset.format || 'json'; this.exportAuditLog(exportFormat); break; case 'prev-audit-page': this.prevAuditPage(); break; case 'next-audit-page': this.nextAuditPage(); break; case 'show-audit-details': const auditId = btn.dataset.auditId; if (auditId) this.showAuditDetails(parseInt(auditId)); break; } }); this.listeners.hasActionListener = true; } // Chat form submission const chatForm = document.getElementById('chatForm'); if (chatForm) { chatForm.addEventListener('submit', (e) => { e.preventDefault(); this.sendChatMessage(e); }); } // Team Dashboard form submissions const addFigmaFileForm = document.getElementById('add-figma-file-form'); if (addFigmaFileForm) { addFigmaFileForm.addEventListener('submit', (e) => { e.preventDefault(); const formData = new FormData(e.target); this.addFigmaFile({ file_name: formData.get('file_name'), figma_url: formData.get('figma_url'), file_key: formData.get('file_key') }); }); } const addESREForm = document.getElementById('add-esre-form'); if (addESREForm) { addESREForm.addEventListener('submit', (e) => { e.preventDefault(); const formData = new FormData(e.target); this.addESREDefinition({ name: formData.get('name'), definition_text: formData.get('definition_text'), component_name: formData.get('component_name') || null, expected_value: formData.get('expected_value') || null }); }); } // Search and filter handlers for services page const toolSearch = document.getElementById('toolSearch'); if (toolSearch) { toolSearch.addEventListener('input', (e) => { this.filterTools(e.target.value, document.getElementById('categoryFilter')?.value || ''); }); } const categoryFilter = document.getElementById('categoryFilter'); if (categoryFilter) { categoryFilter.addEventListener('change', (e) => { this.filterTools(document.getElementById('toolSearch')?.value || '', e.target.value); }); } // Docs navigation document.querySelectorAll('.docs-nav__link[data-doc]').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const docId = e.currentTarget.dataset.doc; const content = document.getElementById('docs-content'); if (content && docId) { setSafeHtml(content, this.getDocContent(docId)); document.querySelectorAll('.docs-nav__link').forEach(l => l.classList.remove('active')); e.currentTarget.classList.add('active'); } }); }); // Setup keyboard shortcuts this.setupKeyboardHandlers(); } setupKeyboardHandlers() { // Handle global keyboard shortcuts if (!this.listeners.hasKeyboardListener) { this.listeners.keyboardHandler = (e) => { // Escape key: Close sidebars and modals if (e.key === 'Escape') { // Close AI sidebar if visible const aiSidebar = document.getElementById('ai-sidebar'); const toggleBtn = document.getElementById('sidebar-toggle'); if (aiSidebar && !aiSidebar.classList.contains('hidden')) { if (toggleBtn) { toggleBtn.click(); // Update ARIA state setTimeout(() => { const isExpanded = !aiSidebar.classList.contains('hidden'); toggleBtn.setAttribute('aria-expanded', isExpanded); }, 0); } } // Close any open details/summary elements document.querySelectorAll('details[open]').forEach(details => { details.open = false; }); } // Alt+N: Navigate to next page if (e.altKey && e.key === 'n') { e.preventDefault(); const navItems = Array.from(document.querySelectorAll('.nav-item')); const currentPage = this.store.get('currentPage'); const currentIndex = navItems.findIndex(item => item.dataset.page === currentPage); if (currentIndex >= 0 && currentIndex < navItems.length - 1) { navItems[currentIndex + 1].focus(); navItems[currentIndex + 1].click(); } } // Alt+P: Navigate to previous page if (e.altKey && e.key === 'p') { e.preventDefault(); const navItems = Array.from(document.querySelectorAll('.nav-item')); const currentPage = this.store.get('currentPage'); const currentIndex = navItems.findIndex(item => item.dataset.page === currentPage); if (currentIndex > 0) { navItems[currentIndex - 1].focus(); navItems[currentIndex - 1].click(); } } }; document.addEventListener('keydown', this.listeners.keyboardHandler); this.listeners.hasKeyboardListener = true; } } // === Settings Actions === async saveFigmaToken() { const input = document.getElementById('figma-token-input'); if (!input || !input.value || input.value.includes('••••')) { this.store.notify('Please enter a valid Figma token', 'warning'); return; } this.store.setLoading('saveFigmaToken', true); try { const api = (await import('./api.js')).default; await api.setFigmaToken(input.value); this.store.set({ figmaConfig: { configured: true } }); this.store.notify('Figma token saved', 'success'); this.render(); } catch (error) { this.store.notify(`Failed to save token: ${error.message}`, 'error'); } finally { this.store.setLoading('saveFigmaToken', false); } } async testFigmaConnection() { this.store.setLoading('testFigma', true); try { const api = (await import('./api.js')).default; const result = await api.testFigmaConnection(); this.store.set({ figmaTestResult: result }); if (result.success) { this.store.notify('Figma connection successful!', 'success'); } else { this.store.notify(`Connection failed: ${result.error}`, 'error'); } this.render(); } catch (error) { this.store.set({ figmaTestResult: { success: false, error: error.message } }); this.store.notify(`Test failed: ${error.message}`, 'error'); this.render(); } finally { this.store.setLoading('testFigma', false); } } async saveDssHost() { const input = document.getElementById('dss-host-input'); if (!input) { notifyError('DSS host input not found', ErrorCode.SYSTEM_UNEXPECTED); return; } const newHost = input.value.trim() || 'localhost'; this.store.setLoading('saveDssHost', true); try { // Validate hostname format (alphanumeric, dots, hyphens) if (!/^[a-zA-Z0-9.-]+$/.test(newHost)) { notifyError('Invalid hostname format', ErrorCode.VALIDATION_INVALID_FORMAT, { host: newHost }); this.store.setLoading('saveDssHost', false); return; } // Save to store (will auto-persist via subscription) this.store.set({ dssHost: newHost }); // Update the link immediately this.updateStorybookLink(); // Notify success notifySuccess('Host configuration saved successfully!', ErrorCode.SUCCESS_UPDATED, { host: newHost, storybookUrl: `${window.location.protocol}//${newHost}:6006` }); this.render(); } catch (error) { handleError(error, { operation: 'save DSS host', host: newHost }); } finally { this.store.setLoading('saveDssHost', false); } } async resetDSS() { // Confirm with user const confirmed = window.confirm( '⚠️ WARNING: This will delete all user-created themes, projects, cached data, and databases.\n\n' + 'The DSS structure and default themes will be preserved.\n\n' + 'Type "RESET" in the next prompt to confirm.' ); if (!confirmed) { return; } const confirmText = window.prompt('Type RESET (all caps) to confirm:'); if (confirmText !== 'RESET') { this.store.notify('Reset cancelled', 'info'); return; } this.store.setLoading('resetDSS', true); try { this.store.notify('Resetting DSS... This may take a moment.', 'info'); // Call Python reset command via shell const response = await fetch('/api/system/reset', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ confirm: 'RESET' }) }); if (!response.ok) { throw new Error('Reset command failed'); } const result = await response.json(); this.store.notify('✅ DSS has been reset to fresh state!', 'success'); // Reload after a short delay setTimeout(() => { window.location.reload(); }, 2000); } catch (error) { this.store.notify(`Reset failed: ${error.message}`, 'error'); } finally { this.store.setLoading('resetDSS', false); } } async setServerMode(mode) { try { const api = (await import('./api.js')).default; await api.setMode(mode); const config = this.store.get('runtimeConfig') || {}; config.mode = mode; this.store.set({ runtimeConfig: config }); this.store.notify(`Server mode set to ${mode}`, 'success'); this.render(); } catch (error) { this.store.notify(`Failed to set mode: ${error.message}`, 'error'); } } async toggleFeature(featureId) { const config = this.store.get('runtimeConfig') || {}; const features = config.features || {}; const newValue = features[featureId] === false; try { const api = (await import('./api.js')).default; await api.updateConfig({ features: { [featureId]: newValue } }); features[featureId] = newValue; config.features = features; this.store.set({ runtimeConfig: config }); this.store.notify(`${featureId} ${newValue ? 'enabled' : 'disabled'}`, 'success'); this.render(); } catch (error) { this.store.notify(`Failed to toggle feature: ${error.message}`, 'error'); } } async togglePlugin(pluginId) { try { await pluginService.toggle(pluginId); const plugin = pluginService.get(pluginId); this.store.notify(`${plugin.name} ${plugin.enabled ? 'enabled' : 'disabled'}`, 'success'); this.render(); } catch (error) { this.store.notify(`Failed to toggle plugin: ${error.message}`, 'error'); } } setPluginSetting(pluginId, key, value) { pluginService.setPluginSetting(pluginId, key, value); this.store.notify('Setting updated', 'success'); this.render(); } async loadServices() { try { const api = (await import('./api.js')).default; const services = await api.getServices(); this.store.set({ services }); this.render(); } catch (error) { console.error('Failed to load services:', error); } } async initStorybook() { try { this.store.notify('Initializing Storybook...', 'info'); const api = (await import('./api.js')).default; const result = await api.post('/storybook/init', {}); if (result.success) { this.store.notify(result.message || 'Storybook initialized', 'success'); } else { this.store.notify('Failed to initialize Storybook', 'error'); } } catch (error) { console.error('Failed to initialize Storybook:', error); this.store.notify(`Failed to initialize Storybook: ${error.message}`, 'error'); } } async clearStorybook() { try { this.store.notify('Clearing Storybook stories...', 'info'); const api = (await import('./api.js')).default; const result = await api.delete('/storybook/stories'); if (result.success) { this.store.notify(result.message || 'Stories cleared', 'success'); } else { this.store.notify('Failed to clear stories', 'error'); } } catch (error) { console.error('Failed to clear Storybook:', error); this.store.notify(`Failed to clear stories: ${error.message}`, 'error'); } } // === Design System Ingestion === async parseIngestionPrompt() { const input = document.getElementById('ingest-prompt'); if (!input || !input.value.trim()) { this.store.notify('Please enter a design system name or prompt', 'warning'); return; } try { this.store.setLoading('ingestion', true); this.store.notify('Parsing ingestion prompt...', 'info'); const api = (await import('./api.js')).default; const result = await api.post('/ingest/parse', { prompt: input.value.trim(), project_id: this.store.get('currentProject') }); this.store.set({ ingestionResult: result }); this.render(); // Show helpful message if (result.sources?.length > 0 && result.sources[0].matched_system) { this.store.notify(`Found: ${result.sources[0].matched_system.name}`, 'success'); } else if (result.next_steps?.length > 0) { this.store.notify(result.next_steps[0].message || 'Processing...', 'info'); } } catch (error) { console.error('Ingestion parse failed:', error); this.store.notify(`Failed to parse: ${error.message}`, 'error'); } finally { this.store.setLoading('ingestion', false); } } async browseDesignSystems() { try { this.store.notify('Loading design systems...', 'info'); const api = (await import('./api.js')).default; const result = await api.get('/ingest/systems'); this.store.set({ designSystems: result.systems || [], browsingDesignSystems: true, ingestionResult: null }); this.render(); } catch (error) { console.error('Failed to load design systems:', error); this.store.notify(`Failed to load: ${error.message}`, 'error'); } } async selectDesignSystem(systemId) { try { const api = (await import('./api.js')).default; const result = await api.get(`/ingest/systems/${systemId}`); this.store.set({ ingestionResult: { sources: [{ matched_system: result.system }], alternatives: result.alternatives }, browsingDesignSystems: false }); this.render(); } catch (error) { console.error('Failed to select design system:', error); this.store.notify(`Failed: ${error.message}`, 'error'); } } async confirmIngestion(systemId) { try { this.store.setLoading('ingestion', true); this.store.notify('Starting ingestion...', 'info'); const api = (await import('./api.js')).default; const result = await api.post('/ingest/confirm', { system_id: systemId, method: 'npm' // Default to npm }); if (result.success) { this.store.notify(`Queued: ${result.message}`, 'success'); // Show next steps if (result.next_steps?.length > 0) { setTimeout(() => { this.store.notify(`Next: ${result.next_steps.join(' → ')}`, 'info'); }, 1500); } // Clear ingestion UI this.store.set({ ingestionResult: null }); this.render(); } else { this.store.notify('Ingestion failed', 'error'); } } catch (error) { console.error('Ingestion failed:', error); this.store.notify(`Failed: ${error.message}`, 'error'); } finally { this.store.setLoading('ingestion', false); } } async showIngestionAlternatives(systemId) { try { const api = (await import('./api.js')).default; const result = await api.get(`/ingest/alternatives?system_id=${systemId}`); // Update the ingestion result to show alternatives const currentResult = this.store.get('ingestionResult') || {}; this.store.set({ ingestionResult: { ...currentResult, next_steps: [{ action: 'request_source', message: 'Choose an ingestion method:', alternatives: result.alternatives || [] }] } }); this.render(); } catch (error) { console.error('Failed to get alternatives:', error); this.store.notify(`Failed: ${error.message}`, 'error'); } } async searchNpmPackages(query) { try { this.store.notify(`Searching npm for "${query}"...`, 'info'); const api = (await import('./api.js')).default; const result = await api.get(`/ingest/npm/search?query=${encodeURIComponent(query)}`); if (result.packages?.length > 0) { // Show npm results as design systems to choose from this.store.set({ designSystems: result.packages.map(pkg => ({ id: pkg.name, name: pkg.name, description: pkg.description, npm_packages: [pkg.name], category: pkg.is_design_system ? 'npm-design-system' : 'npm-package', homepage: pkg.homepage })), browsingDesignSystems: true, ingestionResult: null }); this.store.notify(`Found ${result.packages.length} packages`, 'success'); } else { this.store.notify('No packages found on npm', 'warning'); } this.render(); } catch (error) { console.error('npm search failed:', error); this.store.notify(`Search failed: ${error.message}`, 'error'); } } selectIngestionMethod(methodType) { // Show appropriate UI for the selected method const prompts = { figma_url: 'Enter Figma file URL:', css_url: 'Enter CSS file URL:', github_url: 'Enter GitHub repository URL:', image_url: 'Upload or enter image URL:', text_description: 'Describe your design tokens:' }; const prompt = prompts[methodType] || 'Enter source:'; const value = window.prompt(prompt); if (value) { // Store the method and source for later use this.store.set({ ingestionMethod: methodType, ingestionSource: value }); this.store.notify(`Source saved. Click "Ingest" to proceed.`, 'success'); } } async loadConfig() { try { const api = (await import('./api.js')).default; const [configData, figmaConfig, services] = await Promise.all([ api.getConfig(), api.getFigmaConfig(), api.getServices() ]); this.store.set({ runtimeConfig: configData.config || {}, figmaConfig: figmaConfig || {}, services: services || {} }); } catch (error) { console.error('Failed to load config:', error); } } // === Project Management === async loadProjects() { try { const api = (await import('./api.js')).default; const projects = await api.getProjects(); this.store.setProjects(Array.isArray(projects) ? projects : []); // Render project selector after projects are loaded this.renderProjectSelector(); } catch (error) { console.error('Failed to load projects:', error); this.store.setProjects([]); this.renderProjectSelector(); } } async loadDashboardData(projectId) { if (!projectId) return; try { logger.info('App', 'Loading dashboard data', { projectId }); const dashboardData = await DashboardService.getDashboardSummary(projectId); this.store.set({ dashboardData }); this.render(); // Re-render to show updated data } catch (error) { logger.error('App', 'Failed to load dashboard data', error); // Set empty dashboard data on error this.store.set({ dashboardData: { ux: { figma_files_count: 0, figma_files: [] }, ui: { token_drift: { total: 0, by_severity: {} }, code_metrics: {} }, qa: { esre_count: 0, test_summary: {} } } }); } } renderProjectSelector() { const container = document.getElementById('project-selector-container'); if (!container) return; const projects = this.store.get('projects') || []; const currentProject = this.store.get('currentProject') || (projects.length > 0 ? projects[0].id : null); // Restore from localStorage if available const savedProject = localStorage.getItem('dss_current_project'); if (savedProject && projects.find(p => p.id === savedProject)) { this.store.set({ currentProject: savedProject }); } else if (currentProject) { this.store.set({ currentProject }); } const html = `
Project:
`; setSafeHtml(container, html); // Attach event listener const select = document.getElementById('project-select'); if (select) { select.addEventListener('change', (e) => { const projectId = e.target.value; this.handleProjectChange(projectId); }); } } handleProjectChange(projectId) { logger.info('App', 'Project context changed', { projectId }); // Save to store and localStorage this.store.set({ currentProject: projectId }); localStorage.setItem('dss_current_project', projectId); // Get project details const projects = this.store.get('projects') || []; const project = projects.find(p => p.id === projectId); // Trigger refresh this.render(); logger.info('App', 'Project context set', { project: project?.name }); } // === Audit Log === renderAuditLog() { // Load audit log data after render setTimeout(() => this.loadAuditLog(), 100); return `
Filters
Apply Filters Clear Filters 📥 Export JSON 📥 Export CSV
Activity History Loading...
Loading audit log...
`; } async loadAuditLog() { const auditService = (await import('../services/audit-service.js')).default; const content = document.getElementById('audit-log-content'); if (!content) return; // Get filter values const filters = { category: document.getElementById('audit-category-filter')?.value || '', severity: document.getElementById('audit-severity-filter')?.value || '', start_date: document.getElementById('audit-start-date')?.value || '', end_date: document.getElementById('audit-end-date')?.value || '', limit: 50, offset: this.auditLogOffset || 0 }; // Add current project filter if set const currentProject = this.store.get('currentProject'); if (currentProject) { filters.project_id = currentProject; } try { const result = await auditService.getAuditLog(filters); // Update total count const totalCount = document.getElementById('audit-total-count'); if (totalCount) { totalCount.textContent = `${result.total} total events`; } // Render table if (result.activities && result.activities.length > 0) { const html = ` ${result.activities.map(activity => ` `).join('')}
Time User Action Category Description Severity
${escapeHtml(auditService.formatTimestamp(activity.created_at))} ${escapeHtml(activity.user_name || activity.user_id || 'System')} ${escapeHtml(activity.action)} ${auditService.getCategoryIcon(activity.category)} ${escapeHtml(activity.category || 'other')} ${escapeHtml(activity.description || '-')} ${escapeHtml(activity.severity)}
`; setSafeHtml(content, html); } else { content.innerHTML = `
No audit entries found
`; } // Update pagination const pagination = document.getElementById('audit-pagination'); if (pagination && result.total > 0) { pagination.style.display = 'flex'; const showing = document.getElementById('audit-showing'); const total = document.getElementById('audit-total'); const prevBtn = document.getElementById('audit-prev-btn'); const nextBtn = document.getElementById('audit-next-btn'); const start = filters.offset + 1; const end = Math.min(filters.offset + result.activities.length, result.total); if (showing) showing.textContent = `${start}-${end}`; if (total) total.textContent = result.total; if (prevBtn) { if (filters.offset === 0) { prevBtn.setAttribute('disabled', ''); } else { prevBtn.removeAttribute('disabled'); } } if (nextBtn) { if (!result.has_more) { nextBtn.setAttribute('disabled', ''); } else { nextBtn.removeAttribute('disabled'); } } this.auditLogData = result; } } catch (error) { content.innerHTML = `
Failed to load audit log: ${error.message}
`; } } clearAuditFilters() { document.getElementById('audit-category-filter').value = ''; document.getElementById('audit-severity-filter').value = ''; document.getElementById('audit-start-date').value = ''; document.getElementById('audit-end-date').value = ''; this.auditLogOffset = 0; this.loadAuditLog(); } nextAuditPage() { const limit = 50; this.auditLogOffset = (this.auditLogOffset || 0) + limit; this.loadAuditLog(); } prevAuditPage() { const limit = 50; this.auditLogOffset = Math.max(0, (this.auditLogOffset || 0) - limit); this.loadAuditLog(); } async exportAuditLog(format = 'json') { const auditService = (await import('../services/audit-service.js')).default; const filters = { category: document.getElementById('audit-category-filter')?.value || '', start_date: document.getElementById('audit-start-date')?.value || '', end_date: document.getElementById('audit-end-date')?.value || '' }; // Add current project filter const currentProject = this.store.get('currentProject'); if (currentProject) { filters.project_id = currentProject; } await auditService.export(filters, format); this.store.notify(`Audit log export started (${format})`, 'success'); } showAuditDetails(id) { // TODO: Show modal with full audit entry details console.log('Show audit details for:', id); } async createProject(name, description, figmaFileKey) { this.store.setLoading('createProject', true); try { const api = (await import('./api.js')).default; const project = await api.createProject({ name, description, figma_file_key: figmaFileKey }); this.store.addProject(project); this.store.set({ showCreateProjectForm: false }); this.store.notify(`Project "${name}" created successfully`, 'success'); this.render(); return project; } catch (error) { this.store.notify(`Failed to create project: ${error.message}`, 'error'); throw error; } finally { this.store.setLoading('createProject', false); } } async updateProjectDescription(projectId, description) { this.store.setLoading('updateProject', true); try { const api = (await import('./api.js')).default; await api.updateProject(projectId, { description }); // Update project in store const projects = this.store.get('projects').map(p => p.id === projectId ? { ...p, description } : p ); this.store.setProjects(projects); // Update selected project const selectedProject = this.store.get('selectedProject'); if (selectedProject && selectedProject.id === projectId) { this.store.set({ selectedProject: { ...selectedProject, description } }); } this.store.notify('Project description updated', 'success'); this.render(); } catch (error) { this.store.notify(`Failed to update description: ${error.message}`, 'error'); } finally { this.store.setLoading('updateProject', false); } } async deleteProject(projectId) { if (!confirm('Are you sure you want to delete this project? This cannot be undone.')) { return; } this.store.setLoading('deleteProject', true); try { const api = (await import('./api.js')).default; await api.deleteProject(projectId); const projects = this.store.get('projects').filter(p => p.id !== projectId); this.store.setProjects(projects); this.store.notify('Project deleted', 'success'); this.render(); } catch (error) { this.store.notify(`Failed to delete project: ${error.message}`, 'error'); } finally { this.store.setLoading('deleteProject', false); } } selectProject(projectId) { const project = this.store.get('projects').find(p => p.id === projectId); if (project) { this.store.set({ selectedProject: project, figmaFileKey: project.figma_file_key }); this.store.notify(`Selected project: ${project.name}`, 'info'); // Navigate to dashboard with project context this.navigate('dashboard'); } } async syncProjectTokens(projectId, figmaKey) { if (!figmaKey) { this.store.notify('No Figma file key configured for this project', 'warning'); return; } this.store.set({ figmaFileKey: figmaKey }); await this.extractTokens(); await this.loadProjects(); // Refresh to get updated last_sync } // === Notifications === renderNotifications(notifications) { let container = document.getElementById('notifications'); if (!container) { container = document.createElement('div'); container.id = 'notifications'; container.className = 'notification-container'; // Use CSS class instead of inline styles document.body.appendChild(container); } const html = notifications.map(n => { const notificationType = escapeHtml(n.type); const notificationMessage = escapeHtml(n.message); return `
${notificationMessage}
`; }).join(''); setSafeHtml(container, html); } // === Services Page === renderServices() { const teamContext = this.store.get('teamContext') || localStorage.getItem('dss_team_context') || 'all'; const tools = toolsService.getToolsByTeam(teamContext); const categories = toolsService.getCategories(); const teamNames = { 'all': 'All Tools', 'ui': 'UI Team', 'ux': 'UX Team', 'qa': 'QA Team' }; // Group tools by category const toolsByCategory = {}; tools.forEach(tool => { if (!toolsByCategory[tool.category]) { toolsByCategory[tool.category] = []; } toolsByCategory[tool.category].push(tool); }); // Category icons const categoryIcons = { 'Projects': '', 'Figma': '', 'Ingestion': '', 'Analysis': '', 'Storybook': '', 'Activity': '' }; logger.info('UI', 'Rendering services page', { toolCount: tools.length, teamContext, categories }); return `
${categories.map(category => { const categoryTools = toolsByCategory[category] || []; if (categoryTools.length === 0) return ''; return `
${categoryIcons[category] || '📦'} ${category} ${categoryTools.length}
${categoryTools.map(tool => this.renderToolCard(tool)).join('')}
`; }).join('')}
`; } renderToolCard(tool) { return `
${tool.icon}
${tool.name.replace(/_/g, ' ')}
${tool.description}
${tool.parameters.length > 0 ? `${tool.parameters.length}p` : ''}
`; } // === Quick Wins Page === renderQuickWins() { logger.info('UI', 'Rendering quick wins page'); return `

Click "Analyze Project" to discover quick wins

`; } async loadQuickWins() { const path = document.getElementById('projectPath')?.value || '.'; const container = document.getElementById('quickWinsContainer'); if (!container) return; logger.info('QuickWins', 'Loading quick wins', { path }); container.innerHTML = '
Analyzing project...
'; try { const result = await toolsService.executeTool('get_quick_wins', { path }); const data = typeof result === 'string' ? JSON.parse(result) : result; logger.info('QuickWins', 'Quick wins loaded', data); container.innerHTML = this.renderQuickWinsResults(data); } catch (error) { logger.error('QuickWins', 'Failed to load quick wins', error); container.innerHTML = `

Failed to load quick wins: ${error.message}

`; } } renderQuickWinsResults(data) { const wins = data.wins || data.quick_wins || []; if (wins.length === 0) { return '

No quick wins found. Great job!

'; } const byPriority = { CRITICAL: wins.filter(w => w.priority === 'CRITICAL'), HIGH: wins.filter(w => w.priority === 'HIGH'), MEDIUM: wins.filter(w => w.priority === 'MEDIUM'), LOW: wins.filter(w => w.priority === 'LOW') }; return `
Critical
${byPriority.CRITICAL.length}
High
${byPriority.HIGH.length}
Medium
${byPriority.MEDIUM.length}
Low
${byPriority.LOW.length}
${wins.map(win => this.renderQuickWinCard(win)).join('')}
`; } renderQuickWinCard(win) { const priorityColors = { CRITICAL: 'destructive', HIGH: 'warning', MEDIUM: 'primary', LOW: 'muted' }; const color = priorityColors[win.priority] || 'muted'; return `
${win.priority} ${win.type}
${win.effort ? `Effort: ${win.effort}` : ''}

${win.title}

${win.description}

${win.file ? `
📄 ${win.file}
` : ''} ${win.impact ? `
Impact: ${win.impact}
` : ''}
`; } // === Claude Chat Page === renderChat() { const history = claudeService.getHistory(); const selectedModel = this.store.get('selectedAIModel') || 'claude'; logger.info('UI', 'Rendering chat page', { historyLength: history.length, model: selectedModel }); return `
${history.length === 0 ? `

👋 Hi! I'm Claude

Ask me anything about your design system, tokens, or code

` : history.map(msg => this.renderChatMessage(msg)).join('')}
`; } renderChatMessage(msg) { const isUser = msg.role === 'user'; const bgColor = isUser ? 'bg-primary/10' : 'bg-card'; const align = isUser ? 'ml-auto' : ''; const icon = isUser ? '👤' : '🤖'; return `
${icon} ${isUser ? 'You' : 'Claude'} ${this.formatTime(msg.timestamp)}
${this.escapeHtml(msg.content)}
`; } async sendChatMessage(e) { if (e) e.preventDefault(); const input = document.getElementById('chatInput'); const message = input?.value.trim(); if (!message) return; logger.info('Chat', 'Sending message', { message }); // Clear input input.value = ''; // Add user message to UI immediately const messagesContainer = document.getElementById('chatMessages'); if (messagesContainer) { messagesContainer.innerHTML += this.renderChatMessage({ role: 'user', content: message, timestamp: new Date().toISOString() }); // Add loading indicator messagesContainer.innerHTML += `
🤖 Claude is typing...
`; // Scroll to bottom messagesContainer.scrollTop = messagesContainer.scrollHeight; } try { const response = await claudeService.chat(message, { project: this.store.get('currentProject'), page: 'chat' }); // Remove loading indicator document.getElementById('chatLoading')?.remove(); // Add Claude's response if (messagesContainer) { messagesContainer.innerHTML += this.renderChatMessage({ role: 'assistant', content: response, timestamp: new Date().toISOString() }); messagesContainer.scrollTop = messagesContainer.scrollHeight; } } catch (error) { logger.error('Chat', 'Failed to send message', error); document.getElementById('chatLoading')?.remove(); if (messagesContainer) { messagesContainer.innerHTML += `
❌ Failed to get response: ${error.message}
`; } this.store.notify(`Chat error: ${error.message}`, 'error'); } } // === Tool Execution Actions === async executeToolWithParams(toolName) { logger.info('ToolExecution', `Preparing to execute ${toolName}`); const tool = toolsService.toolsMetadata[toolName]; if (!tool) { logger.error('ToolExecution', `Tool not found: ${toolName}`); return; } // For simple tools with no required params, execute directly if (tool.parameters.length === 0) { try { this.store.notify(`Executing ${toolName}...`, 'info'); const result = await toolsService.executeTool(toolName, {}); logger.info('ToolExecution', `Tool ${toolName} completed`, result); this.store.notify(`${toolName} completed successfully`, 'success'); this.render(); } catch (error) { logger.error('ToolExecution', `Tool ${toolName} failed`, error); this.store.notify(`${toolName} failed: ${error.message}`, 'error'); } return; } // TODO: For tools with parameters, show a modal to collect params this.store.notify(`${toolName} requires parameters. Parameter input UI coming soon.`, 'info'); } filterTools(searchTerm, category) { const cards = document.querySelectorAll('.tool-card'); const search = searchTerm.toLowerCase(); cards.forEach(card => { const toolName = card.dataset.tool?.toLowerCase() || ''; const toolCategory = card.dataset.category || ''; const matchesSearch = !search || toolName.includes(search); const matchesCategory = !category || toolCategory === category; card.style.display = (matchesSearch && matchesCategory) ? 'block' : 'none'; }); } investigateWin(winId) { logger.info('QuickWins', `Investigating win: ${winId}`); this.store.notify('Investigation feature coming soon', 'info'); } markWinDone(winId) { logger.info('QuickWins', `Marking win as done: ${winId}`); this.store.notify('Win marked as done', 'success'); // TODO: Persist to backend } // === Team Dashboard Actions === async addFigmaFile(figmaData) { const projectId = this.store.get('currentProject'); if (!projectId) { this.store.notify('Please select a project first', 'warning'); return; } try { const result = await DashboardService.addFigmaFile(projectId, figmaData); this.store.notify('Figma file added successfully', 'success'); await this.loadDashboardData(projectId); } catch (error) { this.store.notify(`Failed to add Figma file: ${error.message}`, 'error'); } } async deleteFigmaFile(fileId) { const projectId = this.store.get('currentProject'); if (!projectId) return; try { await DashboardService.deleteFigmaFile(projectId, fileId); this.store.notify('Figma file deleted', 'success'); await this.loadDashboardData(projectId); } catch (error) { this.store.notify(`Failed to delete Figma file: ${error.message}`, 'error'); } } async syncFigmaFile(fileId) { const projectId = this.store.get('currentProject'); if (!projectId) return; try { await DashboardService.updateFigmaFileSync(projectId, fileId, 'syncing'); this.store.notify('Syncing Figma file...', 'info'); // TODO: Implement actual sync logic setTimeout(async () => { await DashboardService.updateFigmaFileSync(projectId, fileId, 'success'); this.store.notify('Figma file synced successfully', 'success'); await this.loadDashboardData(projectId); }, 2000); } catch (error) { this.store.notify(`Failed to sync Figma file: ${error.message}`, 'error'); } } async addESREDefinition(esreData) { const projectId = this.store.get('currentProject'); if (!projectId) { this.store.notify('Please select a project first', 'warning'); return; } try { const result = await DashboardService.createESREDefinition(projectId, esreData); this.store.notify('ESRE definition added successfully', 'success'); await this.loadDashboardData(projectId); } catch (error) { this.store.notify(`Failed to add ESRE definition: ${error.message}`, 'error'); } } // === Utilities === escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } formatTime(timestamp) { const date = new Date(timestamp); const now = new Date(); const diff = now - date; if (diff < 60000) return 'Just now'; if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`; if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`; return date.toLocaleDateString(); } /** * Update Storybook link with configured URL * Called on init and when settings are saved */ updateStorybookLink() { const link = document.getElementById('storybook-link'); if (!link) return; try { // Get Storybook URL from server config (loaded from /api/config) const storybookUrl = getStorybookUrl(); link.href = storybookUrl; link.style.pointerEvents = 'auto'; link.style.opacity = '1'; link.title = `Open Storybook at ${storybookUrl}`; } catch (error) { // Config not loaded yet or error occurred link.style.pointerEvents = 'none'; link.style.opacity = '0.5'; link.removeAttribute('href'); link.title = 'Storybook URL not available'; logger.warn('App', 'Could not update Storybook link', { error: error.message }); } } } // Global instance const app = new App(); export default app;