{ "$schema": "dss-knowledge-v1", "type": "coding_standards", "version": "1.0.0", "last_updated": "2025-12-08", "description": "Immutable coding best practices for all DSS code. These standards are enforced via pre-commit hooks and guide all code contributions.", "web_components": { "principle": "All UI components MUST be Web Components using Custom Elements API", "rules": [ { "id": "WC-001", "rule": "Shadow DOM Required", "requirement": "MUST use attachShadow({ mode: 'open' }) for all components", "exceptions": "None - this is non-negotiable for style encapsulation", "rationale": "Prevents global CSS pollution and ensures component isolation", "enforcement": "Pre-commit hook fails if component extends HTMLElement without Shadow DOM" }, { "id": "WC-002", "rule": "Lifecycle Management", "requirement": "MUST implement connectedCallback, disconnectedCallback, and attributeChangedCallback where appropriate", "pattern": "Always clean up event listeners and subscriptions in disconnectedCallback", "example": "disconnectedCallback() { if (this.unsubscribe) this.unsubscribe(); if (this.abortController) this.abortController.abort(); }" }, { "id": "WC-003", "rule": "Observable Attributes", "requirement": "Define observedAttributes static getter for reactive attributes", "pattern": "static get observedAttributes() { return ['title', 'value', 'color']; }", "rationale": "Enables reactive attribute changes without manual DOM manipulation" }, { "id": "WC-004", "rule": "Component Registration", "requirement": "Define custom element in component file using customElements.define()", "pattern": "customElements.define('ds-component-name', ComponentClass);", "naming": "All DSS components prefixed with 'ds-' to avoid conflicts" } ] }, "style_management": { "principle": "Zero inline styles policy - all styles in Shadow DOM or external stylesheets", "rules": [ { "id": "STYLE-001", "rule": "NO Inline Styles", "requirement": "FORBIDDEN: style=\"...\" attributes in templates", "exceptions": "ONLY dynamic computed values (e.g., transform: translateX(${x}px), width: ${percent}%)", "enforcement": "Pre-commit hook fails if >10 inline styles detected", "rationale": "Maintainability, separation of concerns, style encapsulation" }, { "id": "STYLE-002", "rule": "Shadow DOM Styles", "requirement": "All component styles in
...
`;", "best_practice": "Define styles at top of template for clarity" }, { "id": "STYLE-003", "rule": "VSCode Theme Tokens", "requirement": "MUST use CSS custom properties from VSCode theme", "required_variables": [ "--vscode-foreground", "--vscode-background", "--vscode-sidebar-background", "--vscode-button-background", "--vscode-button-foreground", "--vscode-button-secondaryBackground", "--vscode-button-secondaryForeground", "--vscode-focusBorder", "--vscode-list-hoverBackground", "--vscode-list-activeSelectionBackground", "--vscode-list-activeSelectionForeground", "--vscode-descriptionForeground", "--vscode-widget-border", "--vscode-errorForeground" ], "rationale": "Ensures consistent theming with VSCode dark/light mode" }, { "id": "STYLE-004", "rule": "Constructable Stylesheets", "requirement": "Use for shared styles across multiple components", "pattern": "const sheet = new CSSStyleSheet(); sheet.replaceSync(css); shadowRoot.adoptedStyleSheets = [sheet];", "use_cases": ["Common button styles", "Typography system", "Layout utilities"] }, { "id": "STYLE-005", "rule": "CSS Hover Effects", "requirement": "Use :hover pseudo-class, NOT onmouseover/onmouseout", "correct": ".card:hover { background: var(--vscode-list-hoverBackground); }", "forbidden": "onmouseover=\"this.style.background='...'\" onmouseout=\"this.style.background=''\"", "rationale": "Performance, maintainability, separation of concerns" } ] }, "event_handling": { "principle": "Event delegation pattern - NO inline event handlers", "rules": [ { "id": "EVENT-001", "rule": "NO Inline Events", "requirement": "FORBIDDEN: onclick, onmouseover, onmouseout, onkeydown, onkeyup, etc. in HTML", "enforcement": "Pre-commit hook fails if ANY inline event handlers detected", "rationale": "Security (CSP compliance), maintainability, testability, separation of concerns" }, { "id": "EVENT-002", "rule": "Event Delegation Pattern", "requirement": "Single delegated listener per component using data-action attributes", "pattern": "this.shadowRoot.addEventListener('click', (e) => { const action = e.target.closest('[data-action]')?.dataset.action; if (action && this[`handle${action}`]) { this[`handle${action}`](e); } });", "html_example": "", "js_example": "handleSave(e) { /* implementation */ }" }, { "id": "EVENT-003", "rule": "Custom Events for Communication", "requirement": "Component communication via CustomEvent with composed: true", "pattern": "this.dispatchEvent(new CustomEvent('ds-action', { detail: { action, data }, bubbles: true, composed: true }));", "rationale": "composed: true allows events to bubble out of Shadow DOM boundaries" }, { "id": "EVENT-004", "rule": "Event Listener Cleanup", "requirement": "Always remove event listeners in disconnectedCallback", "pattern": "this.abortController = new AbortController(); addEventListener('click', handler, { signal: this.abortController.signal }); // disconnectedCallback: this.abortController.abort();", "alternative": "Store handlers as methods and call removeEventListener explicitly" }, { "id": "EVENT-005", "rule": "Keyboard Navigation", "requirement": "Support Tab, Enter, Escape, Arrow keys for interactive components", "pattern": "this.shadowRoot.addEventListener('keydown', (e) => { if (e.key === 'Escape') this.close(); if (e.key === 'Enter') this.submit(); });", "rationale": "Accessibility requirement for keyboard-only users" } ] }, "accessibility": { "principle": "WCAG 2.1 Level AA compliance minimum", "rules": [ { "id": "A11Y-001", "rule": "Semantic HTML", "requirement": "MUST use semantic elements: ", "rationale": "Screen readers rely on semantic HTML for navigation and interaction" }, { "id": "A11Y-002", "rule": "Button Type Attribute", "requirement": "ALL ", "rationale": "Prevents accidental form submission in HTML forms", "enforcement": "Pre-commit hook warns about buttons without type attribute" }, { "id": "A11Y-003", "rule": "ARIA Attributes", "requirement": "Use when semantic HTML insufficient", "required_patterns": [ "aria-label for icon-only buttons", "aria-labelledby for complex widgets", "aria-describedby for additional context", "role for custom interactive elements" ], "examples": [ "", "
...
" ] }, { "id": "A11Y-004", "rule": "Focus Management", "requirement": "Keyboard navigation support for all interactive elements", "css_pattern": ".btn:focus-visible { outline: 2px solid var(--vscode-focusBorder); outline-offset: 2px; }", "js_requirements": [ "trapFocus() for modals", "restoreFocus() on close", "Skip links for main content", "Focus first invalid field on validation error" ] }, { "id": "A11Y-005", "rule": "Color Contrast", "requirement": "Minimum 4.5:1 for normal text, 3:1 for large text", "tool": "Use VSCode theme tokens which meet contrast requirements", "testing": "Chrome DevTools Lighthouse audit" }, { "id": "A11Y-006", "rule": "Screen Reader Testing", "requirement": "Test all components with NVDA/JAWS (Windows) or VoiceOver (Mac)", "checklist": [ "Navigation order makes sense", "All buttons have clear labels", "Error messages announced", "Status updates announced with aria-live" ] } ] }, "state_management": { "principle": "Centralized state with reactive updates", "rules": [ { "id": "STATE-001", "rule": "Context Store for Global State", "requirement": "Use contextStore for application-wide state (projectId, teamId, userId)", "pattern": "this.unsubscribe = contextStore.subscribeToKey('projectId', (newValue) => { this.projectId = newValue; this.render(); });", "path": "admin-ui/js/stores/context-store.js" }, { "id": "STATE-002", "rule": "Component Local State", "requirement": "Component-specific state in this.state object", "pattern": "this.state = { isLoading: false, data: null, error: null }; setState(updates) { Object.assign(this.state, updates); this.render(); }", "best_practice": "Initialize state in constructor" }, { "id": "STATE-003", "rule": "NO Direct DOM Manipulation", "requirement": "State changes trigger re-renders, not direct DOM updates", "forbidden": "document.getElementById('foo').textContent = 'bar'; element.style.display = 'none';", "correct": "this.setState({ foo: 'bar', visible: false }); // render() handles DOM updates", "rationale": "Maintains single source of truth, prevents state/view desync" }, { "id": "STATE-004", "rule": "Subscription Cleanup", "requirement": "Always unsubscribe in disconnectedCallback", "pattern": "connectedCallback() { this.unsubscribe = store.subscribe(...); } disconnectedCallback() { if (this.unsubscribe) this.unsubscribe(); }", "rationale": "Prevents memory leaks from orphaned subscriptions" }, { "id": "STATE-005", "rule": "Immutable State Updates", "requirement": "Create new state objects, don't mutate existing", "correct": "this.state = { ...this.state, count: this.state.count + 1 };", "forbidden": "this.state.count++; // direct mutation", "rationale": "Enables change detection and debugging" } ] }, "code_organization": { "principle": "Single Responsibility, clear structure, modular design", "rules": [ { "id": "ORG-001", "rule": "File Size Limit", "requirement": "Maximum 500 lines per file", "action": "If larger, split into multiple modules", "enforcement": "Quality check warns on files >500 lines", "current_violation": "app.js at 4,347 lines must be decomposed" }, { "id": "ORG-002", "rule": "Directory Structure", "requirement": "Strict organization by type and purpose", "structure": { "admin-ui/js/components/": "Web components (*.js)", "admin-ui/js/components/layout/": "Layout components (shell, header, sidebar)", "admin-ui/js/components/metrics/": "Dashboard and metric components", "admin-ui/js/components/tools/": "Tool-specific components", "admin-ui/js/components/listings/": "List/table components", "admin-ui/js/stores/": "State management", "admin-ui/js/utils/": "Helper functions and utilities", "admin-ui/js/core/": "Core modules (router, messaging, workflows)", "admin-ui/js/workdesks/": "Team workdesk controllers", "admin-ui/js/config/": "Configuration files" } }, { "id": "ORG-003", "rule": "Import Style", "requirement": "Explicit named imports, no wildcards", "correct": "import { hydrateComponent } from '../config/component-registry.js';", "forbidden": "import * as registry from '../config/component-registry.js';", "rationale": "Tree-shaking, explicit dependencies, better IDE support" }, { "id": "ORG-004", "rule": "Export Style", "requirement": "Named exports for utilities, default export for components", "utility_pattern": "export function debounce(fn, ms) { ... } export function throttle(fn, ms) { ... }", "component_pattern": "export default class MyComponent extends HTMLElement { ... }", "rationale": "Convention clarity, supports tree-shaking" }, { "id": "ORG-005", "rule": "Single Responsibility Principle", "requirement": "One component = one concern, one file = one primary export", "examples": [ "ds-metric-card.js: Displays a single metric card", "ds-frontpage.js: Orchestrates dashboard layout", "context-store.js: Manages global application state" ], "anti_pattern": "utility-functions.js with 50 unrelated functions" } ] }, "error_handling": { "principle": "Structured logging, user-friendly errors, graceful degradation", "rules": [ { "id": "ERROR-001", "rule": "Centralized Logger", "requirement": "Use logger utility for all logging", "path": "admin-ui/js/utils/logger.js", "methods": [ "logger.debug(msg, ...args): Development-only logging", "logger.info(msg, ...args): Informational messages", "logger.warn(msg, ...args): Warning conditions", "logger.error(msg, ...args): Error conditions" ], "pattern": "import { logger } from '../utils/logger.js'; logger.info('[ComponentName] Action completed', { data });" }, { "id": "ERROR-002", "rule": "NO console.log in Production", "requirement": "Replace all console.log/warn/error with logger methods", "enforcement": "Pre-commit hook warns if >10 console statements", "exceptions": "Core initialization logging in app.js only", "migration": "console.log('foo') → logger.debug('foo')" }, { "id": "ERROR-003", "rule": "User-Friendly Error Messages", "requirement": "Error messages must be actionable and clear", "good": "Failed to load projects. Please check your internet connection and try again.", "bad": "Error: HTTP 500", "TypeError: Cannot read property 'id' of undefined", "pattern": "Show what failed + why it might have failed + what user can do" }, { "id": "ERROR-004", "rule": "Error Taxonomy", "requirement": "Use structured error codes from messaging.js", "codes": { "E1xxx": "User errors (invalid input, forbidden actions)", "E2xxx": "Validation errors (missing fields, invalid formats)", "E3xxx": "API errors (request failed, timeout, unauthorized)", "E4xxx": "System errors (unexpected, network, storage)", "E5xxx": "Integration errors (Figma, external APIs)", "S1xxx": "Success codes" }, "usage": "notifyError('E3001', 'Failed to fetch projects', error);" }, { "id": "ERROR-005", "rule": "Graceful Degradation", "requirement": "Components must handle missing/invalid data gracefully", "patterns": [ "if (!data) return this.renderEmptyState();", "if (error) return this.renderError(error);", "const items = data?.items ?? [];" ], "anti_pattern": "Assuming data exists and causing cascading failures" }, { "id": "ERROR-006", "rule": "Try-Catch for Async Operations", "requirement": "Wrap all async operations in try-catch", "pattern": "async loadData() { try { const data = await fetch(...); } catch (error) { logger.error('[Component] Failed to load', error); this.handleError(error); } }", "rationale": "Prevents unhandled promise rejections" } ] }, "performance": { "principle": "Optimize for user experience, measure and improve", "rules": [ { "id": "PERF-001", "rule": "Lazy Loading", "requirement": "Non-critical components loaded on-demand via hydrateComponent()", "pattern": "await hydrateComponent('ds-screenshot-gallery', container);", "benefits": "Faster initial load, reduced bundle size", "applies_to": ["Tool components", "Heavy visualizations", "Rarely-used features"] }, { "id": "PERF-002", "rule": "Virtual Scrolling", "requirement": "Use for lists >100 items", "libraries": ["lit-virtualizer", "virtual-scroller"], "pattern": "import { VirtualScroller } from 'virtual-scroller'; new VirtualScroller(container, { items, renderItem });", "applies_to": ["Token lists", "Component catalogs", "Log viewers"] }, { "id": "PERF-003", "rule": "Debouncing and Throttling", "requirement": "Debounce search inputs, throttle scroll/resize handlers", "debounce_pattern": "this.searchDebounced = debounce(() => this.performSearch(), 300);", "throttle_pattern": "this.onScrollThrottled = throttle(() => this.handleScroll(), 100);", "use_cases": { "debounce": "Search inputs, form validation, autosave", "throttle": "Scroll handlers, resize handlers, mousemove tracking" } }, { "id": "PERF-004", "rule": "Avoid Unnecessary Re-renders", "requirement": "Only re-render when state actually changes", "pattern": "attributeChangedCallback(name, oldValue, newValue) { if (oldValue === newValue) return; this.render(); }", "best_practice": "Compare old vs new state before rendering" }, { "id": "PERF-005", "rule": "Bundle Size Monitoring", "requirement": "Monitor and optimize JavaScript bundle sizes", "targets": { "individual_component": "<50KB", "total_bundle": "<500KB", "critical_path": "<200KB" }, "tools": ["webpack-bundle-analyzer", "source-map-explorer"] }, { "id": "PERF-006", "rule": "Minimize DOM Operations", "requirement": "Batch DOM updates, use DocumentFragment for multiple inserts", "pattern": "const fragment = document.createDocumentFragment(); items.forEach(item => fragment.appendChild(createNode(item))); container.appendChild(fragment);", "anti_pattern": "items.forEach(item => container.appendChild(createNode(item))); // causes multiple reflows" } ] }, "security": { "principle": "Secure by default, defense in depth", "rules": [ { "id": "SEC-001", "rule": "NO Hardcoded Secrets", "requirement": "FORBIDDEN: API keys, passwords, tokens, credentials in code", "enforcement": "Pre-commit hook fails if secret patterns detected (apiKey, api_key, password, secret, token)", "correct": "Use environment variables or secure configuration service", "rationale": "Prevents credential leaks in version control" }, { "id": "SEC-002", "rule": "Input Sanitization", "requirement": "ALWAYS escape user input before rendering to DOM", "utility": "ComponentHelpers.escapeHtml(userInput)", "pattern": "
${ComponentHelpers.escapeHtml(project.name)}
", "forbidden": "
${project.name}
// XSS vulnerability", "rationale": "Prevents XSS attacks" }, { "id": "SEC-003", "rule": "CSP Compliance", "requirement": "NO eval(), NO new Function(), NO inline scripts", "forbidden": [ "eval(code)", "new Function('x', 'return x * 2')", " in templates" ], "rationale": "Content Security Policy compatibility, prevents code injection" }, { "id": "SEC-004", "rule": "HTTPS Only", "requirement": "All external requests use HTTPS", "enforcement": "No http:// URLs in fetch() calls", "correct": "fetch('https://api.example.com/data')", "forbidden": "fetch('http://api.example.com/data')", "exceptions": "localhost development only" }, { "id": "SEC-005", "rule": "Validate API Responses", "requirement": "Always validate structure and content of API responses", "pattern": "const data = await response.json(); if (!data || !Array.isArray(data.items)) throw new Error('Invalid response');", "rationale": "Prevents errors from malformed/malicious responses" }, { "id": "SEC-006", "rule": "Authentication Token Handling", "requirement": "Store auth tokens securely, never in localStorage", "correct": "sessionStorage (better: httpOnly cookies)", "forbidden": "localStorage for sensitive tokens", "rationale": "localStorage accessible to XSS attacks" } ] }, "testing_and_quality": { "principle": "Automated quality gates, comprehensive testing", "rules": [ { "id": "TEST-001", "rule": "Pre-commit Hooks", "requirement": "MUST pass all quality checks before commit", "script": "scripts/verify-quality.sh", "thresholds": { "inline_styles": "≤10 (exceptions for dynamic values only)", "inline_events": "0 (zero tolerance)", "console_statements": "≤10 (production code only)", "file_size": "≤100KB per file", "syntax_errors": "0 (zero tolerance)" }, "bypass": "git commit --no-verify (not recommended, requires justification)" }, { "id": "TEST-002", "rule": "Component Unit Tests", "requirement": "Unit tests for all components", "coverage": "Minimum 80% for critical paths", "framework": "Web Test Runner or Vitest", "test_cases": [ "Component renders correctly", "Props/attributes update component", "Event handlers work", "Cleanup happens on disconnect" ] }, { "id": "TEST-003", "rule": "Integration Tests", "requirement": "Test critical user flows end-to-end", "examples": [ "Project creation workflow", "Token extraction from Figma", "Component audit process", "User authentication flow" ], "tools": ["Playwright", "Cypress"] }, { "id": "TEST-004", "rule": "Visual Regression Testing", "requirement": "Screenshot comparison for UI changes", "tools": ["Playwright screenshots", "Percy", "Chromatic"], "process": "Capture baseline → Make changes → Compare → Approve/reject", "applies_to": "All visible UI components" }, { "id": "TEST-005", "rule": "Documentation", "requirement": "JSDoc comments for public APIs and complex logic", "pattern": "/**\n * Load project data from API\n * @param {string} projectId - The project identifier\n * @returns {Promise} The loaded project\n * @throws {Error} If project not found or network error\n */\nasync loadProject(projectId) { ... }", "applies_to": ["Public component methods", "Utility functions", "Store APIs"] }, { "id": "TEST-006", "rule": "Code Review Checklist", "requirement": "All PRs reviewed against these standards", "checklist": [ "✓ Shadow DOM used for all components", "✓ No inline styles or event handlers", "✓ Proper accessibility attributes", "✓ Event listeners cleaned up", "✓ User input sanitized", "✓ Error handling implemented", "✓ Tests added/updated" ] } ] }, "enforcement": { "description": "How these standards are enforced in the development workflow", "mechanisms": [ { "type": "Pre-commit Hooks", "file": ".git/hooks/pre-commit", "action": "Runs scripts/verify-quality.sh automatically", "can_bypass": "git commit --no-verify (requires justification)" }, { "type": "Quality Verification Script", "file": "scripts/verify-quality.sh", "checks": [ "Inline event handlers detection", "Inline styles counting", "Missing ARIA attributes", "Console.log statements", "JavaScript syntax validation", "Hardcoded secrets detection", "Shadow DOM usage statistics", "File size warnings" ] }, { "type": "Immutability Protection", "file": ".clauderc", "protection": "This file listed in protected_core_files", "requirement": "ALLOW_CORE_CHANGES=true to modify" }, { "type": "AI Agent Instructions", "description": "AI assistants programmed to follow these standards", "behavior": [ "Always use Shadow DOM for components", "Never generate inline event handlers", "Extract inline styles to style blocks", "Add proper accessibility attributes", "Use logger instead of console.log" ] } ] }, "migration_strategy": { "description": "How to migrate existing code to these standards", "phases": [ { "phase": 1, "name": "Foundation Fixes", "duration": "Week 1", "priority": "Critical", "tasks": [ "Refactor tool-templates.js to generate compliant HTML", "Create logger utility (admin-ui/js/utils/logger.js)", "Update verify-quality.sh thresholds", "Create migration guide with examples" ] }, { "phase": 2, "name": "Component Migration - Batch A", "duration": "Week 2", "priority": "High", "targets": [ "Layout components (ds-project-selector, ds-shell, ds-header)", "Most violated: ds-screenshot-gallery, ds-network-monitor" ], "pattern": "Add Shadow DOM → Extract styles → Replace inline events → Add ARIA" }, { "phase": 3, "name": "Component Migration - Batch B", "duration": "Week 3", "priority": "High", "targets": [ "Tool components (ds-activity-log, ds-test-results)", "Admin/listings (ds-project-list, ds-token-list)" ] }, { "phase": 4, "name": "Monolith Decomposition", "duration": "Week 4", "priority": "Critical", "target": "app.js (4,347 lines → 7 modules)", "modules": [ "app.js: Main orchestrator (<500 lines)", "router.js: Hash routing", "auth-manager.js: Authentication", "api-client.js: Fetch wrapper", "error-handler.js: Global errors", "state-manager.js: State coordination", "init.js: Initialization" ] }, { "phase": 5, "name": "Quality Enforcement", "duration": "Week 5", "priority": "Medium", "tasks": [ "Update thresholds (inline styles ≤10, events = 0)", "Add ESLint/Stylelint rules", "CI/CD integration", "Developer training" ] } ] }, "reference_implementations": { "description": "Examples of components that follow these standards", "files": [ { "file": "admin-ui/js/workdesks/base-workdesk.js", "demonstrates": [ "Semantic HTML (buttons not divs)", "Event delegation pattern", "Extracted styles in style block", "ARIA attributes", "Focus management" ] }, { "file": "admin-ui/js/components/metrics/ds-frontpage.js", "demonstrates": [ "Shadow DOM implementation", "Zero inline styles", "Lifecycle management", "State subscription pattern", "Event cleanup" ] }, { "file": "admin-ui/js/components/metrics/ds-metric-card.js", "demonstrates": [ "Observable attributes", "Shadow DOM encapsulation", "Reactive rendering", "Component composition" ] } ] }, "success_metrics": { "description": "Measurable goals for DSS code quality", "current_state": { "shadow_dom_adoption": "23% (12/53 components)", "inline_event_handlers": "20+", "inline_styles": "1,288", "console_statements": "100+", "largest_file": "app.js at 4,347 lines / 156KB" }, "target_state": { "shadow_dom_adoption": "100% (53/53 components)", "inline_event_handlers": "0 (zero tolerance)", "inline_styles": "<10 (dynamic values only)", "console_statements": "<10 (core only)", "largest_file": "<500 lines per file" }, "tracking": { "method": "Pre-commit hook statistics", "frequency": "Every commit", "reporting": "Monthly code quality dashboard" } } }