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,380 @@
/**
* ds-project-selector.js
* Project selector component for workdesk header
* MVP1: Enforces project selection before tools can be used
* FIXED: Now uses authenticated apiClient instead of direct fetch()
*/
import contextStore from '../../stores/context-store.js';
import apiClient from '../../services/api-client.js';
import { ComponentHelpers } from '../../utils/component-helpers.js';
class DSProjectSelector extends HTMLElement {
constructor() {
super();
this.projects = [];
this.isLoading = false;
this.selectedProject = contextStore.get('projectId');
}
async connectedCallback() {
this.render();
await this.loadProjects();
this.setupEventListeners();
// Subscribe to context changes
this.unsubscribe = contextStore.subscribeToKey('projectId', (newValue) => {
this.selectedProject = newValue;
this.updateSelectedDisplay();
});
// Bind auth change handler to this component
this.handleAuthChange = async (event) => {
console.log('[DSProjectSelector] Auth state changed, reloading projects');
await this.reloadProjects();
};
// Listen for custom auth-change events (fires when tokens are refreshed)
document.addEventListener('auth-change', this.handleAuthChange);
}
disconnectedCallback() {
if (this.unsubscribe) {
this.unsubscribe();
}
// Clean up auth change listener
if (this.handleAuthChange) {
document.removeEventListener('auth-change', this.handleAuthChange);
}
// Clean up document click listener for closing dropdown
if (this.closeDropdownHandler) {
document.removeEventListener('click', this.closeDropdownHandler);
}
}
async loadProjects() {
this.isLoading = true;
this.updateLoadingState();
try {
// Fetch projects from authenticated API client
// This ensures Authorization header is sent with the request
this.projects = await apiClient.getProjects();
console.log(`[DSProjectSelector] Loaded ${this.projects.length} projects`);
// If no project selected but we have projects, show prompt
if (!this.selectedProject && this.projects.length > 0) {
this.showProjectModal();
}
this.renderDropdown();
} catch (error) {
console.error('[DSProjectSelector] Failed to load projects:', error);
// Fallback: Create mock admin-ui project for development
this.projects = [{
id: 'admin-ui',
name: 'Admin UI (Default)',
description: 'Design System Server Admin UI'
}];
// Auto-select if no project selected
if (!this.selectedProject) {
try {
contextStore.setProject('admin-ui');
this.selectedProject = 'admin-ui';
} catch (storeError) {
console.error('[DSProjectSelector] Error setting project:', storeError);
this.selectedProject = 'admin-ui';
}
}
this.renderDropdown();
} finally {
this.isLoading = false;
this.updateLoadingState();
}
}
/**
* Public method to reload projects - called when auth state changes
*/
async reloadProjects() {
console.log('[DSProjectSelector] Reloading projects due to auth state change');
await this.loadProjects();
}
setupEventListeners() {
const button = this.querySelector('#project-selector-button');
const dropdown = this.querySelector('#project-dropdown');
if (button && dropdown) {
// Add click listener to button (delegation handles via event target check)
button.addEventListener('click', (e) => {
e.stopPropagation();
dropdown.style.display = dropdown.style.display === 'block' ? 'none' : 'block';
});
}
// Add click listeners to dropdown items
const projectOptions = this.querySelectorAll('.project-option');
projectOptions.forEach(option => {
option.addEventListener('click', (e) => {
e.stopPropagation();
const projectId = option.dataset.projectId;
this.selectProject(projectId);
});
});
// Close dropdown when clicking outside - stored for cleanup
if (!this.closeDropdownHandler) {
this.closeDropdownHandler = (e) => {
if (!this.contains(e.target) && dropdown) {
dropdown.style.display = 'none';
}
};
document.addEventListener('click', this.closeDropdownHandler);
}
}
selectProject(projectId) {
const project = this.projects.find(p => p.id === projectId);
if (!project) {
console.error('[DSProjectSelector] Project not found:', projectId);
return;
}
try {
contextStore.setProject(projectId);
this.selectedProject = projectId;
// Close dropdown
const dropdown = this.querySelector('#project-dropdown');
if (dropdown) {
dropdown.style.display = 'none';
}
this.updateSelectedDisplay();
ComponentHelpers.showToast?.(`Switched to project: ${project.name}`, 'success');
// Notify other components of project change
this.dispatchEvent(new CustomEvent('project-changed', {
detail: { projectId },
bubbles: true,
composed: true
}));
} catch (error) {
console.error('[DSProjectSelector] Error selecting project:', error);
ComponentHelpers.showToast?.(`Failed to select project: ${error.message}`, 'error');
}
}
showProjectModal() {
const modal = document.createElement('div');
modal.id = 'project-selection-modal';
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
`;
const content = document.createElement('div');
content.style.cssText = `
background: var(--vscode-sidebar);
border: 1px solid var(--vscode-border);
border-radius: 4px;
padding: 24px;
max-width: 500px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
`;
// Use event delegation instead of attaching listeners to individual buttons
content.innerHTML = `
<h2 style="font-size: 16px; margin-bottom: 12px;">Select a Project</h2>
<p style="font-size: 12px; color: var(--vscode-text-dim); margin-bottom: 16px;">
Please select a project to start working. All tools require an active project.
</p>
<div style="display: flex; flex-direction: column; gap: 8px;" id="project-buttons-container">
${this.projects.map(project => `
<button
class="project-modal-button"
data-project-id="${project.id}"
type="button"
style="padding: 12px; background: var(--vscode-bg); border: 1px solid var(--vscode-border); border-radius: 4px; cursor: pointer; text-align: left; font-family: inherit; font-size: inherit;"
>
<div style="font-size: 12px; font-weight: 600;">${ComponentHelpers.escapeHtml(project.name)}</div>
${project.description ? `<div style="font-size: 11px; color: var(--vscode-text-dim); margin-top: 4px;">${ComponentHelpers.escapeHtml(project.description)}</div>` : ''}
</button>
`).join('')}
</div>
`;
modal.appendChild(content);
// Store reference to component for event handlers
const component = this;
// Use event delegation on content container
const buttonContainer = content.querySelector('#project-buttons-container');
if (buttonContainer) {
buttonContainer.addEventListener('click', (e) => {
const btn = e.target.closest('.project-modal-button');
if (btn) {
e.preventDefault();
e.stopPropagation();
const projectId = btn.dataset.projectId;
console.log('[DSProjectSelector] Modal button clicked:', projectId);
try {
component.selectProject(projectId);
console.log('[DSProjectSelector] Project selected successfully');
} catch (err) {
console.error('[DSProjectSelector] Error selecting project:', err);
} finally {
// Ensure modal is always removed
if (modal && modal.parentNode) {
modal.remove();
}
}
}
});
}
// Close modal when clicking outside the content area
modal.addEventListener('click', (e) => {
if (e.target === modal) {
console.log('[DSProjectSelector] Closing modal (clicked outside)');
modal.remove();
}
});
document.body.appendChild(modal);
console.log('[DSProjectSelector] Project selection modal shown');
}
updateSelectedDisplay() {
const button = this.querySelector('#project-selector-button');
if (!button) return;
const selectedProject = this.projects.find(p => p.id === this.selectedProject);
if (selectedProject) {
button.innerHTML = `
<span style="font-size: 11px; color: var(--vscode-text-dim);">Project:</span>
<span style="font-size: 12px; font-weight: 600; margin-left: 4px;">${ComponentHelpers.escapeHtml(selectedProject.name)}</span>
<span style="margin-left: 6px;">▼</span>
`;
} else {
button.innerHTML = `
<span style="font-size: 12px; color: var(--vscode-text-dim);">Select Project</span>
<span style="margin-left: 6px;">▼</span>
`;
}
}
updateLoadingState() {
const button = this.querySelector('#project-selector-button');
if (!button) return;
if (this.isLoading) {
button.disabled = true;
button.innerHTML = '<span style="font-size: 11px;">Loading projects...</span>';
} else {
button.disabled = false;
this.updateSelectedDisplay();
}
}
renderDropdown() {
const dropdown = this.querySelector('#project-dropdown');
if (!dropdown) return;
if (this.projects.length === 0) {
dropdown.innerHTML = `
<div style="padding: 12px; font-size: 11px; color: var(--vscode-text-dim);">
No projects available
</div>
`;
return;
}
dropdown.innerHTML = `
${this.projects.map(project => `
<div
class="project-option"
data-project-id="${project.id}"
style="
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid var(--vscode-border);
${this.selectedProject === project.id ? 'background: var(--vscode-list-activeSelectionBackground);' : ''}
"
>
<div style="font-size: 12px; font-weight: 600;">
${this.selectedProject === project.id ? '✓ ' : ''}${ComponentHelpers.escapeHtml(project.name)}
</div>
${project.description ? `<div style="font-size: 10px; color: var(--vscode-text-dim); margin-top: 2px;">${ComponentHelpers.escapeHtml(project.description)}</div>` : ''}
</div>
`).join('')}
`;
// Re-attach event listeners to dropdown items
this.setupEventListeners();
}
render() {
this.innerHTML = `
<div style="position: relative; display: inline-block;">
<button
id="project-selector-button"
style="
display: flex;
align-items: center;
padding: 6px 12px;
background: var(--vscode-sidebar);
border: 1px solid var(--vscode-border);
border-radius: 4px;
cursor: pointer;
color: var(--vscode-text);
"
>
<span style="font-size: 12px;">Loading...</span>
</button>
<div
id="project-dropdown"
style="
display: none;
position: absolute;
top: 100%;
left: 0;
margin-top: 4px;
min-width: 250px;
background: var(--vscode-sidebar);
border: 1px solid var(--vscode-border);
border-radius: 4px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
z-index: 1000;
max-height: 400px;
overflow-y: auto;
"
>
<!-- Projects will be populated here -->
</div>
</div>
`;
}
}
customElements.define('ds-project-selector', DSProjectSelector);
export default DSProjectSelector;