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,399 @@
/**
* @fileoverview A reusable stepper component for guided workflows.
* Supports step dependencies, persistence, and event-driven actions.
*/
const ICONS = {
pending: '',
active: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<path d="M12 6v6l4 2"/>
</svg>`,
completed: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<polyline points="20 6 9 17 4 12"/>
</svg>`,
error: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>`,
skipped: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="5" y1="12" x2="19" y2="12"/>
</svg>`
};
class DsWorkflow extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this._steps = [];
}
static get observedAttributes() {
return ['workflow-id'];
}
get workflowId() {
return this.getAttribute('workflow-id');
}
set steps(stepsArray) {
this._steps = stepsArray.map(s => ({
status: 'pending',
optional: false,
dependsOn: [],
...s
}));
this._loadState();
this._render();
}
get steps() {
return this._steps;
}
connectedCallback() {
this._renderBase();
}
_loadState() {
if (!this.workflowId) return;
try {
const savedState = JSON.parse(localStorage.getItem(`dss_workflow_${this.workflowId}`));
if (savedState) {
this._steps.forEach(step => {
if (savedState[step.id]) {
step.status = savedState[step.id].status;
if (savedState[step.id].message) {
step.message = savedState[step.id].message;
}
}
});
}
} catch (e) {
console.error('Failed to load workflow state:', e);
}
}
_saveState() {
if (!this.workflowId) return;
const stateToSave = this._steps.reduce((acc, step) => {
acc[step.id] = {
status: step.status,
message: step.message || null
};
return acc;
}, {});
localStorage.setItem(`dss_workflow_${this.workflowId}`, JSON.stringify(stateToSave));
}
/**
* Update a step's status
* @param {string} stepId - The step ID
* @param {string} status - 'pending', 'active', 'completed', 'error', 'skipped'
* @param {string} [message] - Optional message (for error states)
*/
updateStepStatus(stepId, status, message = '') {
const step = this._steps.find(s => s.id === stepId);
if (step) {
step.status = status;
step.message = message;
this._saveState();
this._render();
this.dispatchEvent(new CustomEvent('workflow-step-change', {
bubbles: true,
composed: true,
detail: { ...step }
}));
// Check if workflow is complete
const requiredSteps = this._steps.filter(s => !s.optional);
const completedRequired = requiredSteps.filter(s => s.status === 'completed').length;
if (completedRequired === requiredSteps.length && requiredSteps.length > 0) {
this.dispatchEvent(new CustomEvent('workflow-complete', {
bubbles: true,
composed: true
}));
}
}
}
/**
* Reset the workflow to initial state
*/
reset() {
this._steps.forEach(step => {
step.status = 'pending';
step.message = '';
});
this._saveState();
this._render();
}
/**
* Skip a step
* @param {string} stepId - The step ID to skip
*/
skipStep(stepId) {
const step = this._steps.find(s => s.id === stepId);
if (step && step.optional) {
this.updateStepStatus(stepId, 'skipped');
}
}
_determineActiveStep() {
const completedIds = new Set(
this._steps
.filter(s => s.status === 'completed' || s.status === 'skipped')
.map(s => s.id)
);
let foundActive = false;
this._steps.forEach(step => {
if (step.status === 'pending' && !foundActive) {
const depsMet = (step.dependsOn || []).every(depId => completedIds.has(depId));
if (depsMet) {
step.status = 'active';
foundActive = true;
}
}
});
}
_getProgress() {
const total = this._steps.filter(s => !s.optional).length;
const completed = this._steps.filter(s =>
!s.optional && (s.status === 'completed' || s.status === 'skipped')
).length;
return total > 0 ? (completed / total) * 100 : 0;
}
_renderBase() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
}
.workflow-container {
display: flex;
flex-direction: column;
}
.progress-bar {
height: 4px;
background: var(--muted);
border-radius: 2px;
margin-bottom: var(--space-4);
overflow: hidden;
}
.progress-bar__indicator {
height: 100%;
background: var(--success);
width: 0%;
transition: width 0.3s ease;
}
.steps-wrapper {
display: flex;
flex-direction: column;
}
.step {
display: flex;
gap: var(--space-3);
}
.step__indicator {
display: flex;
flex-direction: column;
align-items: center;
}
.step__icon {
width: 28px;
height: 28px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid var(--border);
background: var(--card);
transition: all 0.2s ease;
flex-shrink: 0;
}
.step__icon svg {
color: white;
}
.step--pending .step__icon {
border-color: var(--muted-foreground);
}
.step--active .step__icon {
border-color: var(--primary);
background: var(--primary);
}
.step--completed .step__icon {
border-color: var(--success);
background: var(--success);
}
.step--error .step__icon {
border-color: var(--destructive);
background: var(--destructive);
}
.step--skipped .step__icon {
border-color: var(--muted-foreground);
background: var(--muted-foreground);
}
.step__line {
width: 2px;
flex-grow: 1;
min-height: var(--space-4);
background: var(--border);
margin: var(--space-1) 0;
}
.step:last-child .step__line {
display: none;
}
.step--completed .step__line,
.step--skipped .step__line {
background: var(--success);
}
.step__content {
flex: 1;
padding-bottom: var(--space-4);
min-width: 0;
}
.step__header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--space-2);
}
.step__title {
font-weight: var(--font-medium);
color: var(--foreground);
font-size: var(--text-sm);
}
.step--pending .step__title,
.step--pending .step__description {
color: var(--muted-foreground);
}
.step__optional {
font-size: var(--text-xs);
color: var(--muted-foreground);
background: var(--muted);
padding: 0 var(--space-1);
border-radius: var(--radius-sm);
}
.step__description {
font-size: var(--text-xs);
color: var(--muted-foreground);
margin-top: var(--space-1);
line-height: 1.4;
}
.step__actions {
margin-top: var(--space-3);
display: flex;
gap: var(--space-2);
}
.error-message {
color: var(--destructive);
font-size: var(--text-xs);
margin-top: var(--space-2);
padding: var(--space-2);
background: oklch(from var(--destructive) l c h / 0.1);
border-radius: var(--radius);
}
</style>
<div class="workflow-container">
<div class="progress-bar">
<div class="progress-bar__indicator"></div>
</div>
<div class="steps-wrapper" id="steps-wrapper"></div>
</div>
`;
}
_render() {
const wrapper = this.shadowRoot.getElementById('steps-wrapper');
if (!wrapper || !this._steps || this._steps.length === 0) {
return;
}
// Determine which step should be active
this._determineActiveStep();
// Render steps
wrapper.innerHTML = this._steps.map(step => this._renderStep(step)).join('');
// Update progress bar
const progress = this._getProgress();
const indicator = this.shadowRoot.querySelector('.progress-bar__indicator');
if (indicator) {
indicator.style.width = `${progress}%`;
}
// Add event listeners for action buttons
wrapper.querySelectorAll('[data-action-event]').forEach(button => {
button.addEventListener('click', () => {
this.dispatchEvent(new CustomEvent(button.dataset.actionEvent, {
bubbles: true,
composed: true,
detail: { stepId: button.dataset.stepId }
}));
});
});
// Add skip button listeners
wrapper.querySelectorAll('[data-skip]').forEach(button => {
button.addEventListener('click', () => {
this.skipStep(button.dataset.skip);
});
});
}
_renderStep(step) {
const isActionable = step.status === 'active' && step.action;
const canSkip = step.status === 'active' && step.optional;
return `
<div class="step step--${step.status}" data-step-id="${step.id}">
<div class="step__indicator">
<div class="step__icon">${ICONS[step.status] || ''}</div>
<div class="step__line"></div>
</div>
<div class="step__content">
<div class="step__header">
<div class="step__title">${this._escapeHtml(step.title)}</div>
${step.optional ? '<span class="step__optional">Optional</span>' : ''}
</div>
${step.description ? `<div class="step__description">${this._escapeHtml(step.description)}</div>` : ''}
${step.status === 'error' && step.message ? `<div class="error-message">${this._escapeHtml(step.message)}</div>` : ''}
${isActionable || canSkip ? `
<div class="step__actions">
${isActionable ? `
<ds-button
variant="primary"
size="sm"
data-step-id="${step.id}"
data-action-event="${step.action.event}"
>${step.action.label}</ds-button>
` : ''}
${canSkip ? `
<ds-button
variant="ghost"
size="sm"
data-skip="${step.id}"
>Skip</ds-button>
` : ''}
</div>
` : ''}
</div>
</div>
`;
}
_escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
customElements.define('ds-workflow', DsWorkflow);