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,241 @@
/**
* ds-base-tool.js
* Base class for all DSS tool components
*
* Enforces DSS coding standards:
* - Shadow DOM encapsulation
* - Automatic event listener cleanup via AbortController
* - Constructable Stylesheets support
* - Standardized lifecycle methods
* - Logger utility integration
*
* Reference: .knowledge/dss-coding-standards.json
*/
import { logger } from '../../utils/logger.js';
/**
* Base class for DSS tool components
* All tool components should extend this class to ensure compliance with DSS standards
*/
export default class DSBaseTool extends HTMLElement {
constructor() {
super();
// WC-001: Shadow DOM Required
this.attachShadow({ mode: 'open' });
// EVENT-003: Use AbortController for cleanup
this._abortController = new AbortController();
// Track component state
this._isConnected = false;
logger.debug(`[${this.constructor.name}] Constructor initialized`);
}
/**
* Standard Web Component lifecycle: called when element is added to DOM
*/
connectedCallback() {
this._isConnected = true;
logger.debug(`[${this.constructor.name}] Connected to DOM`);
// Render the component
this.render();
// Setup event listeners after render
this.setupEventListeners();
}
/**
* Standard Web Component lifecycle: called when element is removed from DOM
* Automatically cleans up all event listeners via AbortController
*/
disconnectedCallback() {
this._isConnected = false;
// EVENT-003: Abort all event listeners
this._abortController.abort();
// Create new controller for potential re-connection
this._abortController = new AbortController();
logger.debug(`[${this.constructor.name}] Disconnected from DOM, listeners cleaned up`);
}
/**
* Centralized event binding with automatic cleanup
* @param {EventTarget} target - Element to attach listener to
* @param {string} type - Event type (e.g., 'click', 'mouseover')
* @param {Function} handler - Event handler function
* @param {Object} options - Additional addEventListener options
*/
bindEvent(target, type, handler, options = {}) {
if (!target || typeof handler !== 'function') {
logger.warn(`[${this.constructor.name}] Invalid event binding attempt`, { target, type, handler });
return;
}
// Add AbortController signal to options
const eventOptions = {
...options,
signal: this._abortController.signal
};
target.addEventListener(type, handler, eventOptions);
logger.debug(`[${this.constructor.name}] Event bound: ${type} on`, target);
}
/**
* Event delegation helper for handling multiple elements with data-action attributes
* @param {string} selector - CSS selector for the container element
* @param {string} eventType - Event type to listen for
* @param {Function} handler - Handler function that receives (action, event)
*/
delegateEvents(selector, eventType, handler) {
const container = this.shadowRoot.querySelector(selector);
if (!container) {
logger.warn(`[${this.constructor.name}] Event delegation container not found: ${selector}`);
return;
}
this.bindEvent(container, eventType, (e) => {
// Find element with data-action attribute
const target = e.target.closest('[data-action]');
if (target) {
const action = target.dataset.action;
handler(action, e, target);
}
});
logger.debug(`[${this.constructor.name}] Event delegation setup for ${eventType} on ${selector}`);
}
/**
* Inject CSS styles using Constructable Stylesheets
* STYLE-002: Use Constructable Stylesheets for shared styles
* @param {string} cssString - CSS string to inject
*/
adoptStyles(cssString) {
try {
const sheet = new CSSStyleSheet();
sheet.replaceSync(cssString);
// Append to existing stylesheets
this.shadowRoot.adoptedStyleSheets = [
...this.shadowRoot.adoptedStyleSheets,
sheet
];
logger.debug(`[${this.constructor.name}] Styles adopted (${cssString.length} bytes)`);
} catch (error) {
logger.error(`[${this.constructor.name}] Failed to adopt styles:`, error);
}
}
/**
* Set multiple attributes at once
* @param {Object} attrs - Object with attribute key-value pairs
*/
setAttributes(attrs) {
Object.entries(attrs).forEach(([key, value]) => {
if (value !== null && value !== undefined) {
this.setAttribute(key, value);
}
});
}
/**
* Get attribute with fallback value
* @param {string} name - Attribute name
* @param {*} defaultValue - Default value if attribute doesn't exist
* @returns {string|*} Attribute value or default
*/
getAttr(name, defaultValue = null) {
return this.hasAttribute(name) ? this.getAttribute(name) : defaultValue;
}
/**
* Render method - MUST be implemented by subclasses
* Should set shadowRoot.innerHTML with component template
*/
render() {
throw new Error(`${this.constructor.name} must implement render() method`);
}
/**
* Setup event listeners - should be implemented by subclasses
* Use this.bindEvent() or this.delegateEvents() for automatic cleanup
*/
setupEventListeners() {
// Override in subclass if needed
logger.debug(`[${this.constructor.name}] setupEventListeners() not implemented (optional)`);
}
/**
* Trigger re-render (useful for state changes)
*/
rerender() {
if (this._isConnected) {
// Abort existing listeners before re-render
this._abortController.abort();
this._abortController = new AbortController();
// Re-render and re-setup listeners
this.render();
this.setupEventListeners();
logger.debug(`[${this.constructor.name}] Component re-rendered`);
}
}
/**
* Helper: Query single element in shadow DOM
* @param {string} selector - CSS selector
* @returns {Element|null}
*/
$(selector) {
return this.shadowRoot.querySelector(selector);
}
/**
* Helper: Query multiple elements in shadow DOM
* @param {string} selector - CSS selector
* @returns {NodeList}
*/
$$(selector) {
return this.shadowRoot.querySelectorAll(selector);
}
/**
* Helper: Escape HTML to prevent XSS
* SECURITY-001: Sanitize user input
* @param {string} str - String to escape
* @returns {string} Escaped string
*/
escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
/**
* Helper: Dispatch custom event
* @param {string} eventName - Event name
* @param {*} detail - Event detail payload
* @param {Object} options - Event options
*/
emit(eventName, detail = null, options = {}) {
const event = new CustomEvent(eventName, {
detail,
bubbles: true,
composed: true, // Cross shadow DOM boundary
...options
});
this.dispatchEvent(event);
logger.debug(`[${this.constructor.name}] Event emitted: ${eventName}`, detail);
}
}