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:
249
admin-ui/js/components/tools/ds-accessibility-report.js
Normal file
249
admin-ui/js/components/tools/ds-accessibility-report.js
Normal file
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* ds-accessibility-report.js
|
||||
* Accessibility audit report using axe-core via MCP browser tools
|
||||
*/
|
||||
|
||||
import toolBridge from '../../services/tool-bridge.js';
|
||||
import { ComponentHelpers } from '../../utils/component-helpers.js';
|
||||
|
||||
class DSAccessibilityReport extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.auditResult = null;
|
||||
this.selector = null;
|
||||
this.isRunning = false;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
const runBtn = this.querySelector('#a11y-run-btn');
|
||||
if (runBtn) {
|
||||
runBtn.addEventListener('click', () => this.runAudit());
|
||||
}
|
||||
|
||||
const selectorInput = this.querySelector('#a11y-selector');
|
||||
if (selectorInput) {
|
||||
selectorInput.addEventListener('change', (e) => {
|
||||
this.selector = e.target.value.trim() || null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async runAudit() {
|
||||
if (this.isRunning) return;
|
||||
|
||||
this.isRunning = true;
|
||||
const content = this.querySelector('#a11y-content');
|
||||
const runBtn = this.querySelector('#a11y-run-btn');
|
||||
|
||||
if (!content) {
|
||||
this.isRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (runBtn) {
|
||||
runBtn.disabled = true;
|
||||
runBtn.textContent = 'Running Audit...';
|
||||
}
|
||||
|
||||
content.innerHTML = ComponentHelpers.renderLoading('Running accessibility audit with axe-core...');
|
||||
|
||||
try {
|
||||
const result = await toolBridge.runAccessibilityAudit(this.selector);
|
||||
|
||||
if (result) {
|
||||
this.auditResult = result;
|
||||
this.renderResults();
|
||||
} else {
|
||||
content.innerHTML = ComponentHelpers.renderEmpty('No audit results returned', '🔍');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to run accessibility audit:', error);
|
||||
content.innerHTML = ComponentHelpers.renderError('Failed to run accessibility audit', error);
|
||||
} finally {
|
||||
this.isRunning = false;
|
||||
if (runBtn) {
|
||||
runBtn.disabled = false;
|
||||
runBtn.textContent = '▶ Run Audit';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getSeverityIcon(impact) {
|
||||
const icons = {
|
||||
critical: '🔴',
|
||||
serious: '🟠',
|
||||
moderate: '🟡',
|
||||
minor: '🔵'
|
||||
};
|
||||
return icons[impact] || '⚪';
|
||||
}
|
||||
|
||||
getSeverityBadge(impact) {
|
||||
const types = {
|
||||
critical: 'error',
|
||||
serious: 'error',
|
||||
moderate: 'warning',
|
||||
minor: 'info'
|
||||
};
|
||||
return ComponentHelpers.createBadge(impact.toUpperCase(), types[impact] || 'info');
|
||||
}
|
||||
|
||||
renderResults() {
|
||||
const content = this.querySelector('#a11y-content');
|
||||
if (!content || !this.auditResult) return;
|
||||
|
||||
const violations = this.auditResult.violations || [];
|
||||
const passes = this.auditResult.passes || [];
|
||||
const incomplete = this.auditResult.incomplete || [];
|
||||
const inapplicable = this.auditResult.inapplicable || [];
|
||||
|
||||
const totalViolations = violations.length;
|
||||
const totalPasses = passes.length;
|
||||
const totalTests = totalViolations + totalPasses + incomplete.length + inapplicable.length;
|
||||
|
||||
if (totalViolations === 0) {
|
||||
content.innerHTML = `
|
||||
<div style="text-align: center; padding: 48px;">
|
||||
<div style="font-size: 64px; margin-bottom: 16px;">✅</div>
|
||||
<h3 style="font-size: 18px; margin-bottom: 8px; color: #89d185;">No Violations Found!</h3>
|
||||
<p style="font-size: 12px; color: var(--vscode-text-dim);">
|
||||
All ${totalPasses} accessibility tests passed.
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const violationCards = violations.map((violation, index) => {
|
||||
const impact = violation.impact || 'unknown';
|
||||
const nodes = violation.nodes || [];
|
||||
const nodeCount = nodes.length;
|
||||
|
||||
return `
|
||||
<div style="background-color: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-left: 3px solid ${this.getImpactColor(impact)}; border-radius: 4px; padding: 16px; margin-bottom: 12px;">
|
||||
<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;">
|
||||
<span style="font-size: 20px;">${this.getSeverityIcon(impact)}</span>
|
||||
<h4 style="font-size: 13px; font-weight: 600;">${ComponentHelpers.escapeHtml(violation.description || violation.id)}</h4>
|
||||
</div>
|
||||
<div style="font-size: 11px; color: var(--vscode-text-dim); margin-bottom: 8px;">
|
||||
Rule: <span style="font-family: 'Courier New', monospace;">${ComponentHelpers.escapeHtml(violation.id)}</span>
|
||||
</div>
|
||||
</div>
|
||||
${this.getSeverityBadge(impact)}
|
||||
</div>
|
||||
|
||||
<div style="font-size: 12px; margin-bottom: 12px; padding: 12px; background-color: var(--vscode-bg); border-radius: 2px;">
|
||||
${ComponentHelpers.escapeHtml(violation.help || 'No help text available')}
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 8px;">
|
||||
<div style="font-size: 11px; color: var(--vscode-text-dim); margin-bottom: 4px;">
|
||||
Affected elements: ${nodeCount}
|
||||
</div>
|
||||
${nodes.slice(0, 3).map(node => `
|
||||
<div style="margin-bottom: 6px; padding: 8px; background-color: var(--vscode-bg); border-radius: 2px; font-size: 11px;">
|
||||
<div style="font-family: 'Courier New', monospace; color: var(--vscode-accent); margin-bottom: 4px;">
|
||||
${ComponentHelpers.escapeHtml(ComponentHelpers.truncateText(node.target ? node.target.join(', ') : 'unknown', 80))}
|
||||
</div>
|
||||
${node.failureSummary ? `
|
||||
<div style="color: var(--vscode-text-dim); font-size: 10px;">
|
||||
${ComponentHelpers.escapeHtml(ComponentHelpers.truncateText(node.failureSummary, 150))}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
${nodeCount > 3 ? `<div style="font-size: 10px; color: var(--vscode-text-dim); margin-top: 4px;">... and ${nodeCount - 3} more</div>` : ''}
|
||||
</div>
|
||||
|
||||
${violation.helpUrl ? `
|
||||
<a href="${ComponentHelpers.escapeHtml(violation.helpUrl)}" target="_blank" style="font-size: 11px; color: var(--vscode-accent); text-decoration: none;">
|
||||
Learn more →
|
||||
</a>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
content.innerHTML = `
|
||||
<!-- Summary Card -->
|
||||
<div style="background-color: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px; padding: 16px; margin-bottom: 16px;">
|
||||
<h3 style="font-size: 14px; font-weight: 600; margin-bottom: 12px;">Audit Summary</h3>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 12px;">
|
||||
<div style="text-align: center; padding: 12px; background-color: var(--vscode-bg); border-radius: 4px;">
|
||||
<div style="font-size: 24px; font-weight: 600; color: #f48771;">${totalViolations}</div>
|
||||
<div style="font-size: 11px; color: var(--vscode-text-dim); margin-top: 4px;">Violations</div>
|
||||
</div>
|
||||
<div style="text-align: center; padding: 12px; background-color: var(--vscode-bg); border-radius: 4px;">
|
||||
<div style="font-size: 24px; font-weight: 600; color: #89d185;">${totalPasses}</div>
|
||||
<div style="font-size: 11px; color: var(--vscode-text-dim); margin-top: 4px;">Passes</div>
|
||||
</div>
|
||||
<div style="text-align: center; padding: 12px; background-color: var(--vscode-bg); border-radius: 4px;">
|
||||
<div style="font-size: 24px; font-weight: 600; color: var(--vscode-accent);">${totalTests}</div>
|
||||
<div style="font-size: 11px; color: var(--vscode-text-dim); margin-top: 4px;">Total Tests</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Violations List -->
|
||||
<div style="margin-bottom: 12px;">
|
||||
<h3 style="font-size: 14px; font-weight: 600; margin-bottom: 12px;">Violations (${totalViolations})</h3>
|
||||
${violationCards}
|
||||
</div>
|
||||
|
||||
<!-- Timestamp -->
|
||||
<div style="font-size: 11px; color: var(--vscode-text-dim); text-align: center; padding-top: 8px; border-top: 1px solid var(--vscode-border);">
|
||||
Audit completed: ${ComponentHelpers.formatTimestamp(new Date())}
|
||||
${this.selector ? ` • Scoped to: ${ComponentHelpers.escapeHtml(this.selector)}` : ' • Full page scan'}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
getImpactColor(impact) {
|
||||
const colors = {
|
||||
critical: '#f48771',
|
||||
serious: '#dbb765',
|
||||
moderate: '#dbb765',
|
||||
minor: '#75beff'
|
||||
};
|
||||
return colors[impact] || '#858585';
|
||||
}
|
||||
|
||||
render() {
|
||||
this.innerHTML = `
|
||||
<div style="padding: 16px; height: 100%; display: flex; flex-direction: column;">
|
||||
<div style="margin-bottom: 16px; display: flex; gap: 12px; align-items: center;">
|
||||
<input
|
||||
type="text"
|
||||
id="a11y-selector"
|
||||
placeholder="Optional: CSS selector to scope audit"
|
||||
class="input"
|
||||
style="flex: 1; min-width: 200px;"
|
||||
/>
|
||||
<button id="a11y-run-btn" class="button" style="padding: 4px 12px; font-size: 11px;">
|
||||
▶ Run Audit
|
||||
</button>
|
||||
</div>
|
||||
<div id="a11y-content" style="flex: 1; overflow-y: auto;">
|
||||
<div style="text-align: center; padding: 48px; color: var(--vscode-text-dim);">
|
||||
<div style="font-size: 48px; margin-bottom: 16px;">♿</div>
|
||||
<h3 style="font-size: 14px; margin-bottom: 8px;">Accessibility Audit</h3>
|
||||
<p style="font-size: 12px;">
|
||||
Click "Run Audit" to scan for WCAG violations using axe-core.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('ds-accessibility-report', DSAccessibilityReport);
|
||||
|
||||
export default DSAccessibilityReport;
|
||||
Reference in New Issue
Block a user