# 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 = `
      <div class="container">
        <h2>Title</h2>
        <p>Content</p>
      </div>
    `;
  }
}
```

### ✅ 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 = `
      <style>
        :host {
          display: block;
        }
        .container {
          padding: 16px;
          background: var(--vscode-sidebar-background);
        }
        h2 {
          color: var(--vscode-foreground);
          font-size: 16px;
        }
      </style>
      <div class="container">
        <h2>Title</h2>
        <p>Content</p>
      </div>
    `;
  }
}
```

**Key Changes:**
- ✓ Add `attachShadow()` in constructor
- ✓ Change `this.innerHTML` to `this.shadowRoot.innerHTML`
- ✓ Extract styles to `<style>` block in Shadow DOM
- ✓ Use VSCode theme CSS variables

---

## Inline Event Handler Removal

### ❌ Before (Inline Events - FORBIDDEN)
```javascript
render() {
  this.innerHTML = `
    <div
      class="card"
      onclick="this.getRootNode().host.handleClick()"
      onmouseover="this.style.transform='scale(1.02)'"
      onmouseout="this.style.transform='scale(1)'">
      <button onclick="alert('clicked')">Click me</button>
    </div>
  `;
}
```

### ✅ After (Event Delegation + CSS)
```javascript
constructor() {
  super();
  this.attachShadow({ mode: 'open' });
}

connectedCallback() {
  this.render();
  this.setupEventListeners(); // ✓ Setup after render
}

disconnectedCallback() {
  // ✓ Cleanup happens automatically with AbortController
  if (this.abortController) {
    this.abortController.abort();
  }
}

render() {
  this.shadowRoot.innerHTML = `
    <style>
      .card {
        transition: transform 0.2s;
      }
      .card:hover {
        transform: scale(1.02); /* ✓ CSS hover instead of JS */
      }
      button {
        padding: 8px 16px;
      }
    </style>
    <div class="card" data-action="cardClick">
      <button data-action="buttonClick" type="button">Click me</button>
    </div>
  `;
}

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 = `
    <div style="background: #1e1e1e; padding: 24px; border-radius: 4px;">
      <h2 style="color: #ffffff; font-size: 18px; margin-bottom: 12px;">
        ${this.title}
      </h2>
      <button style="padding: 8px 16px; background: #0e639c; color: white; border: none; border-radius: 2px; cursor: pointer;">
        Action
      </button>
    </div>
  `;
}
```

### ✅ After (Styles in Shadow DOM)
```javascript
render() {
  this.shadowRoot.innerHTML = `
    <style>
      :host {
        display: block;
      }
      .container {
        background: var(--vscode-sidebar-background);
        padding: 24px;
        border-radius: 4px;
      }
      h2 {
        color: var(--vscode-foreground);
        font-size: 18px;
        margin: 0 0 12px 0;
      }
      button {
        padding: 8px 16px;
        background: var(--vscode-button-background);
        color: var(--vscode-button-foreground);
        border: none;
        border-radius: 2px;
        cursor: pointer;
        transition: background-color 0.1s;
      }
      button:hover {
        background: var(--vscode-button-hoverBackground);
      }
      button:focus-visible {
        outline: 2px solid var(--vscode-focusBorder);
        outline-offset: 2px;
      }
    </style>
    <div class="container">
      <h2>${this.title}</h2>
      <button type="button">Action</button>
    </div>
  `;
}
```

**Key Changes:**
- ✓ ALL styles moved to `<style>` block
- ✓ Use VSCode theme CSS variables
- ✓ Add hover and focus states in CSS
- ✓ Exception: Dynamic values like `transform: translateX(${x}px)` allowed

---

## Semantic HTML

### ❌ Before (Divs as Buttons)
```javascript
render() {
  this.innerHTML = `
    <div class="tool-item" onclick="this.selectTool()">
      <div class="tool-name">Settings</div>
      <div class="tool-desc">Configure options</div>
    </div>

    <div class="close" onclick="this.close()">×</div>
  `;
}
```

