Initial commit: Clean DSS implementation

Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm

Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)

Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability

Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
Digital Production Factory
2025-12-09 18:45:48 -03:00
commit 276ed71f31
884 changed files with 373737 additions and 0 deletions

View File

@@ -0,0 +1,435 @@
/**
* DSS Theme Loader Service
*
* Manages the loading and hot-reloading of DSS CSS layers.
* Handles the "Bootstrapping Paradox" by providing fallback mechanisms
* when token files are missing or corrupted.
*
* CSS Layer Order:
* 1. dss-core.css (structural) - REQUIRED, always loaded first
* 2. dss-tokens.css (design tokens) - Can be regenerated from Figma
* 3. dss-theme.css (semantic mapping) - Maps tokens to purposes
* 4. dss-components.css (styled components) - Uses semantic tokens
*/
import logger from './logger.js';
import { notifySuccess, notifyError, notifyInfo, ErrorCode } from './messaging.js';
// CSS Layer definitions with fallback behavior
const CSS_LAYERS = [
{
id: 'dss-core',
path: '/admin-ui/css/dss-core.css',
name: 'Core/Structural',
required: true,
fallback: null // No fallback - this is the baseline
},
{
id: 'dss-tokens',
path: '/admin-ui/css/dss-tokens.css',
name: 'Design Tokens',
required: false,
fallback: '/admin-ui/css/dss-tokens-fallback.css'
},
{
id: 'dss-theme',
path: '/admin-ui/css/dss-theme.css',
name: 'Semantic Theme',
required: false,
fallback: null
},
{
id: 'dss-components',
path: '/admin-ui/css/dss-components.css',
name: 'Component Styles',
required: false,
fallback: null
}
];
class ThemeLoaderService {
constructor() {
this.layers = new Map();
this.isInitialized = false;
this.healthCheckInterval = null;
this.listeners = new Set();
}
/**
* Initialize the theme loader
* Validates all CSS layers are loaded and functional
*/
async init() {
logger.info('ThemeLoader', 'Initializing DSS Theme Loader...');
try {
// Perform health check on all layers
const healthStatus = await this.healthCheck();
if (healthStatus.allHealthy) {
logger.info('ThemeLoader', 'All CSS layers loaded successfully');
this.isInitialized = true;
notifySuccess('Design system styles loaded successfully');
} else {
logger.warn('ThemeLoader', 'Some CSS layers failed to load', {
failed: healthStatus.failed
});
notifyInfo(`Design system loaded with ${healthStatus.failed.length} layer(s) using fallbacks`);
}
// Start periodic health checks (every 30 seconds)
this.startHealthCheckInterval();
return healthStatus;
} catch (error) {
logger.error('ThemeLoader', 'Failed to initialize theme loader', { error: error.message });
notifyError('Failed to load design system styles', ErrorCode.SYSTEM_STARTUP_FAILED);
throw error;
}
}
/**
* Check health of all CSS layers
* Returns status of each layer and overall health
*/
async healthCheck() {
const results = {
allHealthy: true,
layers: [],
failed: [],
timestamp: new Date().toISOString()
};
for (const layer of CSS_LAYERS) {
const linkElement = document.querySelector(`link[href*="${layer.id}"]`);
const status = {
id: layer.id,
name: layer.name,
loaded: false,
path: layer.path,
error: null
};
if (linkElement) {
// Check if stylesheet is loaded and accessible
try {
const response = await fetch(layer.path, { method: 'HEAD' });
status.loaded = response.ok;
if (!response.ok) {
status.error = `HTTP ${response.status}`;
}
} catch (error) {
status.error = error.message;
}
} else {
status.error = 'Link element not found in DOM';
}
if (!status.loaded && layer.required) {
results.allHealthy = false;
results.failed.push(layer.id);
} else if (!status.loaded && !layer.required && layer.fallback) {
// Try to load fallback
await this.loadFallback(layer);
}
results.layers.push(status);
this.layers.set(layer.id, status);
}
// Notify listeners of health check results
this.notifyListeners('healthCheck', results);
return results;
}
/**
* Load a fallback CSS file for a failed layer
*/
async loadFallback(layer) {
if (!layer.fallback) return false;
try {
const response = await fetch(layer.fallback, { method: 'HEAD' });
if (response.ok) {
const linkElement = document.querySelector(`link[href*="${layer.id}"]`);
if (linkElement) {
linkElement.href = layer.fallback;
logger.info('ThemeLoader', `Loaded fallback for ${layer.name}`, { fallback: layer.fallback });
return true;
}
}
} catch (error) {
logger.warn('ThemeLoader', `Failed to load fallback for ${layer.name}`, { error: error.message });
}
return false;
}
/**
* Reload a specific CSS layer
* Used when tokens are regenerated from Figma
*/
async reloadLayer(layerId) {
const layer = CSS_LAYERS.find(l => l.id === layerId);
if (!layer) {
logger.warn('ThemeLoader', `Unknown layer: ${layerId}`);
return false;
}
logger.info('ThemeLoader', `Reloading layer: ${layer.name}`);
try {
const linkElement = document.querySelector(`link[href*="${layer.id}"]`);
if (!linkElement) {
logger.error('ThemeLoader', `Link element not found for ${layer.id}`);
return false;
}
// Force reload by adding cache-busting timestamp
const timestamp = Date.now();
const newHref = `${layer.path}?t=${timestamp}`;
// Create a promise that resolves when the new stylesheet loads
return new Promise((resolve, reject) => {
const tempLink = document.createElement('link');
tempLink.rel = 'stylesheet';
tempLink.href = newHref;
tempLink.onload = () => {
// Replace old link with new one
linkElement.href = newHref;
tempLink.remove();
logger.info('ThemeLoader', `Successfully reloaded ${layer.name}`);
this.notifyListeners('layerReloaded', { layerId, timestamp });
resolve(true);
};
tempLink.onerror = () => {
tempLink.remove();
logger.error('ThemeLoader', `Failed to reload ${layer.name}`);
reject(new Error(`Failed to reload ${layer.name}`));
};
// Add temp link to head to trigger load
document.head.appendChild(tempLink);
// Timeout after 5 seconds
setTimeout(() => {
if (document.head.contains(tempLink)) {
tempLink.remove();
reject(new Error(`Timeout loading ${layer.name}`));
}
}, 5000);
});
} catch (error) {
logger.error('ThemeLoader', `Error reloading ${layer.name}`, { error: error.message });
return false;
}
}
/**
* Reload all CSS layers (hot reload)
*/
async reloadAllLayers() {
logger.info('ThemeLoader', 'Reloading all CSS layers...');
notifyInfo('Reloading design system styles...');
const results = [];
for (const layer of CSS_LAYERS) {
const success = await this.reloadLayer(layer.id);
results.push({ id: layer.id, success });
}
const failed = results.filter(r => !r.success);
if (failed.length === 0) {
notifySuccess('All styles reloaded successfully');
} else {
notifyError(`Failed to reload ${failed.length} layer(s)`);
}
return results;
}
/**
* Reload only the tokens layer
* Used after Figma sync or token generation
*/
async reloadTokens() {
logger.info('ThemeLoader', 'Reloading design tokens...');
notifyInfo('Applying new design tokens...');
try {
await this.reloadLayer('dss-tokens');
// Also reload theme since it depends on tokens
await this.reloadLayer('dss-theme');
notifySuccess('Design tokens applied successfully');
return true;
} catch (error) {
notifyError('Failed to apply design tokens');
return false;
}
}
/**
* Start periodic health check interval
*/
startHealthCheckInterval(intervalMs = 30000) {
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
}
this.healthCheckInterval = setInterval(async () => {
const status = await this.healthCheck();
if (!status.allHealthy) {
logger.warn('ThemeLoader', 'Health check detected issues', {
failed: status.failed
});
}
}, intervalMs);
}
/**
* Stop periodic health checks
*/
stopHealthCheckInterval() {
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
this.healthCheckInterval = null;
}
}
/**
* Get current theme status
*/
getStatus() {
return {
initialized: this.isInitialized,
layers: Array.from(this.layers.values()),
layerCount: CSS_LAYERS.length
};
}
/**
* Subscribe to theme loader events
*/
subscribe(callback) {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
}
/**
* Notify all listeners of an event
*/
notifyListeners(event, data) {
this.listeners.forEach(callback => {
try {
callback(event, data);
} catch (error) {
logger.error('ThemeLoader', 'Listener error', { error: error.message });
}
});
}
/**
* Generate CSS token file from extracted tokens
* This is called after Figma sync to create dss-tokens.css
*/
async generateTokensFile(tokens) {
logger.info('ThemeLoader', 'Generating tokens CSS file...');
// Convert tokens object to CSS custom properties
const cssContent = this.tokensToCSS(tokens);
// Send to backend to save file
try {
const response = await fetch('/api/dss/save-tokens', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content: cssContent,
path: '/admin-ui/css/dss-tokens.css'
})
});
if (response.ok) {
logger.info('ThemeLoader', 'Tokens file generated successfully');
await this.reloadTokens();
return true;
} else {
throw new Error(`Server returned ${response.status}`);
}
} catch (error) {
logger.error('ThemeLoader', 'Failed to generate tokens file', { error: error.message });
notifyError('Failed to save design tokens');
return false;
}
}
/**
* Convert tokens object to CSS custom properties
*/
tokensToCSS(tokens) {
let css = `/**
* DSS Design Tokens - Generated ${new Date().toISOString()}
* Source: Figma extraction
*/
:root {
`;
// Recursively flatten tokens and create CSS variables
const flattenTokens = (obj, prefix = '--ds') => {
let result = '';
for (const [key, value] of Object.entries(obj)) {
const varName = `${prefix}-${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`;
if (typeof value === 'object' && value !== null && !value.$value) {
result += flattenTokens(value, varName);
} else {
const cssValue = value.$value || value;
result += ` ${varName}: ${cssValue};\n`;
}
}
return result;
};
css += flattenTokens(tokens);
css += '}\n';
return css;
}
/**
* Export current tokens as JSON
*/
async exportTokens() {
const computedStyle = getComputedStyle(document.documentElement);
const tokens = {};
// Extract all --ds-* variables
const styleSheets = document.styleSheets;
for (const sheet of styleSheets) {
try {
for (const rule of sheet.cssRules) {
if (rule.selectorText === ':root') {
const text = rule.cssText;
const matches = text.matchAll(/--ds-([^:]+):\s*([^;]+);/g);
for (const match of matches) {
const [, name, value] = match;
tokens[name] = value.trim();
}
}
}
} catch (e) {
// CORS restrictions on external stylesheets
}
}
return tokens;
}
}
// Export singleton instance
const themeLoader = new ThemeLoaderService();
export default themeLoader;
export { ThemeLoaderService, CSS_LAYERS };