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:
305
admin-ui/js/components/tools/ds-quick-wins.js
Normal file
305
admin-ui/js/components/tools/ds-quick-wins.js
Normal file
@@ -0,0 +1,305 @@
|
||||
/**
|
||||
* ds-quick-wins.js
|
||||
* Identifies low-effort, high-impact opportunities for design system adoption
|
||||
* UI Team Tool #5
|
||||
*/
|
||||
|
||||
import { ComponentHelpers } from '../../utils/component-helpers.js';
|
||||
import contextStore from '../../stores/context-store.js';
|
||||
import toolBridge from '../../services/tool-bridge.js';
|
||||
|
||||
class DSQuickWins extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.quickWins = null;
|
||||
this.isAnalyzing = false;
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
await this.loadCachedResults();
|
||||
}
|
||||
|
||||
async loadCachedResults() {
|
||||
try {
|
||||
const context = contextStore.getMCPContext();
|
||||
if (!context.project_id) return;
|
||||
|
||||
const cached = localStorage.getItem(`quickwins_${context.project_id}`);
|
||||
if (cached) {
|
||||
this.quickWins = JSON.parse(cached);
|
||||
this.renderResults();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[DSQuickWins] Failed to load cached results:', error);
|
||||
}
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
const analyzeBtn = this.querySelector('#analyze-quick-wins-btn');
|
||||
const pathInput = this.querySelector('#project-path-input');
|
||||
|
||||
if (analyzeBtn) {
|
||||
analyzeBtn.addEventListener('click', () => this.analyzeQuickWins());
|
||||
}
|
||||
|
||||
if (pathInput) {
|
||||
pathInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.analyzeQuickWins();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async analyzeQuickWins() {
|
||||
const pathInput = this.querySelector('#project-path-input');
|
||||
const projectPath = pathInput?.value.trim() || '';
|
||||
|
||||
if (!projectPath) {
|
||||
ComponentHelpers.showToast?.('Please enter a project path', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
this.isAnalyzing = true;
|
||||
this.updateLoadingState();
|
||||
|
||||
try {
|
||||
// Call dss_find_quick_wins MCP tool
|
||||
const result = await toolBridge.executeTool('dss_find_quick_wins', {
|
||||
path: projectPath
|
||||
});
|
||||
|
||||
this.quickWins = result;
|
||||
|
||||
// Cache results
|
||||
const context = contextStore.getMCPContext();
|
||||
if (context.project_id) {
|
||||
localStorage.setItem(`quickwins_${context.project_id}`, JSON.stringify(result));
|
||||
}
|
||||
|
||||
this.renderResults();
|
||||
ComponentHelpers.showToast?.('Quick wins analysis complete', 'success');
|
||||
} catch (error) {
|
||||
console.error('[DSQuickWins] Analysis failed:', error);
|
||||
ComponentHelpers.showToast?.(`Analysis failed: ${error.message}`, 'error');
|
||||
|
||||
const resultsContainer = this.querySelector('#results-container');
|
||||
if (resultsContainer) {
|
||||
resultsContainer.innerHTML = ComponentHelpers.renderError('Quick wins analysis failed', error);
|
||||
}
|
||||
} finally {
|
||||
this.isAnalyzing = false;
|
||||
this.updateLoadingState();
|
||||
}
|
||||
}
|
||||
|
||||
updateLoadingState() {
|
||||
const analyzeBtn = this.querySelector('#analyze-quick-wins-btn');
|
||||
const resultsContainer = this.querySelector('#results-container');
|
||||
|
||||
if (!analyzeBtn || !resultsContainer) return;
|
||||
|
||||
if (this.isAnalyzing) {
|
||||
analyzeBtn.disabled = true;
|
||||
analyzeBtn.textContent = '⏳ Analyzing...';
|
||||
resultsContainer.innerHTML = ComponentHelpers.renderLoading('Identifying quick win opportunities...');
|
||||
} else {
|
||||
analyzeBtn.disabled = false;
|
||||
analyzeBtn.textContent = '⚡ Find Quick Wins';
|
||||
}
|
||||
}
|
||||
|
||||
renderResults() {
|
||||
const resultsContainer = this.querySelector('#results-container');
|
||||
if (!resultsContainer || !this.quickWins) return;
|
||||
|
||||
const opportunities = this.quickWins.opportunities || [];
|
||||
const totalImpact = opportunities.reduce((sum, opp) => sum + (opp.impact || 0), 0);
|
||||
const avgEffort = opportunities.length > 0
|
||||
? (opportunities.reduce((sum, opp) => sum + (opp.effort || 0), 0) / opportunities.length).toFixed(1)
|
||||
: 0;
|
||||
|
||||
resultsContainer.innerHTML = `
|
||||
<div style="padding: 16px; overflow: auto; height: 100%;">
|
||||
<!-- Summary -->
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 16px; margin-bottom: 24px;">
|
||||
${this.createStatCard('Opportunities', opportunities.length, '⚡')}
|
||||
${this.createStatCard('Total Impact', `${totalImpact}%`, '📈')}
|
||||
${this.createStatCard('Avg Effort', `${avgEffort}h`, '⏱️')}
|
||||
</div>
|
||||
|
||||
<!-- Opportunities List -->
|
||||
${opportunities.length === 0 ? ComponentHelpers.renderEmpty('No quick wins found', '✨') : `
|
||||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||||
${opportunities.sort((a, b) => (b.impact || 0) - (a.impact || 0)).map((opp, idx) => `
|
||||
<div style="background: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px; padding: 16px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 12px;">
|
||||
<div style="flex: 1;">
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
|
||||
<h4 style="font-size: 12px; font-weight: 600;">${ComponentHelpers.escapeHtml(opp.title)}</h4>
|
||||
${this.renderPriorityBadge(opp.priority || 'medium')}
|
||||
</div>
|
||||
<p style="font-size: 11px; color: var(--vscode-text-dim); margin-bottom: 8px;">
|
||||
${ComponentHelpers.escapeHtml(opp.description)}
|
||||
</p>
|
||||
</div>
|
||||
<div style="text-align: right; margin-left: 16px;">
|
||||
<div style="font-size: 10px; color: var(--vscode-text-dim); margin-bottom: 4px;">Impact</div>
|
||||
<div style="font-size: 20px; font-weight: 600; color: #89d185;">${opp.impact || 0}%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Metrics -->
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 12px; font-size: 11px; margin-bottom: 12px;">
|
||||
<div>
|
||||
<div style="color: var(--vscode-text-dim); margin-bottom: 2px;">Effort</div>
|
||||
<div style="font-weight: 600;">${opp.effort || 0} hours</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="color: var(--vscode-text-dim); margin-bottom: 2px;">Files Affected</div>
|
||||
<div style="font-weight: 600;">${opp.filesAffected || 0}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="color: var(--vscode-text-dim); margin-bottom: 2px;">Type</div>
|
||||
<div style="font-weight: 600;">${ComponentHelpers.escapeHtml(opp.type || 'refactor')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<button class="button apply-quick-win-btn" data-idx="${idx}" style="font-size: 10px; padding: 4px 12px;">
|
||||
✨ Apply Fix
|
||||
</button>
|
||||
<button class="button view-files-btn" data-idx="${idx}" style="font-size: 10px; padding: 4px 12px;">
|
||||
📁 View Files
|
||||
</button>
|
||||
</div>
|
||||
|
||||
${opp.files && opp.files.length > 0 ? `
|
||||
<details style="margin-top: 12px;">
|
||||
<summary style="font-size: 10px; color: var(--vscode-text-dim); cursor: pointer;">
|
||||
Affected Files (${opp.files.length})
|
||||
</summary>
|
||||
<div style="margin-top: 8px; padding: 8px; background: var(--vscode-bg); border-radius: 2px; font-family: monospace; font-size: 10px;">
|
||||
${opp.files.slice(0, 10).map(file => `<div style="padding: 2px 0;">${ComponentHelpers.escapeHtml(file)}</div>`).join('')}
|
||||
${opp.files.length > 10 ? `<div style="padding: 2px 0; color: var(--vscode-text-dim);">...and ${opp.files.length - 10} more</div>` : ''}
|
||||
</div>
|
||||
</details>
|
||||
` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Setup button handlers
|
||||
const applyBtns = resultsContainer.querySelectorAll('.apply-quick-win-btn');
|
||||
const viewBtns = resultsContainer.querySelectorAll('.view-files-btn');
|
||||
|
||||
applyBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const idx = parseInt(btn.dataset.idx);
|
||||
this.applyQuickWin(opportunities[idx]);
|
||||
});
|
||||
});
|
||||
|
||||
viewBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const idx = parseInt(btn.dataset.idx);
|
||||
this.viewFiles(opportunities[idx]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
createStatCard(label, value, icon) {
|
||||
return `
|
||||
<div style="background: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px; padding: 16px; text-align: center;">
|
||||
<div style="font-size: 32px; margin-bottom: 8px;">${icon}</div>
|
||||
<div style="font-size: 20px; font-weight: 600; margin-bottom: 4px;">${value}</div>
|
||||
<div style="font-size: 11px; color: var(--vscode-text-dim);">${label}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderPriorityBadge(priority) {
|
||||
const config = {
|
||||
high: { color: '#f48771', label: 'High Priority' },
|
||||
medium: { color: '#ffbf00', label: 'Medium Priority' },
|
||||
low: { color: '#89d185', label: 'Low Priority' }
|
||||
};
|
||||
|
||||
const { color, label } = config[priority] || config.medium;
|
||||
|
||||
return `<span style="padding: 2px 8px; background: ${color}; border-radius: 2px; font-size: 10px; font-weight: 600;">${label}</span>`;
|
||||
}
|
||||
|
||||
applyQuickWin(opportunity) {
|
||||
ComponentHelpers.showToast?.(`Applying: ${opportunity.title}`, 'info');
|
||||
// In real implementation, this would trigger automated refactoring
|
||||
console.log('Apply quick win:', opportunity);
|
||||
}
|
||||
|
||||
viewFiles(opportunity) {
|
||||
if (!opportunity.files || opportunity.files.length === 0) {
|
||||
ComponentHelpers.showToast?.('No files associated with this opportunity', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('View files:', opportunity.files);
|
||||
ComponentHelpers.showToast?.(`${opportunity.files.length} files affected`, 'info');
|
||||
}
|
||||
|
||||
render() {
|
||||
this.innerHTML = `
|
||||
<div style="display: flex; flex-direction: column; height: 100%;">
|
||||
<!-- Header -->
|
||||
<div style="padding: 16px; border-bottom: 1px solid var(--vscode-border); background: var(--vscode-sidebar);">
|
||||
<h3 style="font-size: 12px; font-weight: 600; margin-bottom: 12px;">Quick Wins Identification</h3>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr auto; gap: 12px; align-items: end;">
|
||||
<div>
|
||||
<label style="display: block; font-size: 11px; font-weight: 600; margin-bottom: 4px; color: var(--vscode-text-dim);">
|
||||
Project Path
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="project-path-input"
|
||||
placeholder="/path/to/your/project"
|
||||
class="input"
|
||||
style="width: 100%; font-size: 11px; font-family: monospace;"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button id="analyze-quick-wins-btn" class="button" style="font-size: 11px; padding: 6px 16px;">
|
||||
⚡ Find Quick Wins
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 8px; font-size: 10px; color: var(--vscode-text-dim);">
|
||||
💡 Identifies low-effort, high-impact opportunities for design system adoption
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results Container -->
|
||||
<div id="results-container" style="flex: 1; overflow: hidden;">
|
||||
<div style="display: flex; align-items: center; justify-content: center; height: 100%; text-align: center; padding: 48px;">
|
||||
<div>
|
||||
<div style="font-size: 48px; margin-bottom: 16px;">⚡</div>
|
||||
<h3 style="font-size: 14px; font-weight: 600; margin-bottom: 8px;">Ready to Find Quick Wins</h3>
|
||||
<p style="font-size: 12px; color: var(--vscode-text-dim);">
|
||||
Enter your project path above to identify low-effort, high-impact improvements
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('ds-quick-wins', DSQuickWins);
|
||||
|
||||
export default DSQuickWins;
|
||||
Reference in New Issue
Block a user