### ✅ After (Semantic Elements)
```javascript
render() {
  this.shadowRoot.innerHTML = `
    <style>
      button {
        appearance: none;
        background: transparent;
        border: 1px solid transparent;
        padding: 8px;
        width: 100%;
        text-align: left;
        cursor: pointer;
        border-radius: 4px;
      }
      button:hover {
        background: var(--vscode-list-hoverBackground);
      }
      button:focus-visible {
        outline: 2px solid var(--vscode-focusBorder);
      }
      .tool-name {
        font-size: 13px;
        font-weight: 500;
      }
      .tool-desc {
        font-size: 11px;
        color: var(--vscode-descriptionForeground);
      }
      .close-btn {
        padding: 4px 8px;
        font-size: 20px;
      }
    </style>

    <button type="button" data-action="selectTool">
      <div class="tool-name">Settings</div>
      <div class="tool-desc">Configure options</div>
    </button>

    <button
      type="button"
      class="close-btn"
      data-action="close"
      aria-label="Close dialog">
      ×
    </button>
  `;
}
```

**Key Changes:**
- ✓ Use `<button type="button">` for interactive elements
- ✓ Add `aria-label` for icon-only buttons
- ✓ Keyboard accessible by default
- ✓ Proper focus management

---

## Accessibility Improvements

### ❌ Before (Poor A11y)
```javascript
render() {
  this.innerHTML = `
    <div class="modal">
      <div class="close" onclick="this.close()">×</div>
      <div class="content">${this.content}</div>
    </div>
  `;
}
```

### ✅ After (WCAG 2.1 AA Compliant)
```javascript
render() {
  this.shadowRoot.innerHTML = `
    <style>
      .modal {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background: var(--vscode-sidebar-background);
        border: 1px solid var(--vscode-widget-border);
        border-radius: 4px;
        padding: 24px;
        max-width: 600px;
      }
      .close-btn {
        position: absolute;
        top: 8px;
        right: 8px;
        background: transparent;
        border: none;
        font-size: 20px;
        cursor: pointer;
        padding: 4px 8px;
      }
      .close-btn:focus-visible {
        outline: 2px solid var(--vscode-focusBorder);
        outline-offset: 2px;
      }
    </style>
    <div
      class="modal"
      role="dialog"
      aria-modal="true"
      aria-labelledby="modal-title">
      <button
        class="close-btn"
        type="button"
        data-action="close"
        aria-label="Close dialog">
        ×
      </button>
      <h2 id="modal-title">${this.title}</h2>
      <div class="content">${this.content}</div>
    </div>
  `;
}

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 `<button>` with `aria-label`
- ✓ Focus trapping for modals
- ✓ Keyboard support (Tab, Shift+Tab, Escape)
- ✓ Focus restoration on close
- ✓ `:focus-visible` styling

---

## Logger Migration

### ❌ Before (console.log Everywhere)
```javascript
async loadData() {
  console.log('Loading data...');

  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    console.log('Data loaded:', data);
    this.data = data;
  } catch (error) {
    console.error('Failed to load data:', error);
  }
}

processData() {
  console.log('Processing...');
  console.warn('This might take a while');
  // processing logic
  console.log('Done processing');
}
```

### ✅ After (Centralized Logger)
```javascript
import { logger } from '../utils/logger.js';

async loadData() {
  const endTimer = logger.time('[MyComponent] Data load'); // ✓ Performance timing
  logger.debug('[MyComponent] Starting data load'); // ✓ debug() only in dev

  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    logger.info('[MyComponent] Data loaded successfully', {
      itemCount: data.length
    }); // ✓ Structured data
    this.data = data;
    endTimer(); // Logs elapsed time
  } catch (error) {
    logger.error('[MyComponent] Failed to load data', error); // ✓ Proper error logging
    throw error;
  }
}

processData() {
  logger.info('[MyComponent] Starting data processing');
  logger.warn('[MyComponent] Heavy processing operation'); // ✓ Use warn for concerns
  // processing logic
  logger.info('[MyComponent] Processing completed');
}
```

**Key Changes:**
- ✓ Import logger utility
- ✓ Use `logger.debug()` for development-only logs
- ✓ Use `logger.info()` for informational messages
- ✓ Use `logger.warn()` for warnings
- ✓ Use `logger.error()` for errors
- ✓ Add component name prefix `[ComponentName]`
- ✓ Use `logger.time()` for performance measurements

**Logger API:**
```javascript
// Enable debug logs: localStorage.setItem('dss_debug', 'true')
// Or in console: window.dssLogger.enableDebug()

