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:
979
docs/DSS_CODING_PRINCIPLES.md
Normal file
979
docs/DSS_CODING_PRINCIPLES.md
Normal file
@@ -0,0 +1,979 @@
|
||||
# DSS Coding Principles
|
||||
|
||||
Comprehensive principles for building the Design System Swarm (DSS) - a living, breathing design system organism.
|
||||
|
||||
**Version**: 1.0
|
||||
**Last Updated**: 2025-12-06
|
||||
**Grade**: A (Excellent quality with room for accessibility improvements)
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Architectural Principles](#1-architectural-principles)
|
||||
2. [Code Quality Standards](#2-code-quality-standards)
|
||||
3. [Organism Framework Principles](#3-organism-framework-principles)
|
||||
4. [Keyboard Accessibility](#4-keyboard-accessibility)
|
||||
5. [Web Component Standards](#5-web-component-standards)
|
||||
6. [Security Guidelines](#6-security-guidelines)
|
||||
7. [Event Handling & Memory Management](#7-event-handling--memory-management)
|
||||
8. [Testing & Quality Assurance](#8-testing--quality-assurance)
|
||||
9. [Documentation Standards](#9-documentation-standards)
|
||||
10. [Review Checklists](#10-review-checklists)
|
||||
|
||||
---
|
||||
|
||||
## 1. Architectural Principles
|
||||
|
||||
DSS follows a **monolithic core architecture with strong separation of concerns**. These principles ensure maintainability and clarity.
|
||||
|
||||
### 1.1 Monolithic Core Design
|
||||
|
||||
**Principle**: Single source of truth with immutable design system core
|
||||
|
||||
**Guidelines**:
|
||||
- Central database of record (heart ❤️)
|
||||
- All design tokens pass through normalization pipeline
|
||||
- External systems translate TO DSS, not the reverse
|
||||
- Immutable by default for design data
|
||||
|
||||
**Benefits**:
|
||||
- ✅ Simplified deployment and maintenance
|
||||
- ✅ Reduced operational complexity
|
||||
- ✅ Clear data flow and debugging
|
||||
- ✅ Strong consistency guarantees
|
||||
|
||||
**Trade-offs**:
|
||||
- Less flexible than microservices
|
||||
- Single point of failure (mitigated by backups)
|
||||
- Harder to scale horizontally (acceptable for current scale)
|
||||
|
||||
**Reference**: `/docs/ARCHITECTURE_REVIEW.md#1-monolithic-core`
|
||||
|
||||
### 1.2 External Translation Pattern
|
||||
|
||||
**Principle**: DSS doesn't adapt to external systems; external systems translate TO DSS
|
||||
|
||||
**Implementation**:
|
||||
```
|
||||
Figma → Translation Layer → DSS Core
|
||||
CSS → Translation Layer → DSS Core
|
||||
Tailwind → Translation Layer → DSS Core
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- ✅ Maintains data integrity
|
||||
- ✅ Prevents drift between sources
|
||||
- ✅ Clear contract boundaries
|
||||
- ✅ Easy to add new sources
|
||||
|
||||
**Anti-Pattern**:
|
||||
- ❌ DSS adapting to external format variations
|
||||
- ❌ Custom handling per-source in core
|
||||
- ❌ Loose source contracts
|
||||
|
||||
**Reference**: `/docs/ARCHITECTURE_REVIEW.md#2-external-translation`
|
||||
|
||||
### 1.3 Layered Architecture
|
||||
|
||||
**Principle**: Clear separation between storage, domain logic, and interfaces
|
||||
|
||||
**Layers** (bottom to top):
|
||||
1. **Storage Layer** - SQLite, file system, cache
|
||||
2. **Core Domain Layer** - Token models, merge logic, validation
|
||||
3. **Ingestion Layer** - Source parsers (CSS, SCSS, Figma, etc.)
|
||||
4. **Analysis Layer** - Project scanning, quick wins, metrics
|
||||
5. **Generation Layer** - Storybook, component generation, output
|
||||
6. **API Layer** - REST + MCP interfaces
|
||||
|
||||
**Rules**:
|
||||
- Lower layers have NO dependencies on upper layers
|
||||
- Each layer has a single responsibility
|
||||
- Data flows through layers in defined directions
|
||||
- Minimal coupling between layers
|
||||
|
||||
**Reference**: `/docs/ARCHITECTURE_REVIEW.md#3-layered-architecture`
|
||||
|
||||
### 1.4 Architectural Patterns
|
||||
|
||||
**Strategy Pattern** - For token merge strategies
|
||||
```python
|
||||
class TokenMerger:
|
||||
def __init__(self, strategy: MergeStrategy):
|
||||
self.strategy = strategy # FIRST, LAST, PREFER_FIGMA, etc.
|
||||
```
|
||||
|
||||
**Abstract Base Classes** - For extension points
|
||||
```python
|
||||
class TokenSource(ABC):
|
||||
@abstractmethod
|
||||
async def extract(self, source: str) -> TokenCollection:
|
||||
pass
|
||||
```
|
||||
|
||||
**Data Classes** - For immutable domain models
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class DesignToken:
|
||||
name: str
|
||||
value: str
|
||||
type: TokenType
|
||||
```
|
||||
|
||||
**Dependency Injection** - Explicit dependencies
|
||||
```python
|
||||
# Good: dependencies explicit
|
||||
def __init__(self, db_path: str, cache: Cache):
|
||||
self.db = Database(db_path)
|
||||
self.cache = cache
|
||||
|
||||
# Avoid: hard-coded dependencies
|
||||
config_path = Path(__file__).parent / "config.json"
|
||||
```
|
||||
|
||||
**Reference**: `/docs/ARCHITECTURE_REVIEW.md#architecture-patterns`
|
||||
|
||||
---
|
||||
|
||||
## 2. Code Quality Standards
|
||||
|
||||
DSS maintains **A-grade code quality** with comprehensive standards.
|
||||
|
||||
### 2.1 Python Style & Conventions
|
||||
|
||||
**Following**: PEP 8 with flexibility
|
||||
|
||||
**Standards**:
|
||||
- 4-space indentation (never tabs)
|
||||
- Max line length: 100 characters (flexible)
|
||||
- snake_case for functions and variables
|
||||
- PascalCase for classes
|
||||
- UPPER_CASE for constants
|
||||
- Type hints for 100% of public APIs
|
||||
|
||||
**Naming Examples**:
|
||||
```python
|
||||
class TokenMerger: # Class: PascalCase
|
||||
async def merge(self, ...): # Method: snake_case
|
||||
MAX_TOKENS = 1000 # Constant: UPPER_CASE
|
||||
token_count = 0 # Variable: snake_case
|
||||
```
|
||||
|
||||
### 2.2 Error Handling
|
||||
|
||||
**Principle**: Explicit, meaningful error handling with no silent failures
|
||||
|
||||
**Good Practices**:
|
||||
```python
|
||||
# Specific exceptions with context
|
||||
try:
|
||||
data = json.loads(content)
|
||||
except (json.JSONDecodeError, KeyError) as e:
|
||||
logger.error(f"Failed to parse tokens: {e}")
|
||||
raise ValueError(f"Invalid token data: {e}") from e
|
||||
|
||||
# Meaningful error messages
|
||||
raise ValueError(f"Invalid token type: {token_type}. Expected one of: {valid_types}")
|
||||
```
|
||||
|
||||
**Anti-Patterns**:
|
||||
- ❌ Bare `except:` statements
|
||||
- ❌ Silent failures (`pass` without logging)
|
||||
- ❌ Generic `Exception` catching
|
||||
- ❌ Swallowing error details
|
||||
|
||||
**Reference**: `/docs/CODE_QUALITY.md#error-handling`
|
||||
|
||||
### 2.3 Async Patterns
|
||||
|
||||
**Principle**: All I/O operations are async
|
||||
|
||||
**Standard Pattern**:
|
||||
```python
|
||||
async def extract(self, source: str) -> TokenCollection:
|
||||
# Async file reading
|
||||
content = await asyncio.to_thread(Path(source).read_text)
|
||||
return self._parse(content)
|
||||
|
||||
# Never block the event loop
|
||||
# Use asyncio.to_thread for blocking operations
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- ✅ Responsive API under load
|
||||
- ✅ Efficient resource usage
|
||||
- ✅ Better scalability
|
||||
|
||||
### 2.4 Docstrings & Comments
|
||||
|
||||
**Standard**: All modules, classes, and public functions have docstrings
|
||||
|
||||
```python
|
||||
"""Module docstring explaining purpose.
|
||||
|
||||
This module handles token extraction from design files.
|
||||
"""
|
||||
|
||||
class TokenSource(ABC):
|
||||
"""Abstract base for design token sources.
|
||||
|
||||
Subclasses must implement the extract method to parse
|
||||
design files and return normalized TokenCollection.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def extract(self, source: str) -> TokenCollection:
|
||||
"""Extract tokens from a design source.
|
||||
|
||||
Args:
|
||||
source: Path to design file or design system URL
|
||||
|
||||
Returns:
|
||||
TokenCollection with normalized design tokens
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If source file doesn't exist
|
||||
ValueError: If source format is invalid
|
||||
"""
|
||||
```
|
||||
|
||||
**Comments**:
|
||||
- Explain WHY, not WHAT (code shows what)
|
||||
- Comment complex logic sections
|
||||
- Mark TODOs with urgency level
|
||||
- Update comments when code changes
|
||||
|
||||
**Reference**: `/docs/CODE_QUALITY.md#docstrings`
|
||||
|
||||
### 2.5 Type Hints & Mypy
|
||||
|
||||
**Principle**: 100% type hint coverage for public APIs
|
||||
|
||||
**Target Coverage**:
|
||||
- ✅ Current: ~60% for internal code
|
||||
- 🎯 Goal: 100% for all public functions
|
||||
- 🎯 Goal: 80% overall codebase coverage
|
||||
|
||||
**Examples**:
|
||||
```python
|
||||
from typing import Optional, Dict, List, Union
|
||||
from dataclasses import dataclass
|
||||
|
||||
async def process_tokens(
|
||||
tokens: List[DesignToken],
|
||||
merge_strategy: str = "first",
|
||||
validate: bool = True
|
||||
) -> Dict[str, DesignToken]:
|
||||
"""Process design tokens."""
|
||||
...
|
||||
|
||||
@dataclass
|
||||
class Result:
|
||||
success: bool
|
||||
data: Optional[Dict] = None
|
||||
error: Optional[str] = None
|
||||
```
|
||||
|
||||
**Run Mypy**: `mypy tools/ --strict`
|
||||
|
||||
**Reference**: `/docs/CODE_QUALITY.md#type-hints`
|
||||
|
||||
---
|
||||
|
||||
## 3. Organism Framework Principles
|
||||
|
||||
DSS is a **living organism** with organ systems. Understand and design for these systems.
|
||||
|
||||
### 3.1 The Heart ❤️ - Database Layer
|
||||
|
||||
**Function**: Central source of truth, persistent storage
|
||||
|
||||
**Vital Signs**:
|
||||
- Heart Rate = Database query response time
|
||||
- Blood Pressure = Data write performance
|
||||
- Rhythm = Regular sync cycles with external systems
|
||||
|
||||
**Health Indicators**:
|
||||
- ✅ Consistent query latency
|
||||
- ✅ No data corruption
|
||||
- ✅ Reliable backups
|
||||
- ❌ Slow queries = system sluggish
|
||||
- ❌ Data inconsistency = system confused
|
||||
- ❌ Missing backups = system vulnerable
|
||||
|
||||
**Design Principles**:
|
||||
- **Immutability First**: Historical record of all changes
|
||||
- **Strong Consistency**: No eventual consistency for design data
|
||||
- **Backups**: Regular backups (weekly minimum)
|
||||
- **Indexing**: Optimize common query patterns
|
||||
- **Schema**: Never break existing contracts
|
||||
|
||||
**Reference**: `/docs/DSS_ORGANISM_GUIDE.md#1-the-heart`
|
||||
|
||||
### 3.2 The Brain 🧠 - Validators & Analysis
|
||||
|
||||
**Function**: Validate data quality, detect patterns, learn from data
|
||||
|
||||
**Neural Functions**:
|
||||
- Pattern Recognition = Detecting design consistency issues
|
||||
- Memory = Storing learned rules and patterns
|
||||
- Decision-Making = Validation rules and quality gates
|
||||
- Learning = Quick wins detection, improvement suggestions
|
||||
|
||||
**Health Indicators**:
|
||||
- ✅ Validators catching errors early
|
||||
- ✅ Analysis discovering patterns
|
||||
- ✅ Smart suggestions for improvements
|
||||
- ❌ Validators too strict = system constrained
|
||||
- ❌ Validators too loose = garbage data accepted
|
||||
- ❌ Analysis finds nothing = system unaware
|
||||
|
||||
**Design Principles**:
|
||||
- **Validation First**: Catch errors at ingestion time
|
||||
- **No False Positives**: Rules must catch real problems
|
||||
- **Learning**: Improve detection over time
|
||||
- **Transparency**: Users understand why validation fails
|
||||
|
||||
**Reference**: `/docs/DSS_ORGANISM_GUIDE.md#2-the-brain`
|
||||
|
||||
### 3.3 The Digestive System 🍽️ - Ingestion Pipeline
|
||||
|
||||
**Function**: Break down external design information into usable tokens
|
||||
|
||||
**Digestion Stages**:
|
||||
1. **Intake** = Receiving raw design files
|
||||
2. **Breakdown** = Parsing format-specific syntax
|
||||
3. **Extraction** = Pulling out tokens/definitions
|
||||
4. **Normalization** = Converting to standard token format
|
||||
5. **Absorption** = Storing in the system
|
||||
|
||||
**Health Indicators**:
|
||||
- ✅ Successfully parsing diverse input formats
|
||||
- ✅ Zero loss of design information
|
||||
- ✅ Fast processing time
|
||||
- ❌ Failed parsing = maldigestion
|
||||
- ❌ Lost data = nutrient malabsorption
|
||||
- ❌ Slow processing = digestive dysfunction
|
||||
|
||||
**Design Principles**:
|
||||
- **Format Agnostic**: Support CSS, SCSS, JSON, Figma, etc.
|
||||
- **Zero Data Loss**: Never drop design information
|
||||
- **Normalization**: Convert to standard token format immediately
|
||||
- **Error Recovery**: Partial failures don't block entire ingestion
|
||||
|
||||
**Reference**: `/docs/DSS_ORGANISM_GUIDE.md#3-the-digestive-system`
|
||||
|
||||
### 3.4 The Nervous System 🔌 - APIs & Webhooks
|
||||
|
||||
**Function**: Perceive external world and respond automatically
|
||||
|
||||
**Neural Pathways**:
|
||||
- **Sensory Input** = Figma webhooks detecting changes
|
||||
- **Response** = Automatic sync and update triggers
|
||||
- **Communication** = REST API, MCP interface
|
||||
|
||||
**Health Indicators**:
|
||||
- ✅ Real-time responsiveness to Figma changes
|
||||
- ✅ Reliable webhook delivery
|
||||
- ✅ Available API endpoints
|
||||
- ❌ Missed webhook events = delayed sync
|
||||
- ❌ API downtime = external systems blocked
|
||||
- ❌ Race conditions = data corruption
|
||||
|
||||
**Design Principles**:
|
||||
- **Real-Time**: Respond to changes immediately
|
||||
- **Reliable**: Guarantee event delivery (retries, idempotency)
|
||||
- **Available**: 99.9% uptime for APIs
|
||||
- **Observable**: Log all external interactions
|
||||
|
||||
**Reference**: `/docs/DSS_ORGANISM_GUIDE.md#4-the-nervous-system`
|
||||
|
||||
### 3.5 The Circulatory System 🩸 - Token Distribution
|
||||
|
||||
**Function**: Distribute tokens throughout the system to where they're needed
|
||||
|
||||
**Distribution Network**:
|
||||
- Token → CSS Variables
|
||||
- Token → Storybook Documentation
|
||||
- Token → Component Generation
|
||||
- Token → External Integrations
|
||||
|
||||
**Design Principles**:
|
||||
- **Consistency**: Same token everywhere
|
||||
- **Traceability**: Know where each token goes
|
||||
- **Performance**: Efficient distribution, caching
|
||||
- **Flexibility**: Multiple output formats
|
||||
|
||||
**Reference**: `/docs/DSS_ORGANISM_GUIDE.md` (distributed throughout docs)
|
||||
|
||||
---
|
||||
|
||||
## 4. Keyboard Accessibility
|
||||
|
||||
All interactive elements must be keyboard accessible. Users should be able to navigate and interact with the entire application using only the keyboard.
|
||||
|
||||
### Guidelines
|
||||
|
||||
- **Tabindex Management**
|
||||
- Use `tabindex="0"` on all keyboard-accessible elements (buttons, links, inputs, custom components)
|
||||
- Use `tabindex="-1"` for elements that shouldn't be in the tab order but need programmatic focus
|
||||
- Never use positive tabindex values (e.g., `tabindex="1"`) - maintain natural DOM order
|
||||
|
||||
- **Focus Styling**
|
||||
- Always implement `:focus-visible` CSS for keyboard users
|
||||
- Ensure 3:1 contrast ratio for focus indicators
|
||||
- Use outline or box-shadow, not display/visibility changes
|
||||
- Apply consistently across all interactive elements
|
||||
|
||||
- **Keyboard Event Handlers**
|
||||
- **Enter/Space**: Activate buttons and toggles
|
||||
- **Escape**: Close modals, sidebars, and collapsible panels
|
||||
- **Arrow Keys**: Navigate lists, tabs, and menus
|
||||
- **Alt+Key**: Power user shortcuts for common actions
|
||||
|
||||
### Web Component Implementation
|
||||
|
||||
```javascript
|
||||
class MyButton extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ['disabled', 'tabindex', 'aria-label', 'aria-expanded'];
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Handle Enter/Space for activation
|
||||
this.keydownHandler = (e) => {
|
||||
if ((e.key === 'Enter' || e.key === ' ') && !this.disabled) {
|
||||
e.preventDefault();
|
||||
this.shadowRoot.querySelector('button').click();
|
||||
}
|
||||
};
|
||||
|
||||
this.addEventListener('keydown', this.keydownHandler);
|
||||
}
|
||||
|
||||
render() {
|
||||
const tabindex = this.disabled ? '-1' : (this.getAttribute('tabindex') || '0');
|
||||
// ... delegate tabindex to internal button
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.removeEventListener('keydown', this.keydownHandler);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Reference**: `/admin-ui/js/components/ds-button.js:79-96`
|
||||
|
||||
---
|
||||
|
||||
## 2. ARIA & Screen Reader Support
|
||||
|
||||
Screen reader users need semantic information about UI elements, their state, and purpose.
|
||||
|
||||
### Guidelines
|
||||
|
||||
- **Labeling**
|
||||
- Always use `aria-label` on icon-only buttons
|
||||
- Use `aria-label` on elements without visible text
|
||||
- Example: `<button aria-label="Toggle theme">🌙</button>`
|
||||
|
||||
- **State Indication**
|
||||
- Use `aria-expanded="true|false"` for toggles/collapsible elements
|
||||
- Use `aria-pressed="true|false"` for toggle buttons
|
||||
- Use `aria-invalid="true"` on form fields with errors
|
||||
- Update ARIA attributes when state changes
|
||||
|
||||
- **Relationships**
|
||||
- Use `aria-controls="id"` to link controls to their targets
|
||||
- Use `aria-describedby="id"` to link inputs to error messages
|
||||
- Use `aria-labelledby="id"` to link to headings when appropriate
|
||||
- Use `aria-hidden="true"` on decorative elements (SVGs, icons)
|
||||
|
||||
- **Semantic Roles**
|
||||
- Use native HTML elements when possible (button, input, link)
|
||||
- Use `role="button"` only on divs styled as buttons (not ideal)
|
||||
- Use `role="alert"` on error messages that appear dynamically
|
||||
- Always prefer semantic HTML over generic divs
|
||||
|
||||
- **Error Handling**
|
||||
- Generate unique IDs for error messages
|
||||
- Link errors to inputs: `aria-describedby="error-{id}"`
|
||||
- Use `role="alert"` so screen readers announce errors
|
||||
- Set `aria-invalid="true"` on the input element
|
||||
|
||||
### Web Component Implementation
|
||||
|
||||
```javascript
|
||||
class MyInput extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ['aria-label', 'aria-invalid', 'aria-describedby', 'error'];
|
||||
}
|
||||
|
||||
render() {
|
||||
const hasError = !!this.getAttribute('error');
|
||||
const errorId = hasError ? `error-${Math.random().toString(36).substr(2, 9)}` : '';
|
||||
const ariaInvalid = hasError ? 'aria-invalid="true"' : '';
|
||||
const ariaDescribedBy = hasError ? `aria-describedby="${errorId}"` : '';
|
||||
const ariaLabel = this.getAttribute('aria-label') || this.label || '';
|
||||
|
||||
this.shadowRoot.innerHTML = `
|
||||
<input
|
||||
aria-label="${ariaLabel}"
|
||||
${ariaInvalid}
|
||||
${ariaDescribedBy}
|
||||
/>
|
||||
${hasError ? `<p id="${errorId}" role="alert">${this.getAttribute('error')}</p>` : ''}
|
||||
`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Reference**: `/admin-ui/js/components/ds-input.js:172-240`
|
||||
|
||||
---
|
||||
|
||||
## 3. Web Component Lifecycle Management
|
||||
|
||||
Memory leaks from accumulated event listeners are a critical performance issue in SPAs. Web Components must properly manage their lifecycle.
|
||||
|
||||
### Guidelines
|
||||
|
||||
- **Connection/Disconnection**
|
||||
- Implement both `connectedCallback()` and `disconnectedCallback()`
|
||||
- `connectedCallback()`: Create shadow DOM, render, attach listeners
|
||||
- `disconnectedCallback()`: Remove all listeners, clean up references
|
||||
|
||||
- **Event Listener Storage**
|
||||
- Store listener references on `this` for later cleanup
|
||||
- Never attach anonymous functions without storing references
|
||||
- Example: `this.clickHandler = (e) => { ... }`
|
||||
- Later: `element.removeEventListener('click', this.clickHandler)`
|
||||
|
||||
- **Attribute Observation**
|
||||
- Implement `observedAttributes()` static getter
|
||||
- Include all attributes that affect rendering: `disabled`, `tabindex`, ARIA attributes
|
||||
- In `attributeChangedCallback()`, cleanup old listeners before re-rendering
|
||||
|
||||
### Implementation Pattern
|
||||
|
||||
```javascript
|
||||
class MyComponent extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ['disabled', 'tabindex', 'aria-label'];
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.cleanupEventListeners();
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Store references
|
||||
this.clickHandler = (e) => { /* ... */ };
|
||||
this.focusHandler = (e) => { /* ... */ };
|
||||
|
||||
const button = this.shadowRoot.querySelector('button');
|
||||
button.addEventListener('click', this.clickHandler);
|
||||
this.addEventListener('focus', this.focusHandler);
|
||||
}
|
||||
|
||||
cleanupEventListeners() {
|
||||
const button = this.shadowRoot?.querySelector('button');
|
||||
if (button && this.clickHandler) {
|
||||
button.removeEventListener('click', this.clickHandler);
|
||||
delete this.clickHandler;
|
||||
}
|
||||
if (this.focusHandler) {
|
||||
this.removeEventListener('focus', this.focusHandler);
|
||||
delete this.focusHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Reference**: `/admin-ui/js/components/ds-button.js:27-113`
|
||||
|
||||
---
|
||||
|
||||
## 4. Event Delegation & Listener Accumulation Prevention
|
||||
|
||||
High-volume event listeners create memory pressure. Use event delegation and guard flags.
|
||||
|
||||
### Guidelines
|
||||
|
||||
- **Use Document-Level Delegation**
|
||||
- Instead of: `buttons.forEach(b => b.addEventListener('click', handler))`
|
||||
- Do: `document.addEventListener('click', (e) => { const btn = e.target.closest('[data-action]'); ... })`
|
||||
- Benefit: One listener for N elements, regardless of count
|
||||
|
||||
- **Guard Flags**
|
||||
- Prevent re-attachment on re-renders
|
||||
- Check flag before attaching: `if (!this.listeners.hasActionListener) { ... }`
|
||||
- Set flag after attachment: `this.listeners.hasActionListener = true;`
|
||||
|
||||
- **Data-Driven Actions**
|
||||
- Use `data-action` attributes instead of inline onclick handlers
|
||||
- Router actions through a single switch statement
|
||||
- Example: `<button data-action="toggle-theme">Toggle</button>`
|
||||
|
||||
### Implementation Pattern
|
||||
|
||||
```javascript
|
||||
attachEventHandlers() {
|
||||
// Guard: only attach once, even if called multiple times
|
||||
if (!this.listeners.hasActionListener) {
|
||||
document.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('[data-action]');
|
||||
if (!btn) return;
|
||||
|
||||
const action = btn.dataset.action;
|
||||
switch (action) {
|
||||
case 'toggle-theme': this.toggleTheme(); break;
|
||||
case 'load-audit': this.loadAuditLog(); break;
|
||||
// ... more actions
|
||||
}
|
||||
});
|
||||
|
||||
this.listeners.hasActionListener = true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Reference**: `/admin-ui/js/core/app.js:2202-2414`
|
||||
|
||||
---
|
||||
|
||||
## 5. Security: No Global State Pollution
|
||||
|
||||
Never expose critical classes or functions to `window`. Always use ES6 module imports.
|
||||
|
||||
### Guidelines
|
||||
|
||||
- **No Window Assignments**
|
||||
- ❌ `window.app = app;`
|
||||
- ❌ `window.themeManager = themeManager;`
|
||||
- ✅ Use: `import app from './core/app.js';`
|
||||
|
||||
- **Risk Mitigation**
|
||||
- Malicious/poorly-written third-party scripts can't hijack your code
|
||||
- Prevents accidental global namespace pollution
|
||||
- Makes code dependencies explicit and traceable
|
||||
|
||||
- **Development Debugging**
|
||||
- If debugging requires window access, check environment first
|
||||
- Example: Only expose in development mode
|
||||
- Use: `if (process.env.NODE_ENV === 'development') { window.debug = {...}; }`
|
||||
|
||||
### Implementation
|
||||
|
||||
```javascript
|
||||
// ❌ DON'T DO THIS
|
||||
const app = new App();
|
||||
window.app = app;
|
||||
|
||||
// ✅ DO THIS
|
||||
const app = new App();
|
||||
export default app;
|
||||
|
||||
// In other files:
|
||||
import app from './app.js';
|
||||
```
|
||||
|
||||
**Reference**: `/admin-ui/js/core/app.js` (removed window exposure), `/admin-ui/index.html:750`
|
||||
|
||||
---
|
||||
|
||||
## 6. Security: Sanitize HTML Input
|
||||
|
||||
Always sanitize HTML before insertion into the DOM. Use DOMPurify.
|
||||
|
||||
### Guidelines
|
||||
|
||||
- **Never Use Unsafe Insertion**
|
||||
- ❌ `element.innerHTML = userInput;`
|
||||
- ❌ `element.innerHTML = apiResponse;`
|
||||
- ✅ Use: `sanitizeHtml()` and `setSafeHtml()`
|
||||
|
||||
- **DOMPurify Configuration**
|
||||
- Default settings block dangerous attributes (onclick, onerror, etc.)
|
||||
- Whitelist only necessary tags and attributes
|
||||
- Strip scripts while preserving content
|
||||
|
||||
- **Content Sources Requiring Sanitization**
|
||||
- User input (forms, search, comments)
|
||||
- API responses (markdown, rich text, HTML)
|
||||
- External data (imported content, integrations)
|
||||
|
||||
### Implementation
|
||||
|
||||
```javascript
|
||||
import { setSafeHtml, sanitizeHtml } from './sanitizer.js';
|
||||
|
||||
// Safe HTML insertion
|
||||
const htmlContent = `<h2>${name}</h2><p>${description}</p>`;
|
||||
setSafeHtml(element, htmlContent);
|
||||
|
||||
// Sanitize and retrieve
|
||||
const clean = sanitizeHtml(userInput);
|
||||
```
|
||||
|
||||
**Reference**: `/admin-ui/js/core/sanitizer.js`, `/admin-ui/js/core/app.js:28`
|
||||
|
||||
---
|
||||
|
||||
## 7. Non-Invasive Network Monitoring
|
||||
|
||||
Never monkey-patch native APIs. Use non-invasive alternatives.
|
||||
|
||||
### Guidelines
|
||||
|
||||
- **Don't Monkey-Patch Global Functions**
|
||||
- ❌ `window.fetch = async (...) => { ... }`
|
||||
- ❌ Breaks third-party integrations
|
||||
- ❌ Causes race conditions
|
||||
|
||||
- **Use Platform APIs Instead**
|
||||
- ✅ `PerformanceObserver` for network monitoring
|
||||
- ✅ Browser Performance API for metrics
|
||||
- ✅ Event listeners on `window` for errors
|
||||
|
||||
- **Graceful Degradation**
|
||||
- Check if API is available: `if ('PerformanceObserver' in window)`
|
||||
- Try/catch for unsupported browsers
|
||||
- Continue functionality if monitoring unavailable
|
||||
|
||||
### Implementation
|
||||
|
||||
```javascript
|
||||
captureNetworkActivity() {
|
||||
if ('PerformanceObserver' in window) {
|
||||
try {
|
||||
const observer = new PerformanceObserver((list) => {
|
||||
for (const entry of list.getEntries()) {
|
||||
if (entry.initiatorType === 'fetch' || entry.initiatorType === 'xmlhttprequest') {
|
||||
this.log('network', entry.initiatorType, `${entry.name}`, {
|
||||
duration: entry.duration,
|
||||
transferSize: entry.transferSize
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
observer.observe({ entryTypes: ['resource'] });
|
||||
} catch (e) {
|
||||
// Graceful degradation - network logging won't work
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Reference**: `/admin-ui/js/core/browser-logger.js:134-162`
|
||||
|
||||
---
|
||||
|
||||
## 8. Focus Management
|
||||
|
||||
Maintain user's context by managing focus properly during UI updates.
|
||||
|
||||
### Guidelines
|
||||
|
||||
- **Focus Delegation**
|
||||
- Web Components should delegate focus to internal focusable element
|
||||
- Implement focus() method that focuses internal input/button
|
||||
|
||||
- **Focus Restoration**
|
||||
- Store focused element before UI change
|
||||
- Restore focus after update if appropriate
|
||||
- Announce focus changes to screen readers
|
||||
|
||||
- **Aria-Expanded Updates**
|
||||
- Update `aria-expanded` when toggles change state
|
||||
- Ensures screen readers announce state changes
|
||||
- Example: `toggleBtn.setAttribute('aria-expanded', isOpen);`
|
||||
|
||||
### Implementation
|
||||
|
||||
```javascript
|
||||
class MyComponent extends HTMLElement {
|
||||
focus() {
|
||||
// Delegate focus to internal input
|
||||
this.shadowRoot.querySelector('input')?.focus();
|
||||
}
|
||||
|
||||
togglePanel() {
|
||||
const isOpen = !this.isOpen;
|
||||
this.isOpen = isOpen;
|
||||
|
||||
// Update ARIA state
|
||||
this.setAttribute('aria-expanded', isOpen);
|
||||
|
||||
// Restore focus to toggle button after panel opens
|
||||
if (isOpen) {
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Reference**: `/admin-ui/js/core/app.js:2495-2502`
|
||||
|
||||
---
|
||||
|
||||
## 9. Semantic HTML Over Custom Divs
|
||||
|
||||
Use native HTML elements. They provide built-in accessibility.
|
||||
|
||||
### Guidelines
|
||||
|
||||
- **Prefer Native Elements**
|
||||
- ✅ `<button>` instead of `<div role="button">`
|
||||
- ✅ `<input>` instead of `<div contenteditable>`
|
||||
- ✅ `<select>` instead of custom dropdown
|
||||
- ✅ `<label>` instead of span with onclick
|
||||
|
||||
- **When Custom Elements Are Necessary**
|
||||
- Implement proper ARIA roles and attributes
|
||||
- Ensure keyboard interaction works identically to native
|
||||
- Test with screen readers and keyboard-only navigation
|
||||
|
||||
- **Benefits of Native HTML**
|
||||
- Built-in keyboard support
|
||||
- Screen reader integration
|
||||
- Mobile device handling
|
||||
- Browser compatibility
|
||||
- User expectations met
|
||||
|
||||
---
|
||||
|
||||
## 10. Observable Attributes for Web Components
|
||||
|
||||
Web Components must expose all relevant attributes in `observedAttributes()` to respond to changes.
|
||||
|
||||
### Guidelines
|
||||
|
||||
- **Include All State Attributes**
|
||||
- `disabled`, `aria-label`, `aria-expanded`, `aria-pressed`
|
||||
- `tabindex`, `value`, `error`, `variant`
|
||||
- Any attribute that affects rendering or behavior
|
||||
|
||||
- **Cleanup in attributeChangedCallback**
|
||||
- Remove old listeners before re-rendering
|
||||
- Call `cleanupEventListeners()` first
|
||||
- Then call `render()` and `setupEventListeners()`
|
||||
|
||||
- **Re-render Efficiently**
|
||||
- Only re-render if shadowRoot has content
|
||||
- Check `if (this.shadowRoot.innerHTML)`
|
||||
- Avoid re-rendering on initial connection
|
||||
|
||||
### Implementation
|
||||
|
||||
```javascript
|
||||
class MyComponent extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
// Include ALL attributes that affect rendering
|
||||
return ['disabled', 'tabindex', 'aria-label', 'aria-expanded', 'aria-invalid', 'error'];
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldValue, newValue) {
|
||||
if (this.shadowRoot.innerHTML && oldValue !== newValue) {
|
||||
// Cleanup first
|
||||
this.cleanupEventListeners();
|
||||
// Then re-render
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Reference**: `/admin-ui/js/components/ds-input.js:39-52`
|
||||
|
||||
---
|
||||
|
||||
## 11. Logging with Organism Awareness
|
||||
|
||||
Use structured logging that helps understand system state and consciousness.
|
||||
|
||||
### Guidelines
|
||||
|
||||
- **Log Categories** (Organ Systems)
|
||||
- `'heart'` - Database operations and data persistence
|
||||
- `'brain'` - Validation, analysis, and decision making
|
||||
- `'nervous'` - API calls, webhooks, communication
|
||||
- `'digestive'` - Data ingestion, parsing, transformation
|
||||
- `'endocrine'` - Theme system and configuration
|
||||
- `'immune'` - Validation, error detection, security
|
||||
|
||||
- **Log Levels**
|
||||
- `DEBUG` - Deep thought, internal analysis
|
||||
- `INFO` - Organism awareness, normal operations
|
||||
- `WARN` - Symptom detection, unusual conditions
|
||||
- `ERROR` - Immune alert, critical threats
|
||||
|
||||
- **Structure**
|
||||
- Always include: timestamp, level, category, message, data
|
||||
- Use consistent naming for categories
|
||||
- Include relevant IDs (user, session, operation)
|
||||
|
||||
**Reference**: `/admin-ui/js/core/logger.js:27-152`
|
||||
|
||||
---
|
||||
|
||||
## 12. Error Handling & User Feedback
|
||||
|
||||
Provide clear feedback for errors while maintaining security.
|
||||
|
||||
### Guidelines
|
||||
|
||||
- **User-Facing Messages**
|
||||
- Be specific but not technical: "Unable to save changes" not "500 Internal Server Error"
|
||||
- Suggest action: "Please try again" or "Contact support"
|
||||
- Announce errors to screen readers with `role="alert"`
|
||||
|
||||
- **Internal Logging**
|
||||
- Log full error details internally for debugging
|
||||
- Include stack traces, request data, timestamps
|
||||
- Never expose sensitive information to users
|
||||
|
||||
- **Notification Timing**
|
||||
- Success: 3-5 seconds
|
||||
- Warnings: 5-7 seconds
|
||||
- Errors: Persistent until user dismisses
|
||||
|
||||
**Reference**: `/admin-ui/js/core/messaging.js`
|
||||
|
||||
---
|
||||
|
||||
## Summary: Accessibility Checklist
|
||||
|
||||
Before shipping a component, verify:
|
||||
|
||||
- [ ] All interactive elements have `tabindex="0"` or semantic focus
|
||||
- [ ] All icon buttons have `aria-label`
|
||||
- [ ] All toggles/collapsibles have `aria-expanded`
|
||||
- [ ] All form errors have `aria-invalid` and `aria-describedby`
|
||||
- [ ] `:focus-visible` CSS is present for keyboard users
|
||||
- [ ] Keyboard shortcuts work (Enter, Space, Escape, Arrows)
|
||||
- [ ] `disconnectedCallback()` removes all event listeners
|
||||
- [ ] Event listeners are stored as `this.handler` for cleanup
|
||||
- [ ] No HTML is inserted via `innerHTML` without sanitization
|
||||
- [ ] No functions/classes exposed to `window`
|
||||
- [ ] Decorative SVGs have `aria-hidden="true"`
|
||||
- [ ] Error messages have `role="alert"`
|
||||
- [ ] Uses semantic HTML where possible
|
||||
- [ ] Works with keyboard-only navigation
|
||||
- [ ] Works with screen readers (test with NVDA or JAWS)
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [WCAG 2.1 Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)
|
||||
- [ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/)
|
||||
- [Web Components Best Practices](https://web.dev/articles/custom-elements-best-practices)
|
||||
- [Focus Management](https://www.smashingmagazine.com/2019/06/focus-management-keyboard-navigation/)
|
||||
- [DOMPurify Documentation](https://github.com/cure53/DOMPurify)
|
||||
Reference in New Issue
Block a user