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,334 @@
/**
* component-helpers.js
* Shared utilities for panel components
* Provides standardized loading, error, and empty states
*/
export class ComponentHelpers {
/**
* Render a loading spinner with optional message
* @param {string} message - Optional loading message
* @returns {string} HTML string for loading state
*/
static renderLoading(message = 'Loading...') {
return `
<div style="
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 32px;
color: var(--vscode-text-dim);
">
<div style="
width: 24px;
height: 24px;
border: 3px solid var(--vscode-border);
border-top-color: var(--vscode-accent);
border-radius: 50%;
animation: spin 1s linear infinite;
"></div>
<div style="margin-top: 16px; font-size: 12px;">${message}</div>
</div>
<style>
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
`;
}
/**
* Render an error state with message
* @param {string} message - Error message to display
* @param {Error} error - Optional error object for details
* @returns {string} HTML string for error state
*/
static renderError(message, error = null) {
const details = error ? `<div style="
margin-top: 8px;
padding: 8px;
background-color: rgba(244, 135, 113, 0.1);
border-radius: 2px;
font-family: 'Courier New', monospace;
font-size: 11px;
color: #f48771;
">${error.message || error.toString()}</div>` : '';
return `
<div style="
padding: 24px;
text-align: center;
color: #f48771;
">
<div style="font-size: 32px; margin-bottom: 12px;">⚠️</div>
<div style="font-size: 13px; font-weight: 500;">${message}</div>
${details}
</div>
`;
}
/**
* Render an empty state with message
* @param {string} message - Empty state message
* @param {string} icon - Optional icon/emoji
* @returns {string} HTML string for empty state
*/
static renderEmpty(message = 'No data available', icon = '📭') {
return `
<div style="
padding: 48px 24px;
text-align: center;
color: var(--vscode-text-dim);
">
<div style="font-size: 48px; margin-bottom: 16px;">${icon}</div>
<div style="font-size: 13px;">${message}</div>
</div>
`;
}
/**
* Format timestamp for display
* @param {Date|string|number} date - Date to format
* @param {boolean} includeTime - Include time in output
* @returns {string} Formatted date string
*/
static formatTimestamp(date, includeTime = true) {
if (!date) return 'N/A';
const d = date instanceof Date ? date : new Date(date);
if (isNaN(d.getTime())) return 'Invalid Date';
const dateStr = d.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
if (!includeTime) return dateStr;
const timeStr = d.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
});
return `${dateStr} ${timeStr}`;
}
/**
* Format relative time (e.g., "2 minutes ago")
* @param {Date|string|number} date - Date to format
* @returns {string} Relative time string
*/
static formatRelativeTime(date) {
if (!date) return 'N/A';
const d = date instanceof Date ? date : new Date(date);
if (isNaN(d.getTime())) return 'Invalid Date';
const now = new Date();
const diffMs = now - d;
const diffSec = Math.floor(diffMs / 1000);
const diffMin = Math.floor(diffSec / 60);
const diffHour = Math.floor(diffMin / 60);
const diffDay = Math.floor(diffHour / 24);
if (diffSec < 60) return 'just now';
if (diffMin < 60) return `${diffMin} minute${diffMin !== 1 ? 's' : ''} ago`;
if (diffHour < 24) return `${diffHour} hour${diffHour !== 1 ? 's' : ''} ago`;
if (diffDay < 7) return `${diffDay} day${diffDay !== 1 ? 's' : ''} ago`;
return this.formatTimestamp(d, false);
}
/**
* Truncate text to max length with ellipsis
* @param {string} text - Text to truncate
* @param {number} maxLength - Maximum length
* @returns {string} Truncated text
*/
static truncateText(text, maxLength = 100) {
if (!text || text.length <= maxLength) return text;
return text.substring(0, maxLength - 3) + '...';
}
/**
* Escape HTML to prevent XSS
* @param {string} text - Text to escape
* @returns {string} Escaped HTML
*/
static escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
/**
* Format file size in human-readable format
* @param {number} bytes - Size in bytes
* @returns {string} Formatted size string
*/
static formatFileSize(bytes) {
if (bytes === 0) return '0 B';
if (!bytes) return 'N/A';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
}
/**
* Format duration in human-readable format
* @param {number} ms - Duration in milliseconds
* @returns {string} Formatted duration string
*/
static formatDuration(ms) {
if (ms === 0) return '0ms';
if (!ms) return 'N/A';
if (ms < 1000) return `${Math.round(ms)}ms`;
if (ms < 60000) return `${(ms / 1000).toFixed(2)}s`;
if (ms < 3600000) return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`;
const hours = Math.floor(ms / 3600000);
const minutes = Math.floor((ms % 3600000) / 60000);
return `${hours}h ${minutes}m`;
}
/**
* Create a badge element
* @param {string} label - Badge label
* @param {string} type - Badge type (success, warning, error, info)
* @returns {string} HTML string for badge
*/
static createBadge(label, type = 'info') {
const colors = {
success: '#89d185',
warning: '#dbb765',
error: '#f48771',
info: '#75beff'
};
const color = colors[type] || colors.info;
return `
<span style="
display: inline-block;
padding: 2px 8px;
background-color: rgba(${this.hexToRgb(color)}, 0.2);
color: ${color};
border-radius: 2px;
font-size: 11px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
">${label}</span>
`;
}
/**
* Convert hex color to RGB values
* @param {string} hex - Hex color
* @returns {string} RGB values (e.g., "255, 0, 0")
*/
static hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}`
: '128, 128, 128';
}
/**
* Debounce function execution
* @param {Function} func - Function to debounce
* @param {number} wait - Wait time in milliseconds
* @returns {Function} Debounced function
*/
static debounce(func, wait = 300) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
/**
* Parse JSON safely with fallback
* @param {string} jsonString - JSON string to parse
* @param {*} fallback - Fallback value if parse fails
* @returns {*} Parsed object or fallback
*/
static safeJsonParse(jsonString, fallback = null) {
try {
return JSON.parse(jsonString);
} catch (e) {
console.warn('Failed to parse JSON:', e);
return fallback;
}
}
/**
* Create a table from array of objects
* @param {Array<Object>} data - Array of objects
* @param {Array<string>} columns - Column names to display
* @returns {string} HTML string for table
*/
static createTable(data, columns = null) {
if (!data || data.length === 0) {
return this.renderEmpty('No data to display', '📋');
}
const cols = columns || Object.keys(data[0]);
const headerRow = cols.map(col =>
`<th style="
padding: 8px 12px;
text-align: left;
border-bottom: 1px solid var(--vscode-border);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--vscode-text-dim);
">${col}</th>`
).join('');
const dataRows = data.map(row => `
<tr style="border-bottom: 1px solid var(--vscode-border);">
${cols.map(col => `
<td style="
padding: 8px 12px;
font-size: 12px;
color: var(--vscode-text);
">${this.escapeHtml(String(row[col] ?? ''))}</td>
`).join('')}
</tr>
`).join('');
return `
<div style="overflow-x: auto;">
<table style="
width: 100%;
border-collapse: collapse;
background-color: var(--vscode-sidebar);
">
<thead>
<tr>${headerRow}</tr>
</thead>
<tbody>
${dataRows}
</tbody>
</table>
</div>
`;
}
}