logger.debug('Debug message'); // Only in dev or when debug enabled
logger.info('Info message');   // Always shown
logger.warn('Warning');         // Warning level
logger.error('Error', err);     // Error level

const endTimer = logger.time('Operation label');
// ... do work ...
endTimer(); // Logs: [TIME] Operation label: 234ms
```

---

## State Management

### ❌ Before (Direct DOM Manipulation)
```javascript
export default class Counter extends HTMLElement {
  connectedCallback() {
    this.count = 0;
    this.innerHTML = `
      <div>Count: <span id="count">0</span></div>
      <button onclick="this.getRootNode().host.increment()">+</button>
    `;
  }

  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 = `
      <style>
        .container {
          display: flex;
          align-items: center;
          gap: 16px;
          padding: 16px;
        }
        button {
          padding: 8px 16px;
          background: var(--vscode-button-background);
          color: var(--vscode-button-foreground);
          border: none;
          border-radius: 2px;
          cursor: pointer;
        }
      </style>
      <div class="container">
        <div>Count: <span>${this.state.count}</span></div>
        <button type="button" data-action="increment">+</button>
        <button type="button" data-action="decrement">-</button>
      </div>
    `;
  }

  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 = `
      <div style="padding: 24px; background: #1e1e1e;">
        <h2 style="color: white; font-size: 18px;">Title</h2>
        <div class="item" onclick="alert('clicked')" onmouseover="this.style.background='#333'" onmouseout="this.style.background=''">
          Click me
        </div>
        <div onclick="this.getRootNode().host.loadData()" style="cursor: pointer; padding: 8px;">Load Data</div>
      </div>
    `;
  }
}

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 = `
      <style>
        :host {
          display: block;
        }
        .container {
          padding: 24px;
          background: var(--vscode-sidebar-background);
        }
        h2 {
          color: var(--vscode-foreground);
          font-size: 18px;
          margin: 0 0 16px 0;
        }
        .item {
          padding: 12px;
          background: var(--vscode-list-inactiveSelectionBackground);
          border-radius: 4px;
          margin-bottom: 8px;
          cursor: pointer;
          transition: background-color 0.1s;
        }
        .item:hover {
          background: var(--vscode-list-hoverBackground);
        }
        .item:focus-visible {
          outline: 2px solid var(--vscode-focusBorder);
          outline-offset: 2px;
        }
        button {
          padding: 8px 16px;
          background: var(--vscode-button-background);
          color: var(--vscode-button-foreground);
          border: none;
          border-radius: 2px;
          cursor: pointer;
        }
        button:disabled {
          opacity: 0.5;
          cursor: not-allowed;
        }
      </style>
      <div class="container">
        <h2>Title</h2>
        ${this.state.data.map((item) => `
          <div class="item" tabindex="0" data-action="itemClick" data-item-id="${item.id}">
            ${item.name}
          </div>
        `).join('')}
        <button
          type="button"
          data-action="loadData"
          ?disabled="${this.state.isLoading}">
          ${this.state.isLoading ? 'Loading...' : 'Load Data'}
        </button>
        ${this.state.error ? `
          <div role="alert" style="color: var(--vscode-errorForeground); margin-top: 8px;">
            Error: ${this.state.error}
          </div>
        ` : ''}
      </div>
    `;
  }

  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 `<style>` block)
- ✓ Semantic `<button>` elements with `type="button"`
- ✓ Event delegation pattern
- ✓ Proper state management with `setState()`
- ✓ Logger utility instead of console.log
- ✓ Accessibility (keyboard support, focus management, ARIA)
- ✓ Error handling and loading states
- ✓ AbortController for cleanup
- ✓ Custom events for component communication

---

## Testing Your Migration

After migrating, verify compliance:

```bash
# Run quality checks
./scripts/verify-quality.sh

# Expected results:
# ✓ No inline event handlers (0)
# ✓ Inline styles ≤10
# ✓ Console statements ≤10
# ✓ All syntax valid
```

## Reference Implementations

Study these files for best practices:
- `admin-ui/js/workdesks/base-workdesk.js`
- `admin-ui/js/components/metrics/ds-frontpage.js`
- `admin-ui/js/components/metrics/ds-metric-card.js`

## Standards Documentation

Full standards: `.knowledge/dss-coding-standards.json`

Need help? Check the coding standards JSON for detailed rules, patterns, and enforcement mechanisms.
