# DSS Coding Standards Migration Guide This guide shows how to migrate existing code to DSS coding standards defined in `.knowledge/dss-coding-standards.json`. ## Table of Contents - [Shadow DOM Migration](#shadow-dom-migration) - [Inline Event Handler Removal](#inline-event-handler-removal) - [Inline Style Extraction](#inline-style-extraction) - [Semantic HTML](#semantic-html) - [Accessibility Improvements](#accessibility-improvements) - [Logger Migration](#logger-migration) - [State Management](#state-management) --- ## Shadow DOM Migration ### ❌ Before (No Shadow DOM) ```javascript export default class MyComponent extends HTMLElement { connectedCallback() { this.innerHTML = `

Title

Content

`; } } ``` ### ✅ After (With Shadow DOM) ```javascript export default class MyComponent extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); // ✓ Enable Shadow DOM } connectedCallback() { this.render(); } render() { this.shadowRoot.innerHTML = `

Title

Content

`; } } ``` **Key Changes:** - ✓ Add `attachShadow()` in constructor - ✓ Change `this.innerHTML` to `this.shadowRoot.innerHTML` - ✓ Extract styles to `
`; } setupEventListeners() { // ✓ Event delegation with AbortController for cleanup this.abortController = new AbortController(); this.shadowRoot.addEventListener('click', (e) => { const action = e.target.closest('[data-action]')?.dataset.action; if (action === 'cardClick') { this.handleCardClick(e); } else if (action === 'buttonClick') { this.handleButtonClick(e); } }, { signal: this.abortController.signal }); } handleCardClick(e) { console.log('Card clicked'); } handleButtonClick(e) { e.stopPropagation(); this.dispatchEvent(new CustomEvent('button-clicked', { bubbles: true, composed: true })); } ``` **Key Changes:** - ✓ Remove ALL `onclick`, `onmouseover`, `onmouseout` attributes - ✓ Use CSS `:hover` for hover effects - ✓ Event delegation with `data-action` attributes - ✓ Single event listener using `closest('[data-action]')` - ✓ AbortController for automatic cleanup - ✓ Custom events for component communication --- ## Inline Style Extraction ### ❌ Before (Inline Styles Everywhere) ```javascript render() { this.innerHTML = `

${this.title}

`; } ``` ### ✅ After (Styles in Shadow DOM) ```javascript render() { this.shadowRoot.innerHTML = `

${this.title}

`; } ``` **Key Changes:** - ✓ ALL styles moved to ` `; } ``` **Key Changes:** - ✓ Use `
${this.content}
`; } connectedCallback() { this.render(); this.setupEventListeners(); this.trapFocus(); // ✓ Keep focus inside modal this.previousFocus = document.activeElement; // ✓ Store for restoration } disconnectedCallback() { if (this.previousFocus) { this.previousFocus.focus(); // ✓ Restore focus on close } if (this.abortController) { this.abortController.abort(); } } trapFocus() { const focusable = this.shadowRoot.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ); const firstFocusable = focusable[0]; const lastFocusable = focusable[focusable.length - 1]; this.shadowRoot.addEventListener('keydown', (e) => { if (e.key === 'Tab') { if (e.shiftKey && document.activeElement === firstFocusable) { e.preventDefault(); lastFocusable.focus(); } else if (!e.shiftKey && document.activeElement === lastFocusable) { e.preventDefault(); firstFocusable.focus(); } } else if (e.key === 'Escape') { this.close(); } }); firstFocusable.focus(); // ✓ Focus first element } ``` **Key Changes:** - ✓ Add ARIA attributes (`role`, `aria-modal`, `aria-labelledby`) - ✓ Semantic ` `; } increment() { this.count++; document.getElementById('count').textContent = this.count; // ✗ Direct DOM } } ``` ### ✅ After (Reactive State Updates) ```javascript import contextStore from '../stores/context-store.js'; export default class Counter extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.state = { count: 0 }; } connectedCallback() { this.render(); this.setupEventListeners(); // ✓ Subscribe to global state changes this.unsubscribe = contextStore.subscribeToKey('someValue', (newValue) => { this.setState({ externalValue: newValue }); }); } disconnectedCallback() { if (this.unsubscribe) { this.unsubscribe(); // ✓ Cleanup subscription } if (this.abortController) { this.abortController.abort(); } } setState(updates) { // ✓ Immutable state update this.state = { ...this.state, ...updates }; this.render(); // ✓ Re-render on state change } render() { this.shadowRoot.innerHTML = `
Count: ${this.state.count}
`; } setupEventListeners() { this.abortController = new AbortController(); this.shadowRoot.addEventListener('click', (e) => { const action = e.target.closest('[data-action]')?.dataset.action; if (action === 'increment') { this.setState({ count: this.state.count + 1 }); // ✓ State update triggers render } else if (action === 'decrement') { this.setState({ count: this.state.count - 1 }); } }, { signal: this.abortController.signal }); } } customElements.define('ds-counter', Counter); ``` **Key Changes:** - ✓ State in `this.state` object - ✓ `setState()` method for immutable updates - ✓ State changes trigger `render()` - ✓ No direct DOM manipulation - ✓ Subscribe to global state via contextStore - ✓ Cleanup subscriptions in `disconnectedCallback` --- ## Complete Example: Full Migration ### ❌ Before (All Anti-Patterns) ```javascript export default class OldComponent extends HTMLElement { connectedCallback() { this.data = []; this.render(); } async loadData() { console.log('Loading...'); const response = await fetch('/api/data'); this.data = await response.json(); this.render(); } render() { this.innerHTML = `

Title

Click me
Load Data
`; } } customElements.define('old-component', OldComponent); ``` ### ✅ After (DSS Standards Compliant) ```javascript import { logger } from '../utils/logger.js'; import contextStore from '../stores/context-store.js'; export default class NewComponent extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); // ✓ Shadow DOM this.state = { data: [], isLoading: false, error: null }; } connectedCallback() { this.render(); this.setupEventListeners(); this.loadData(); // Initial load // ✓ Global state subscription this.unsubscribe = contextStore.subscribeToKey('theme', (newTheme) => { logger.debug('[NewComponent] Theme changed', { theme: newTheme }); }); } disconnectedCallback() { // ✓ Cleanup if (this.unsubscribe) this.unsubscribe(); if (this.abortController) this.abortController.abort(); } setState(updates) { this.state = { ...this.state, ...updates }; this.render(); } async loadData() { const endTimer = logger.time('[NewComponent] Data load'); this.setState({ isLoading: true, error: null }); try { const response = await fetch('/api/data'); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); logger.info('[NewComponent] Data loaded', { count: data.length }); this.setState({ data, isLoading: false }); endTimer(); } catch (error) { logger.error('[NewComponent] Failed to load data', error); this.setState({ error: error.message, isLoading: false }); } } render() { this.shadowRoot.innerHTML = `

Title

${this.state.data.map((item) => `
${item.name}
`).join('')} ${this.state.error ? `
Error: ${this.state.error}
` : ''}
`; } setupEventListeners() { this.abortController = new AbortController(); // ✓ Event delegation this.shadowRoot.addEventListener('click', (e) => { const action = e.target.closest('[data-action]')?.dataset.action; if (action === 'itemClick') { const itemId = e.target.dataset.itemId; this.handleItemClick(itemId); } else if (action === 'loadData') { this.loadData(); } }, { signal: this.abortController.signal }); // ✓ Keyboard support this.shadowRoot.addEventListener('keydown', (e) => { if (e.key === 'Enter' && e.target.hasAttribute('data-action')) { e.target.click(); } }, { signal: this.abortController.signal }); } handleItemClick(itemId) { logger.debug('[NewComponent] Item clicked', { itemId }); this.dispatchEvent(new CustomEvent('item-selected', { detail: { itemId }, bubbles: true, composed: true // ✓ Bubble out of Shadow DOM })); } } customElements.define('ds-new-component', NewComponent); ``` **All Improvements Applied:** - ✓ Shadow DOM with encapsulated styles - ✓ No inline event handlers - ✓ No inline styles (all in `