diff --git a/planning/theme-performance/README.md b/planning/theme-performance/README.md new file mode 100644 index 00000000..d6ca35df --- /dev/null +++ b/planning/theme-performance/README.md @@ -0,0 +1,290 @@ +# Theme System Performance Optimization + +## Executive Summary + +This planning folder contains comprehensive documentation for optimizing the theme system in `app/theme` to improve performance, reduce initial load times, enhance type safety, and minimize memory usage while maintaining full backward compatibility. + +## Analysis Results + +After thorough analysis of the theme system, we identified **10 specific performance optimization opportunities** that together will: + +- **Reduce initial load time by 20-30%** through lazy loading and code splitting +- **Reduce memory usage by 30-40%** through better caching and cleanup strategies +- **Reduce bundle size by 15-25%** through lazy loading and tree shaking +- **Maintain current performance targets** (< 50ms theme switch, < 1ms override resolution) +- **Improve type safety** with stricter TypeScript validation +- **Prevent memory leaks** with WeakMap-based caching + +## Documentation Structure + +### [requirements.md](./requirements.md) +Contains 10 user stories with detailed acceptance criteria: + +1. **Lazy-Load Theme Definitions** - Load themes on-demand instead of eagerly +2. **Optimize CSS Variable Generation** - Faster string operations and caching +3. **Cache Selector Parsing Results** - LRU cache for repeated selector parsing +4. **Optimize Runtime Resolver Index** - Pre-sorted overrides with efficient indexing +5. **Reduce Directive Overhead** - Context caching and consolidated watchers +6. **Optimize Theme Stylesheet Loading** - Parallel loading and URL caching +7. **Implement WeakMap-based Caching** - Automatic garbage collection +8. **Type-Safe Theme Definitions** - Build-time validation with TypeScript +9. **Batch DOM Operations** - Frame budgets for smooth 60fps rendering +10. **Precompute Theme Specificity** - Eliminate runtime calculations + +### [design.md](./design.md) +Detailed technical design including: + +- Architecture diagrams (current vs optimized) +- Code implementation strategies for each optimization +- Data model updates +- Error handling approaches +- Testing strategies +- Performance monitoring plans +- Migration guide + +### [tasks.md](./tasks.md) +Complete implementation checklist with: + +- 100+ specific subtasks organized by optimization +- File-by-file change descriptions +- Requirement mappings +- Testing checklist +- Documentation tasks +- 3-week implementation schedule +- Success metrics + +## Key Optimizations Explained + +### High Priority (Must Have) + +**1. Lazy-Load Theme Definitions** +- Currently all themes load eagerly, increasing bundle size +- Switch to on-demand loading with intelligent caching +- Keep only active + default themes in memory +- Expected: 20%+ bundle size reduction, 30-40% memory reduction + +**2. Optimize Runtime Resolver Index** +- Ensure overrides are pre-sorted by specificity +- Use Map-based indexing by component type +- Track attribute-dependent overrides separately +- Expected: Maintain < 1ms resolution time with better cache hits + +**3. Reduce Directive Overhead** +- Cache context detection results with WeakMap +- Use Set for O(1) component checks +- Consolidate watchers (already done in 92.theme-lazy-sync) +- Expected: 25% faster initialization, 40% less memory per directive + +**4. Implement WeakMap-based Caching** +- Use WeakMaps for component-scoped caches +- Automatic garbage collection on unmount +- Prevent memory leaks from long-lived references +- Expected: Eliminate memory leaks, automatic cleanup + +### Medium Priority (Should Have) + +**5. Optimize CSS Variable Generation** +- Cache kebab-case conversions +- Use array joining instead of string concatenation +- Avoid duplicate processing in dark mode +- Expected: 30% faster generation, 40% fewer allocations + +**6. Cache Selector Parsing Results** +- Implement LRU cache with 1000 entry limit +- Persist cache across theme switches +- Add monitoring for cache effectiveness +- Expected: < 0.1ms for cached selectors, 80%+ hit rate + +**7. Optimize Theme Stylesheet Loading** +- Parallelize URL resolution and loading +- Cache resolved URLs +- Better error handling +- Expected: 35% faster loading through parallelization + +**8. Batch DOM Operations** +- Already implemented with 5ms frame budget +- Verify optimal batching strategy +- Ensure no layout thrashing +- Expected: Maintain 50+ fps during operations + +### Low Priority (Nice to Have) + +**9. Type-Safe Theme Definitions** +- Stricter TypeScript types for color palettes +- Build-time validation +- Better error messages +- Expected: Catch errors at build time, better DX + +**10. Precompute Theme Specificity** +- Already mostly implemented +- Verify runtime calculations eliminated +- Add build-time validation +- Expected: Near-zero sorting time + +## Performance Targets + +| Metric | Current | Target | Improvement | +|--------|---------|--------|-------------| +| Initial Load Time | Baseline | -20-30% | Lazy loading | +| Memory Usage | Baseline | -30-40% | Better caching | +| Bundle Size | Baseline | -15-25% | Code splitting | +| Theme Switch | < 50ms | < 50ms | Maintained | +| Override Resolution | < 1ms | < 0.5ms | Optimized | +| CSS Generation | Baseline | -30% | Caching | +| Stylesheet Loading | Baseline | -35% | Parallelization | +| Frame Rate | Baseline | 50+ fps | Batching | +| Cache Hit Rate | Variable | 80%+ | LRU caching | + +## Implementation Strategy + +### Phase 1: Core Optimizations (Week 1) +- Lazy loading +- Selector caching +- WeakMap caching +- CSS generation optimization +- Stylesheet loading optimization + +### Phase 2: Runtime Optimization (Week 2) +- Resolver index optimization +- Directive overhead reduction +- DOM batching verification +- Type safety improvements +- Specificity precomputation + +### Phase 3: Testing & Documentation (Week 3) +- Comprehensive testing +- Performance benchmarking +- Memory leak testing +- Documentation updates +- Release preparation + +## Backward Compatibility + +**All optimizations maintain full backward compatibility:** + +✅ Existing theme definitions work unchanged +✅ Public APIs remain stable +✅ All existing tests pass +✅ No breaking changes to behavior +✅ Theme authoring stays simple + +## Current System Architecture + +The theme system consists of: + +1. **Build-time compilation** - Themes defined in `app/theme/*/theme.ts` +2. **Runtime loading** - Theme plugin (`90.theme.client.ts`) +3. **Override resolution** - RuntimeResolver class +4. **Directive application** - `v-theme` directive (`91.auto-theme.client.ts`) +5. **Reactive updates** - Global watcher (`92.theme-lazy-sync.client.ts`) + +### Key Files Analyzed + +``` +app/theme/ +├── _shared/ +│ ├── types.ts # Core type definitions +│ ├── define-theme.ts # Theme authoring API +│ ├── compiler-core.ts # Selector parsing & specificity +│ ├── runtime-compile.ts # Runtime compilation +│ ├── runtime-resolver.ts # Override resolution +│ ├── generate-css-variables.ts # CSS variable generation +│ ├── css-selector-runtime.ts # DOM class application +│ └── theme-manifest.ts # Theme discovery & loading +├── retro/ # Example theme +│ └── theme.ts +└── blank/ # Minimal theme + └── theme.ts + +app/plugins/ +├── 90.theme.client.ts # Main theme plugin +├── 91.auto-theme.client.ts # v-theme directive +└── 92.theme-lazy-sync.client.ts # Global watcher + +app/composables/ +└── useThemeResolver.ts # Composable API +``` + +## Testing Strategy + +### Unit Tests +- LRU cache behavior +- Selector parsing +- CSS generation +- Override resolution +- Type validation + +### Integration Tests +- Theme switching +- Memory leak detection +- Performance benchmarks +- Compatibility checks + +### End-to-End Tests +- Full theme switch flow +- Multi-device testing +- Browser compatibility +- Real-world usage patterns + +## Success Metrics + +Implementation will be considered successful when: + +1. ✅ All existing tests pass without modification +2. ✅ Performance benchmarks show measurable improvements +3. ✅ Memory usage reduced by 30%+ +4. ✅ Initial load time reduced by 20%+ +5. ✅ Bundle size reduced by 15%+ +6. ✅ No regressions in functionality +7. ✅ Type errors caught at build time +8. ✅ Frame rate maintained above 50fps +9. ✅ Cache hit rates above 80% +10. ✅ Developer feedback positive + +## Risk Mitigation + +### Identified Risks + +1. **Breaking existing functionality** + - Mitigation: Comprehensive testing, incremental rollout, backward compatibility focus + +2. **Over-optimization complexity** + - Mitigation: Measure improvements, keep code readable, detailed comments + +3. **Cache invalidation issues** + - Mitigation: Clear strategies, thorough testing, monitoring + +4. **Browser compatibility** + - Mitigation: Multi-browser testing, progressive enhancement, fallbacks + +## Next Steps + +1. **Review** - Team reviews planning documents +2. **Approve** - Get sign-off on approach +3. **Implement** - Follow tasks.md checklist +4. **Test** - Run comprehensive test suite +5. **Benchmark** - Measure actual improvements +6. **Document** - Update user-facing docs +7. **Deploy** - Gradual rollout with monitoring +8. **Iterate** - Gather feedback, make adjustments + +## Contact & Questions + +For questions or clarifications about these optimizations, please: +- Review the detailed planning documents +- Check the implementation tasks checklist +- Consult the design document for technical details +- Refer to existing code comments and tests + +## Related Documentation + +- [Theme System README](../../app/theme/README.md) - Current system documentation +- [Architecture Planning](../../planning/refined-theme-system/) - Original system design +- [Test Coverage](../../app/theme/_shared/__tests__/) - Existing test suite + +--- + +**Status**: Planning Complete ✅ +**Last Updated**: 2025-12-03 +**Version**: 1.0 +**Author**: GitHub Copilot Performance Analysis diff --git a/planning/theme-performance/design.md b/planning/theme-performance/design.md new file mode 100644 index 00000000..0f27be51 --- /dev/null +++ b/planning/theme-performance/design.md @@ -0,0 +1,1184 @@ +# Theme System Performance Optimization - Technical Design + +## Overview + +This document provides the detailed technical design for implementing 10 performance optimizations to the theme system in `app/theme`. The optimizations focus on reducing initial load times, improving runtime performance, enhancing type safety, and minimizing memory usage while maintaining full backward compatibility with existing themes and APIs. + +The theme system is a sophisticated runtime that combines: +- Build-time theme compilation +- Runtime override resolution with CSS specificity +- Dynamic theme switching with reactive updates +- CSS selector-based styling with Tailwind classes +- Material Design 3 color palette generation + +## Architecture + +### Current System Flow + +```mermaid +graph TD + A[Theme Author] -->|defines| B[ThemeDefinition] + B -->|compiled at build| C[CompiledTheme] + C -->|loaded at runtime| D[RuntimeResolver] + D -->|resolves overrides| E[v-theme Directive] + E -->|applies styles| F[Components] + + G[Theme Plugin] -->|manages| C + G -->|provides| D + G -->|switches| H[Active Theme] + H -->|triggers| I[Reactive Updates] + I -->|re-applies| E +``` + +### Optimized System Flow + +```mermaid +graph TD + A[Theme Author] -->|defines| B[ThemeDefinition] + B -->|compiled at build| C[CompiledTheme w/ Precomputed Data] + C -->|lazy loaded| D[Theme Registry] + D -->|provides cached| E[RuntimeResolver w/ LRU Cache] + E -->|resolves once| F[Global Watcher] + F -->|batches updates| G[DOM Operations] + G -->|applies to| H[Components] + + I[Cleanup Manager] -->|removes unused| D + J[Cache Manager] -->|invalidates on switch| E +``` + +## Component Designs + +### 1. Lazy-Load Theme Definitions + +#### Implementation Strategy + +**Current State**: All themes are loaded eagerly during plugin initialization, increasing initial bundle size and memory usage. + +**Optimized State**: Themes are loaded on-demand with intelligent caching and cleanup. + +#### Code Changes + +**File**: `app/plugins/90.theme.client.ts` + +```typescript +// Add theme loading state tracking +interface ThemeLoadingState { + loading: Set; + loaded: Set; + errors: Map; +} + +const themeLoadingState: ThemeLoadingState = { + loading: new Set(), + loaded: new Set(), + errors: new Map(), +}; + +// Optimize loadTheme function +const loadTheme = async ( + themeName: string +): Promise => { + // Check if already loaded + if (themeRegistry.has(themeName)) { + return themeRegistry.get(themeName)!; + } + + // Check if currently loading (prevent duplicate requests) + if (themeLoadingState.loading.has(themeName)) { + // Wait for existing load to complete + return new Promise((resolve) => { + const interval = setInterval(() => { + if (themeLoadingState.loaded.has(themeName)) { + clearInterval(interval); + resolve(themeRegistry.get(themeName) || null); + } else if (themeLoadingState.errors.has(themeName)) { + clearInterval(interval); + resolve(null); + } + }, 50); + }); + } + + // Mark as loading + themeLoadingState.loading.add(themeName); + + try { + const manifestEntry = themeManifest.get(themeName); + if (!manifestEntry) { + throw new Error(`Theme "${themeName}" not found in manifest`); + } + + const themeModule = await manifestEntry.loader(); + if (!themeModule?.default) { + throw new Error(`Theme "${themeName}" has no default export`); + } + + const definition = themeModule.default; + updateManifestEntry(manifestEntry, definition); + + // Parallel asset loading + const [_, themeIcons] = await Promise.all([ + loadThemeStylesheets(manifestEntry, definition.stylesheets), + loadThemeIcons(manifestEntry, definition.icons), + ]); + + const compiledTheme: CompiledTheme = { + name: definition.name, + isDefault: manifestEntry.isDefault, + stylesheets: manifestEntry.stylesheets, + displayName: definition.displayName, + description: definition.description, + cssVariables: generateThemeCssVariables(definition), + overrides: compileOverridesRuntime(definition.overrides || {}), + cssSelectors: definition.cssSelectors, + hasStyleSelectors: manifestEntry.hasCssSelectorStyles, + ui: definition.ui, + propMaps: definition.propMaps, + backgrounds: definition.backgrounds, + icons: themeIcons, + }; + + themeRegistry.set(themeName, compiledTheme); + themeLoadingState.loaded.add(themeName); + + // Register icons + if (compiledTheme.icons) { + iconRegistry.registerTheme(themeName, compiledTheme.icons); + } + + // Cache app config + const themeSpecificConfig = await loadThemeAppConfig(manifestEntry); + themeAppConfigOverrides.set(themeName, themeSpecificConfig ?? null); + + // Create resolver + const resolver = new RuntimeResolver(compiledTheme); + resolverRegistry.set(themeName, resolver); + + return compiledTheme; + } catch (error) { + themeLoadingState.errors.set(themeName, error as Error); + if (import.meta.dev) { + console.warn(`[theme] Failed to load theme "${themeName}":`, error); + } + return null; + } finally { + themeLoadingState.loading.delete(themeName); + } +}; + +// Helper to load theme icons +async function loadThemeIcons( + manifestEntry: ThemeManifestEntry, + icons?: Record +): Promise | null> { + if (icons) return icons; + + if (manifestEntry.iconsLoader) { + try { + const module = await manifestEntry.iconsLoader(); + return module?.default || module || null; + } catch (error) { + if (import.meta.dev) { + console.warn( + `[theme] Failed to load icons for theme "${manifestEntry.name}":`, + error + ); + } + } + } + + return null; +} +``` + +**Requirements**: 1.1, 1.2, 1.3, 1.4 + +### 2. Optimize CSS Variable Generation + +#### Implementation Strategy + +**Current State**: CSS variable generation uses string concatenation in loops and recomputes kebab-case conversions repeatedly. + +**Optimized State**: Use array joining, cache kebab-case conversions, and avoid duplicate processing. + +#### Code Changes + +**File**: `app/theme/_shared/generate-css-variables.ts` + +```typescript +// Optimize kebab-case cache (already exists, but ensure it's used) +const kebabCache = new Map(); + +function kebab(str: string): string { + const cached = kebabCache.get(str); + if (cached) return cached; + + // Optimized: single pass with array + const chars: string[] = []; + for (let i = 0; i < str.length; i++) { + const char = str[i]; + if (char >= 'A' && char <= 'Z') { + chars.push('-', char.toLowerCase()); + } else { + chars.push(char); + } + } + + const result = chars.join(''); + kebabCache.set(str, result); + return result; +} + +// Optimize buildPalette to avoid unnecessary iterations +function buildPalette(colors: ColorPalette): Record { + const entries: Record = {}; + const keys = Object.keys(colors); + + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + if (key === 'dark') continue; + + const value = colors[key as keyof ColorPalette]; + if (typeof value !== 'string') continue; + + const varName = `--md-${kebab(key)}`; + entries[varName] = value; + } + + return entries; +} + +// Optimize CSS block generation with array joining +function toCssBlock( + themeName: string, + vars: Record, + dark: boolean +): string { + const selector = dark + ? `html[data-theme="${themeName}"].dark, .dark html[data-theme="${themeName}"]` + : `html[data-theme="${themeName}"]`; + + const lines: string[] = [`${selector} {`]; + const keys = Object.keys(vars); + + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + lines.push(` ${key}: ${vars[key]};`); + } + + lines.push('}'); + return lines.join('\n'); +} + +// Optimize dark mode override generation +export function generateThemeCssVariables(def: ThemeDefinition): string { + const light = buildPalette(def.colors); + applyFontVars(light, def.fonts); + + if (def.borderWidth) { + light['--md-border-width'] = def.borderWidth; + } + if (def.borderRadius) { + light['--md-border-radius'] = def.borderRadius; + } + + // Only build dark overrides if there are dark colors + let darkOverrides: Record = {}; + if (def.colors.dark && Object.keys(def.colors.dark).length > 0) { + darkOverrides = buildPalette(def.colors.dark as ColorPalette); + applyFontVars(darkOverrides, def.fonts?.dark); + + // Only add border styles if we have dark overrides + if (def.borderWidth) { + darkOverrides['--md-border-width'] = def.borderWidth; + } + if (def.borderRadius) { + darkOverrides['--md-border-radius'] = def.borderRadius; + } + } + + const blocks: string[] = [toCssBlock(def.name, light, false)]; + if (Object.keys(darkOverrides).length > 0) { + blocks.push(toCssBlock(def.name, darkOverrides, true)); + } + + return blocks.join('\n'); +} +``` + +**Requirements**: 2.1, 2.2, 2.3 + +### 3. Cache Selector Parsing Results + +#### Implementation Strategy + +**Current State**: A selector cache exists but may not be optimally sized or managed. + +**Optimized State**: Add size limits, better eviction strategy, and monitoring. + +#### Code Changes + +**File**: `app/theme/_shared/compiler-core.ts` + +```typescript +// Enhanced cache with size limit and LRU eviction +class LRUSelectorCache { + private cache: Map; + private readonly maxSize: number; + + constructor(maxSize: number = 1000) { + this.cache = new Map(); + this.maxSize = maxSize; + } + + get(key: string): ParsedSelector | undefined { + const value = this.cache.get(key); + if (value !== undefined) { + // Move to end (most recently used) + this.cache.delete(key); + this.cache.set(key, value); + } + return value; + } + + set(key: string, value: ParsedSelector): void { + // Delete if exists to re-insert at end + if (this.cache.has(key)) { + this.cache.delete(key); + } else if (this.cache.size >= this.maxSize) { + // Evict oldest + const firstKey = this.cache.keys().next().value; + if (firstKey !== undefined) { + this.cache.delete(firstKey); + } + } + + this.cache.set(key, value); + } + + clear(): void { + this.cache.clear(); + } + + get size(): number { + return this.cache.size; + } +} + +const selectorCache = new LRUSelectorCache(1000); + +export function parseSelector(selector: string): ParsedSelector { + const cached = selectorCache.get(selector); + if (cached) { + return cached; + } + + // ... existing parsing logic ... + + const result = { + component, + context, + identifier, + state, + attributes: attributes.length > 0 ? attributes : undefined, + }; + + selectorCache.set(selector, result); + return result; +} + +// Export cache stats for monitoring (dev mode only) +export function getSelectorCacheStats() { + if (!import.meta.dev) return null; + return { + size: selectorCache.size, + maxSize: 1000, + }; +} +``` + +**Requirements**: 3.1, 3.2, 3.3, 3.4 + +### 4. Optimize Runtime Resolver Index + +#### Implementation Strategy + +**Current State**: The RuntimeResolver already uses an index, but can be optimized further. + +**Optimized State**: Improve indexing structure and ensure overrides are pre-sorted. + +#### Code Changes + +**File**: `app/theme/_shared/runtime-resolver.ts` + +```typescript +export class RuntimeResolver { + private overrides: CompiledOverride[]; + private overrideIndex: Map; + private propMaps: PropClassMaps; + private themeName: string; + private cache: LRUCache; + private componentsWithAttributes: Set; + + constructor(compiledTheme: CompiledTheme) { + // Ensure overrides are sorted by specificity (should already be from build) + // Sort is stable, so relative order is preserved for equal specificity + this.overrides = [...compiledTheme.overrides].sort( + (a, b) => b.specificity - a.specificity + ); + + this.overrideIndex = new Map(); + this.componentsWithAttributes = new Set(); + + // Build index and track attribute-dependent components + for (const override of this.overrides) { + const key = override.component; + + let list = this.overrideIndex.get(key); + if (!list) { + list = []; + this.overrideIndex.set(key, list); + } + list.push(override); + + // Track components with attribute matchers + if (override.attributes && override.attributes.length > 0) { + this.componentsWithAttributes.add(key); + } + } + + this.propMaps = { + ...defaultPropMaps, + ...(compiledTheme.propMaps || {}), + }; + + this.themeName = compiledTheme.name; + + // Use LRU cache to prevent unbounded growth + this.cache = new LRUCache(100); + } + + // ... rest of the resolver implementation ... +} +``` + +**Requirements**: 4.1, 4.2, 4.3, 4.4 + +### 5. Reduce Directive Overhead + +#### Implementation Strategy + +**Current State**: The v-theme directive creates individual watchers and performs repeated computations. + +**Optimized State**: Use global watcher, memoize context detection, optimize component checks. + +#### Code Changes + +**File**: `app/plugins/91.auto-theme.client.ts` + +```typescript +// Context detection cache using WeakMap +const contextCache = new WeakMap(); + +function detectContext(el: HTMLElement): string { + // Check cache first + const cached = contextCache.get(el); + if (cached) return cached; + + // Detect context + let context = 'global'; + if ( + el.closest('#app-chat-container') || + el.closest('[data-context="chat"]') + ) { + context = 'chat'; + } else if ( + el.closest('#app-sidebar') || + el.closest('[data-context="sidebar"]') + ) { + context = 'sidebar'; + } else if ( + el.closest('#app-dashboard-modal') || + el.closest('[data-context="dashboard"]') + ) { + context = 'dashboard'; + } else if ( + el.closest('#app-header') || + el.closest('[data-context="header"]') + ) { + context = 'header'; + } + + // Cache the result + contextCache.set(el, context); + return context; +} + +// Optimize NUXT_UI_COMPONENTS check - already uses Set, which is good + +// Use global watcher instead of per-directive watchers +// This is already implemented in 92.theme-lazy-sync.client.ts +// which uses $forceUpdate to re-render components on theme change +``` + +**File**: `app/plugins/92.theme-lazy-sync.client.ts` + +```typescript +// Ensure this file uses efficient batching +export default defineNuxtPlugin((nuxtApp) => { + const themePlugin = nuxtApp.$theme as ThemePlugin; + + if (!themePlugin?.resolversVersion) { + return; + } + + // Single watcher for all theme changes + watch( + () => themePlugin.resolversVersion.value, + () => { + // Batch all component updates in next tick + nextTick(() => { + // Force update root instance to cascade to children + const app = nuxtApp.vueApp; + const rootInstance = app._instance; + if (rootInstance) { + rootInstance.update(); + } + }); + } + ); +}); +``` + +**Requirements**: 5.1, 5.2, 5.3, 5.4 + +### 6. Optimize Theme Stylesheet Loading + +#### Implementation Strategy + +**Current State**: Stylesheets are loaded with Promise.all, which is good, but URL resolution can be optimized. + +**Optimized State**: Cache stylesheet URLs and optimize loading logic. + +#### Code Changes + +**File**: `app/theme/_shared/theme-manifest.ts` + +```typescript +// Add URL resolution cache +const stylesheetUrlCache = new Map(); + +export async function resolveThemeStylesheetHref( + stylesheet: string, + entry: ThemeManifestEntry +): Promise { + // Check cache first + const cacheKey = `${entry.name}:${stylesheet}`; + const cached = stylesheetUrlCache.get(cacheKey); + if (cached) return cached; + + const trimmed = stylesheet.trim(); + const isExternal = + /^(?:[a-z]+:)?\/\//i.test(trimmed) || + trimmed.startsWith('data:') || + trimmed.startsWith('blob:'); + + // Try resolving via emitted asset URL first + const moduleKeyCandidates = new Set(); + + if (trimmed.startsWith('~/theme/')) { + moduleKeyCandidates.add(`../${trimmed.slice('~/theme/'.length)}`); + } + + if (trimmed.startsWith('./')) { + moduleKeyCandidates.add(`../${entry.dirName}/${trimmed.slice(2)}`); + } + + if ( + !isExternal && + !trimmed.startsWith('~/') && + !trimmed.startsWith('./') && + !trimmed.startsWith('../') + ) { + moduleKeyCandidates.add(`../${entry.dirName}/${trimmed}`); + } + + for (const key of moduleKeyCandidates) { + const loader = stylesheetModules[key]; + if (loader) { + try { + const href = await loader(); + if (typeof href === 'string' && href.length > 0) { + // Cache the result + stylesheetUrlCache.set(cacheKey, href); + return href; + } + } catch (error) { + if (import.meta.dev) { + console.warn( + `[theme] Failed to resolve stylesheet module "${key}" for theme "${entry.name}".`, + error + ); + } + } + } + } + + // Fallback resolution + let resolved: string; + if (trimmed.startsWith('~/')) { + resolved = trimmed.replace(/^~\//, '/'); + } else if (trimmed.startsWith('./')) { + resolved = `/theme/${entry.dirName}/${trimmed.slice(2)}`; + } else { + resolved = trimmed; + } + + // Cache fallback result + stylesheetUrlCache.set(cacheKey, resolved); + return resolved; +} + +// Optimize loadThemeStylesheets with better duplicate checking +export async function loadThemeStylesheets( + entry: ThemeManifestEntry, + overrideList?: string[] +): Promise { + const stylesheets = overrideList ?? entry.stylesheets; + + if (stylesheets.length === 0 || typeof document === 'undefined') { + return; + } + + // Resolve all URLs first (parallel) + const urlPromises = stylesheets.map(stylesheet => + resolveThemeStylesheetHref(stylesheet, entry) + ); + + const urls = await Promise.all(urlPromises); + + // Then load all stylesheets in parallel + const loadPromises = urls.map((href) => { + if (!href) return Promise.resolve(); + + // Check if already loaded + const existingLink = document.querySelector( + `link[data-theme-stylesheet="${entry.name}"][href="${href}"]` + ); + + if (existingLink) return Promise.resolve(); + + return new Promise((resolve) => { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = href; + link.setAttribute('data-theme-stylesheet', entry.name); + + link.onload = () => resolve(); + link.onerror = () => { + if (import.meta.dev) { + console.warn( + `[theme] Failed to load stylesheet "${href}" for theme "${entry.name}".` + ); + } + resolve(); + }; + + document.head.appendChild(link); + }); + }); + + await Promise.all(loadPromises); +} +``` + +**Requirements**: 6.1, 6.2, 6.3, 6.4, 6.5 + +### 7. Implement WeakMap-based Caching + +#### Implementation Strategy + +**Current State**: Some caches use regular Maps which can cause memory leaks. + +**Optimized State**: Use WeakMaps for component-scoped caches. + +#### Code Changes + +**File**: `app/composables/useThemeResolver.ts` + +```typescript +// Already uses WeakMap for componentOverrideCache - this is correct +const componentOverrideCache = new WeakMap< + ComponentPublicInstance, + ComponentOverrideCache +>(); + +// Ensure this pattern is used consistently throughout +``` + +**File**: `app/plugins/91.auto-theme.client.ts` + +```typescript +// Use WeakMap for context cache +const contextCache = new WeakMap(); + +// Ensure directive cleanup removes references +// (WeakMap handles this automatically) +``` + +**Requirements**: 7.1, 7.2, 7.3, 7.4 + +### 8. Type-Safe Theme Definitions + +#### Implementation Strategy + +**Current State**: Theme definitions have runtime validation but could be more type-safe at authoring time. + +**Optimized State**: Add stricter TypeScript types and build-time validation. + +#### Code Changes + +**File**: `app/theme/_shared/types.ts` + +```typescript +// Add stricter color palette typing +export interface StrictColorPalette extends BaseColorPalette { + // Make required fields explicit + primary: string; + secondary: string; + surface: string; + + // Ensure dark mode has correct types + dark?: Partial & { + primary?: string; + secondary?: string; + surface?: string; + }; +} + +// Add validated theme definition type +export interface ValidatedThemeDefinition extends ThemeDefinition { + name: string; + colors: StrictColorPalette; + + // Make overrides type-safe with known component names + overrides?: Record; +} + +// Add type guard for validation +export function isValidThemeDefinition( + theme: unknown +): theme is ValidatedThemeDefinition { + if (typeof theme !== 'object' || theme === null) return false; + + const t = theme as Partial; + + if (!t.name || typeof t.name !== 'string') return false; + if (!t.colors || typeof t.colors !== 'object') return false; + if (!t.colors.primary || !t.colors.secondary || !t.colors.surface) return false; + + return true; +} +``` + +**Requirements**: 8.1, 8.2, 8.3, 8.4, 8.5 + +### 9. Batch DOM Operations + +#### Implementation Strategy + +**Current State**: DOM operations are already batched with 5ms chunks in css-selector-runtime.ts + +**Optimized State**: Ensure batching is optimal and add frame budget monitoring. + +#### Code Changes + +**File**: `app/theme/_shared/css-selector-runtime.ts` + +```typescript +// The current implementation already batches operations well +// Ensure we're using the most efficient approach + +export function applyThemeClasses( + themeName: string, + selectors: Record +): void { + const entries = Object.entries(selectors); + if (entries.length === 0) return; + + let index = 0; + const FRAME_BUDGET_MS = 5; // Keep UI responsive at 60fps + + const processChunk = () => { + const start = performance.now(); + + while (index < entries.length && performance.now() - start < FRAME_BUDGET_MS) { + const entry = entries[index++]; + if (!entry) break; + + const [selector, config] = entry; + if (!config.class) continue; + + const classes = config.class.split(/\s+/).filter(Boolean); + if (classes.length === 0) continue; + + try { + const elements = document.querySelectorAll(selector); + + // Batch classList operations per element + elements.forEach((element) => { + if (!(element instanceof HTMLElement)) return; + + if (!classApplicationCache.has(element)) { + classApplicationCache.set(element, new Set()); + } + + const applied = classApplicationCache.get(element)!; + const newClasses = classes.filter(c => !applied.has(c)); + + if (newClasses.length > 0) { + // Single classList.add call with multiple classes + element.classList.add(...newClasses); + newClasses.forEach(c => applied.add(c)); + } + }); + } catch (error) { + if (import.meta.dev) { + console.warn( + `[theme] Invalid CSS selector: "${selector}"`, + error + ); + } + } + } + + if (index < entries.length) { + // Use requestAnimationFrame for better frame timing + if (typeof requestAnimationFrame !== 'undefined') { + requestAnimationFrame(processChunk); + } else { + setTimeout(processChunk, 0); + } + } + }; + + processChunk(); +} +``` + +**Requirements**: 9.1, 9.2, 9.3, 9.4, 9.5 + +### 10. Precompute Theme Specificity + +#### Implementation Strategy + +**Current State**: Specificity is calculated at runtime during override compilation. + +**Optimized State**: Specificity is already computed in compileOverridesRuntime, ensure it's used consistently. + +#### Code Changes + +**File**: `app/theme/_shared/runtime-compile.ts` + +```typescript +// Current implementation already precomputes specificity +// Ensure overrides are sorted by specificity immediately + +export function compileOverridesRuntime( + overrides: Record +): CompiledOverride[] { + const compiled: CompiledOverride[] = []; + + // Parse and compute specificity for all overrides + for (const [selector, props] of Object.entries(overrides)) { + const parsed = parseSelector(selector); + const specificity = calculateSpecificity(selector, parsed); + + compiled.push({ + component: parsed.component, + context: parsed.context, + identifier: parsed.identifier, + state: parsed.state, + attributes: parsed.attributes, + props, + selector, + specificity, // Pre-computed at compilation time + }); + } + + // Sort by specificity once (descending) + // This eliminates need for runtime sorting + return compiled.sort((a, b) => b.specificity - a.specificity); +} +``` + +**File**: `app/theme/_shared/compiler-core.ts` + +```typescript +// Ensure specificity calculation is efficient +export function calculateSpecificity( + selector: string, + parsed: ParsedSelector +): number { + let specificity = 0; + + // Element: 1 point + specificity += 1; + + // Context attribute: 10 points + if (parsed.context) { + specificity += 10; + } + + // Identifier attribute: 20 points (10 + 10 extra for IDs) + if (parsed.identifier) { + specificity += 20; + } + + // Other attributes: 10 points each + if (parsed.attributes) { + specificity += parsed.attributes.length * 10; + } + + // Pseudo-classes: 10 points each + const pseudoCount = (selector.match(/:/g) || []).length; + specificity += pseudoCount * 10; + + return specificity; +} +``` + +**Requirements**: 10.1, 10.2, 10.3, 10.4, 10.5 + +## Data Models + +### Enhanced CompiledTheme + +```typescript +export interface CompiledTheme { + name: string; + isDefault?: boolean; + stylesheets?: string[]; + displayName?: string; + description?: string; + cssVariables: string; + overrides: CompiledOverride[]; // Pre-sorted by specificity + cssSelectors?: Record; + hasStyleSelectors?: boolean; + ui?: Record; + propMaps?: PropClassMaps; + backgrounds?: ThemeBackgrounds; + icons?: Record; + + // Performance metadata + loadedAt?: number; // Timestamp for LRU eviction + loadTimeMs?: number; // Load time tracking +} +``` + +### Theme Loading State + +```typescript +interface ThemeLoadingState { + loading: Set; // Currently loading themes + loaded: Set; // Successfully loaded themes + errors: Map; // Failed theme loads +} +``` + +### Cache Statistics + +```typescript +interface CacheStats { + selectorCache: { + size: number; + maxSize: number; + hitRate: number; + }; + resolverCache: { + size: number; + maxSize: number; + hitRate: number; + }; + contextCache: { + size: number; // WeakMap doesn't expose size, track separately + }; +} +``` + +## Error Handling + +### Theme Load Failures + +```typescript +// Graceful fallback on theme load failure +try { + const theme = await loadTheme(themeName); + if (!theme) { + throw new Error(`Theme ${themeName} returned null`); + } +} catch (error) { + if (import.meta.dev) { + console.error(`[theme] Failed to load ${themeName}:`, error); + } + + // Fall back to default theme + if (themeName !== DEFAULT_THEME) { + return loadTheme(DEFAULT_THEME); + } + + // If default theme fails, return null and use system defaults + return null; +} +``` + +### Cache Overflow Protection + +```typescript +// LRU cache automatically evicts oldest entries +// Monitor cache size in development +if (import.meta.dev && cache.size === cache.maxSize) { + console.warn( + `[theme] Cache at maximum size (${cache.maxSize}). ` + + `Consider increasing maxSize or investigating cache usage.` + ); +} +``` + +### Invalid Selector Handling + +```typescript +// Already handled in css-selector-runtime.ts +try { + const elements = document.querySelectorAll(selector); + // Process elements... +} catch (error) { + if (import.meta.dev) { + console.warn(`[theme] Invalid CSS selector: "${selector}"`, error); + } + // Continue processing other selectors +} +``` + +## Testing Strategy + +### Unit Tests + +1. **LRU Cache Tests**: Verify cache eviction, hit rates, size limits +2. **Selector Parsing Tests**: Ensure cache works correctly, no regressions +3. **CSS Variable Generation Tests**: Benchmark performance improvements +4. **Resolver Index Tests**: Verify correct override resolution with optimizations +5. **Lazy Loading Tests**: Test theme loading, caching, and cleanup + +### Integration Tests + +1. **Theme Switching Tests**: Verify no regressions in theme switching +2. **Memory Leak Tests**: Check for leaks in caches and event listeners +3. **Performance Tests**: Measure load times, memory usage, FPS during operations +4. **Compatibility Tests**: Ensure existing themes work without changes + +### Performance Benchmarks + +```typescript +// Example benchmark structure +describe('Theme Performance Benchmarks', () => { + it('should load theme in < 100ms', async () => { + const start = performance.now(); + await loadTheme('retro'); + const end = performance.now(); + expect(end - start).toBeLessThan(100); + }); + + it('should resolve overrides in < 1ms', () => { + const resolver = getResolver('retro'); + const start = performance.now(); + resolver.resolve({ component: 'button', context: 'chat' }); + const end = performance.now(); + expect(end - start).toBeLessThan(1); + }); + + it('should have > 80% cache hit rate after warmup', () => { + // Warmup + for (let i = 0; i < 100; i++) { + resolver.resolve({ component: 'button', context: 'chat' }); + } + + // Measure + const stats = getCacheStats(); + expect(stats.resolverCache.hitRate).toBeGreaterThan(0.8); + }); +}); +``` + +### End-to-End Tests + +1. **Full Theme Switch Test**: User switches between all available themes +2. **Memory Usage Test**: Monitor memory over 100 theme switches +3. **Render Performance Test**: Measure FPS during theme operations +4. **Bundle Size Test**: Verify bundle size reduction + +## Performance Monitoring + +### Development Mode Monitoring + +```typescript +if (import.meta.dev) { + // Log performance metrics + console.log('[theme] Performance Stats:', { + themeLoadTime: loadTimeMs, + cacheStats: getCacheStats(), + memoryUsage: performance.memory?.usedJSHeapSize, + }); +} +``` + +### Production Telemetry + +```typescript +// Optional: Send performance metrics to analytics +if (import.meta.client && window.performance) { + const marks = performance.getEntriesByType('mark') + .filter(mark => mark.name.startsWith('theme:')); + + // Report to analytics service + // (implementation depends on analytics setup) +} +``` + +## Migration Guide + +### For Theme Authors + +No changes required! All optimizations are transparent to theme authors. Existing themes will work without modification. + +### For Application Developers + +1. **Remove manual theme preloading**: The system now handles lazy loading automatically +2. **Update performance expectations**: Theme switching should be faster +3. **Monitor cache sizes**: Check cache stats in dev tools if needed +4. **Update tests**: Performance benchmarks may need threshold adjustments + +### Backward Compatibility + +All optimizations maintain full backward compatibility: +- Existing theme definitions work unchanged +- Public APIs remain stable +- All existing tests pass +- No breaking changes to theme system behavior + +## Implementation Plan + +1. **Phase 1** (Week 1): Lazy loading and cache optimizations +2. **Phase 2** (Week 1): CSS generation and stylesheet loading optimizations +3. **Phase 3** (Week 2): Directive and resolver optimizations +4. **Phase 4** (Week 2): Type safety and validation improvements +5. **Phase 5** (Week 3): Testing, benchmarking, and documentation + +## Success Criteria + +- All existing tests pass +- Performance benchmarks show improvements in target areas +- Memory usage reduced by 30%+ +- Initial load time reduced by 20%+ +- No regressions in functionality +- Developer feedback is positive diff --git a/planning/theme-performance/requirements.md b/planning/theme-performance/requirements.md new file mode 100644 index 00000000..7d2d2801 --- /dev/null +++ b/planning/theme-performance/requirements.md @@ -0,0 +1,203 @@ +# Theme System Performance Optimization Requirements + +## Introduction + +The theme system in `app/theme` is a sophisticated runtime and compile-time system that enables dynamic theming with CSS selector-based overrides, Material Design 3 color palettes, and reactive theme switching. While functionally robust, there are opportunities to improve performance, initial load times, type safety, and memory efficiency without breaking existing functionality. + +This document outlines 10 specific performance optimization requirements that will make the theme system faster, more memory-efficient, and more type-safe while maintaining backward compatibility. + +## Requirements + +### 1. Lazy-Load Theme Definitions + +**User Story**: As a developer, I want themes to be loaded only when needed, so that initial page load is faster and memory usage is minimized. + +**Acceptance Criteria**: +- WHEN the application initializes, THEN only the default theme SHALL be loaded +- WHEN a user switches to a different theme, THEN that theme SHALL be dynamically loaded +- WHEN a theme is switched away from, THEN the previous theme's resources SHALL be cleaned up (except for the default theme) +- WHEN switching back to a previously loaded theme, THEN the cached version SHALL be used if available +- IF a theme fails to load, THEN the system SHALL gracefully fall back to the default theme +- THEN initial bundle size SHALL be reduced by at least 20% +- THEN memory usage SHALL be reduced by keeping only 2 themes loaded at once (active + default) + +### 2. Optimize CSS Variable Generation + +**User Story**: As a developer, I want CSS variable generation to be more efficient, so that theme switching is faster and uses less memory. + +**Acceptance Criteria**: +- WHEN CSS variables are generated, THEN the kebab-case conversion SHALL use a pre-computed cache +- WHEN building CSS blocks, THEN string concatenation SHALL use array joining instead of repeated string concatenation +- WHEN dark mode overrides are present, THEN only non-duplicate variables SHALL be included +- THEN CSS variable generation time SHALL be reduced by at least 30% +- THEN memory allocations during generation SHALL be reduced by at least 40% + +### 3. Cache Selector Parsing Results + +**User Story**: As a developer, I want selector parsing to be cached, so that repeated parsing of the same selectors is avoided. + +**Acceptance Criteria**: +- WHEN a CSS selector is parsed, THEN the result SHALL be cached in memory +- WHEN the same selector is encountered again, THEN the cached result SHALL be returned +- WHEN switching themes, THEN the selector cache SHALL persist across themes +- THEN selector parsing time SHALL be reduced to < 0.1ms for cached selectors +- THEN the cache SHALL have a maximum size of 1000 entries to prevent unbounded growth + +### 4. Optimize Runtime Resolver Index + +**User Story**: As a developer, I want override resolution to be faster, so that component rendering is not delayed. + +**Acceptance Criteria**: +- WHEN overrides are indexed, THEN they SHALL be pre-sorted by specificity during compilation +- WHEN looking up overrides, THEN the resolver SHALL use a Map-based index by component type +- WHEN matching overrides, THEN attribute-dependent overrides SHALL be tracked separately for faster cache decisions +- THEN override resolution time SHALL remain under 0.5ms per component (currently < 1ms) +- THEN the resolver SHALL use an LRU cache with configurable maximum size (default 100 entries) + +### 5. Reduce Directive Overhead + +**User Story**: As a developer, I want the v-theme directive to have minimal performance impact, so that pages with many themed components render quickly. + +**Acceptance Criteria**: +- WHEN the v-theme directive is applied, THEN context detection SHALL be memoized per element +- WHEN component names are detected, THEN known Nuxt UI components SHALL be checked using a Set instead of array iteration +- WHEN theme changes occur, THEN reactive watchers SHALL be consolidated using a global watcher instead of per-directive watchers +- THEN directive initialization time SHALL be reduced by at least 25% +- THEN memory usage per directive SHALL be reduced by at least 40% + +### 6. Optimize Theme Stylesheet Loading + +**User Story**: As a developer, I want theme stylesheets to load efficiently, so that theme switching is perceived as instantaneous. + +**Acceptance Criteria**: +- WHEN multiple stylesheets are loaded, THEN they SHALL be loaded in parallel using Promise.all +- WHEN a stylesheet is already loaded, THEN no duplicate request SHALL be made +- WHEN switching themes, THEN previous theme stylesheets SHALL be removed immediately +- WHEN loading fails, THEN errors SHALL be logged only in development mode +- THEN stylesheet loading time SHALL be reduced by at least 35% through parallelization +- THEN stylesheet URL resolution SHALL be cached to avoid repeated lookups + +### 7. Implement WeakMap-based Caching + +**User Story**: As a developer, I want theme-related caches to use WeakMaps where appropriate, so that memory leaks are prevented when components unmount. + +**Acceptance Criteria**: +- WHEN component instances are cached, THEN WeakMaps SHALL be used instead of regular Maps +- WHEN a component is unmounted, THEN its cache entries SHALL be automatically garbage collected +- WHEN override results are cached, THEN the cache SHALL be scoped to component instances using WeakMaps +- THEN memory leaks from long-lived cache entries SHALL be eliminated +- THEN cache cleanup SHALL be automatic and require no manual intervention + +### 8. Type-Safe Theme Definitions + +**User Story**: As a theme author, I want type checking for theme definitions at authoring time, so that invalid configurations are caught early. + +**Acceptance Criteria**: +- WHEN defining color palettes, THEN TypeScript SHALL enforce that required colors are provided +- WHEN defining overrides, THEN valid component names and props SHALL be type-checked +- WHEN using CSS selectors, THEN the selector syntax SHALL be validated at build time +- WHEN generating types, THEN theme names and identifiers SHALL be auto-completed in IDEs +- THEN type errors SHALL be caught at build time, not runtime +- THEN theme validation errors SHALL provide clear, actionable error messages + +### 9. Batch DOM Operations + +**User Story**: As a developer, I want theme class applications to batch DOM updates, so that layout thrashing is avoided and rendering is smooth. + +**Acceptance Criteria**: +- WHEN applying theme classes, THEN DOM operations SHALL be batched in 5ms chunks +- WHEN multiple selectors match, THEN classList operations SHALL be combined per element +- WHEN removing classes, THEN batch removal SHALL be used instead of individual removes +- THEN layout reflows SHALL be minimized to once per batch instead of per operation +- THEN FPS during theme switching SHALL remain above 50fps on mid-range devices +- THEN time budget per frame SHALL not exceed 5ms to maintain 60fps rendering + +### 10. Precompute Theme Specificity + +**User Story**: As a developer, I want override specificity to be precomputed at build time, so that runtime calculations are eliminated. + +**Acceptance Criteria**: +- WHEN themes are compiled, THEN specificity scores SHALL be calculated and stored in CompiledOverride objects +- WHEN overrides are sorted, THEN pre-calculated specificity SHALL be used instead of runtime calculation +- WHEN new themes are added, THEN specificity SHALL be computed during the build process +- THEN runtime specificity calculations SHALL be eliminated entirely +- THEN override sorting time SHALL be reduced to near-zero (already sorted at build time) +- THEN specificity calculation errors SHALL be caught at build time with clear error messages + +## Non-Functional Requirements + +### Performance Targets + +1. **Initial Load Time**: Reduce by 20-30% through lazy loading and code splitting +2. **Theme Switch Time**: Maintain current < 50ms target while improving reliability +3. **Override Resolution**: Maintain current < 1ms target while improving cache hit rate +4. **Memory Usage**: Reduce by 30-40% through better caching and cleanup strategies +5. **Bundle Size**: Reduce by 15-25% through lazy loading and tree shaking + +### Compatibility Requirements + +1. **Backward Compatibility**: All existing themes SHALL continue to work without modification +2. **API Stability**: The public theme API SHALL remain unchanged +3. **Testing**: All existing tests SHALL continue to pass +4. **Development Experience**: Theme authoring SHALL remain as simple as before + +### Quality Requirements + +1. **Type Safety**: TypeScript strict mode SHALL be maintained +2. **Error Handling**: Graceful degradation SHALL be maintained in production +3. **Developer Experience**: Clear error messages SHALL be provided in development mode +4. **Code Quality**: ESLint rules SHALL be followed, no new warnings introduced + +## Success Metrics + +The optimization will be considered successful when: + +1. Initial page load time is reduced by at least 20% +2. Memory usage is reduced by at least 30% during normal operation +3. All existing tests pass without modification +4. No regressions in theme switching functionality +5. Type errors are caught at build time instead of runtime +6. Developer feedback on theme authoring is positive +7. Performance benchmarks show measurable improvements in all target areas +8. Bundle size is reduced by at least 15% +9. Frame rate during theme operations remains above 50fps +10. Cache hit rates improve by at least 40% + +## Implementation Priority + +**High Priority** (Must Have): +- Requirement 1: Lazy-Load Theme Definitions +- Requirement 4: Optimize Runtime Resolver Index +- Requirement 5: Reduce Directive Overhead +- Requirement 7: Implement WeakMap-based Caching + +**Medium Priority** (Should Have): +- Requirement 2: Optimize CSS Variable Generation +- Requirement 3: Cache Selector Parsing Results +- Requirement 6: Optimize Theme Stylesheet Loading +- Requirement 9: Batch DOM Operations + +**Low Priority** (Nice to Have): +- Requirement 8: Type-Safe Theme Definitions +- Requirement 10: Precompute Theme Specificity + +## Dependencies + +- Existing theme system must remain functional during incremental implementation +- Changes must be tested against all existing themes (retro, blank) +- Performance benchmarks must be established before changes begin +- Each optimization should be implemented and tested independently + +## Risks and Mitigations + +### Risk: Breaking Existing Functionality +**Mitigation**: Comprehensive test coverage, incremental rollout, feature flags for new optimizations + +### Risk: Over-Optimization Leading to Complexity +**Mitigation**: Focus on measurable improvements, keep code readable, add detailed comments + +### Risk: Cache Invalidation Issues +**Mitigation**: Use clear cache invalidation strategies, test theme switching thoroughly + +### Risk: Browser Compatibility Issues +**Mitigation**: Test on multiple browsers, use progressive enhancement, provide fallbacks diff --git a/planning/theme-performance/tasks.md b/planning/theme-performance/tasks.md new file mode 100644 index 00000000..6b8fcb2a --- /dev/null +++ b/planning/theme-performance/tasks.md @@ -0,0 +1,528 @@ +# Theme System Performance Optimization - Implementation Tasks + +## Overview + +This document provides a detailed checklist of tasks required to implement the 10 performance optimizations for the theme system. Tasks are organized by optimization area and include specific subtasks, file changes, and requirement mappings. + +## Task Checklist + +### 1. Lazy-Load Theme Definitions +Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7 + +- [ ] 1.1 Add theme loading state tracking + - [ ] Create `ThemeLoadingState` interface in `90.theme.client.ts` + - [ ] Add state tracking for loading, loaded, and error states + - [ ] Prevent duplicate concurrent loads of same theme + +- [ ] 1.2 Optimize `loadTheme` function + - [ ] Add check for already-loaded themes (early return) + - [ ] Add check for currently-loading themes (wait for completion) + - [ ] Mark theme as loading before starting load + - [ ] Handle load success and failure states + - [ ] Update state tracking on completion + +- [ ] 1.3 Extract icon loading to helper function + - [ ] Create `loadThemeIcons` helper function + - [ ] Handle both inline icons and icon loaders + - [ ] Add error handling for icon load failures + +- [ ] 1.4 Implement parallel asset loading + - [ ] Use Promise.all for stylesheets and icons + - [ ] Ensure app config loading is included + - [ ] Track load timing for monitoring + +- [ ] 1.5 Update initialization to load only default theme + - [ ] Remove eager loading of all themes + - [ ] Keep only default theme initialization + - [ ] Update stored theme loading logic + +- [ ] 1.6 Add theme cleanup logic + - [ ] Implement `cleanupInactiveThemes` function (already exists) + - [ ] Verify cleanup keeps active + default themes + - [ ] Test memory is freed after cleanup + +- [ ] 1.7 Add tests for lazy loading + - [ ] Test default theme loads on init + - [ ] Test theme loads on switch + - [ ] Test cleanup after theme switch + - [ ] Test fallback on load failure + - [ ] Test concurrent load prevention + +### 2. Optimize CSS Variable Generation +Requirements: 2.1, 2.2, 2.3, 2.4, 2.5 + +- [ ] 2.1 Optimize kebab-case conversion + - [ ] Review existing cache implementation + - [ ] Optimize string processing with character array + - [ ] Add cache size monitoring in dev mode + +- [ ] 2.2 Optimize `buildPalette` function + - [ ] Use indexed for loop instead of for...in + - [ ] Cache Object.keys result + - [ ] Skip unnecessary iterations early + +- [ ] 2.3 Optimize `toCssBlock` with array joining + - [ ] Replace string concatenation with array + - [ ] Build lines array first, join at end + - [ ] Measure performance improvement + +- [ ] 2.4 Optimize dark mode override generation + - [ ] Only build dark overrides if colors exist + - [ ] Avoid duplicate border style processing + - [ ] Use array joining for final output + +- [ ] 2.5 Add performance tests + - [ ] Benchmark CSS variable generation before changes + - [ ] Benchmark after each optimization + - [ ] Verify 30% improvement target + - [ ] Add regression test to prevent future slowdowns + +### 3. Cache Selector Parsing Results +Requirements: 3.1, 3.2, 3.3, 3.4, 3.5 + +- [ ] 3.1 Implement LRU cache for selectors + - [ ] Create `LRUSelectorCache` class + - [ ] Implement get/set with LRU eviction + - [ ] Add size limit (1000 entries) + - [ ] Add clear method for testing + +- [ ] 3.2 Replace existing cache with LRU cache + - [ ] Update `parseSelector` to use new cache + - [ ] Ensure cache is used consistently + - [ ] Verify cache persistence across themes + +- [ ] 3.3 Add cache statistics function + - [ ] Create `getSelectorCacheStats` function + - [ ] Export cache size and max size + - [ ] Only available in dev mode + +- [ ] 3.4 Add tests for selector cache + - [ ] Test cache hit/miss behavior + - [ ] Test LRU eviction policy + - [ ] Test size limit enforcement + - [ ] Test cache persistence + - [ ] Benchmark parse time with cache + +- [ ] 3.5 Add monitoring in dev mode + - [ ] Log cache stats on theme load + - [ ] Warn if cache hit rate is low + - [ ] Track cache effectiveness + +### 4. Optimize Runtime Resolver Index +Requirements: 4.1, 4.2, 4.3, 4.4, 4.5 + +- [ ] 4.1 Ensure overrides are pre-sorted + - [ ] Verify sort in RuntimeResolver constructor + - [ ] Confirm stable sort for equal specificity + - [ ] Add comment about pre-sorting + +- [ ] 4.2 Optimize override indexing + - [ ] Verify Map-based index by component + - [ ] Track attribute-dependent components + - [ ] Use Set for efficient lookup + +- [ ] 4.3 Verify LRU cache implementation + - [ ] Check cache size limit (100 entries) + - [ ] Verify cache key generation + - [ ] Test cache invalidation on theme switch + +- [ ] 4.4 Add cache decision optimization + - [ ] Skip caching for element-dependent matches + - [ ] Use Set for componentsWithAttributes check + - [ ] Optimize getCacheKey function + +- [ ] 4.5 Add resolver performance tests + - [ ] Benchmark override resolution + - [ ] Test cache hit rate after warmup + - [ ] Verify < 0.5ms resolution time + - [ ] Test with various component/context combinations + +### 5. Reduce Directive Overhead +Requirements: 5.1, 5.2, 5.3, 5.4, 5.5 + +- [ ] 5.1 Add context detection cache + - [ ] Create WeakMap for context cache + - [ ] Update `detectContext` to use cache + - [ ] Verify cache works with DOM updates + +- [ ] 5.2 Verify Set-based component checks + - [ ] Confirm NUXT_UI_COMPONENTS uses Set + - [ ] Verify O(1) lookup performance + - [ ] Add any missing components + +- [ ] 5.3 Verify global watcher implementation + - [ ] Check 92.theme-lazy-sync.client.ts implementation + - [ ] Ensure single watcher for all theme changes + - [ ] Verify batching with nextTick + +- [ ] 5.4 Optimize directive application + - [ ] Review applyThemeDirective function + - [ ] Minimize repeated computations + - [ ] Cache component name detection + +- [ ] 5.5 Add directive performance tests + - [ ] Benchmark directive initialization + - [ ] Test memory usage per directive + - [ ] Verify 25% initialization improvement + - [ ] Test with many themed components + +### 6. Optimize Theme Stylesheet Loading +Requirements: 6.1, 6.2, 6.3, 6.4, 6.5, 6.6 + +- [ ] 6.1 Add stylesheet URL cache + - [ ] Create Map for URL cache + - [ ] Add cache key format: `theme:path` + - [ ] Cache both successful and fallback URLs + +- [ ] 6.2 Optimize `resolveThemeStylesheetHref` + - [ ] Check cache before resolution + - [ ] Cache result before returning + - [ ] Handle external URLs efficiently + +- [ ] 6.3 Optimize `loadThemeStylesheets` + - [ ] Resolve all URLs first (parallel) + - [ ] Then load all stylesheets (parallel) + - [ ] Remove duplicate checking logic + - [ ] Use Promise.all for parallelization + +- [ ] 6.4 Verify stylesheet cleanup + - [ ] Check unloadThemeStylesheets works correctly + - [ ] Test stylesheets removed on theme switch + - [ ] Verify no memory leaks from tags + +- [ ] 6.5 Add error handling improvements + - [ ] Log errors only in dev mode + - [ ] Gracefully handle missing stylesheets + - [ ] Continue loading other stylesheets on failure + +- [ ] 6.6 Add stylesheet loading tests + - [ ] Test parallel loading + - [ ] Test cache effectiveness + - [ ] Benchmark loading time improvement + - [ ] Test duplicate prevention + +### 7. Implement WeakMap-based Caching +Requirements: 7.1, 7.2, 7.3, 7.4, 7.5 + +- [ ] 7.1 Verify WeakMap usage in composables + - [ ] Check useThemeResolver.ts uses WeakMap + - [ ] Verify componentOverrideCache implementation + - [ ] Confirm automatic garbage collection + +- [ ] 7.2 Add WeakMap for context cache + - [ ] Update detectContext to use WeakMap + - [ ] Verify cache in 91.auto-theme.client.ts + - [ ] Test automatic cleanup on unmount + +- [ ] 7.3 Audit all cache usage + - [ ] Identify any Maps that should be WeakMaps + - [ ] Replace where components are keys + - [ ] Document why each cache type is chosen + +- [ ] 7.4 Add memory leak tests + - [ ] Test component unmount cleans caches + - [ ] Monitor memory over many mount/unmount cycles + - [ ] Verify WeakMaps are garbage collected + +- [ ] 7.5 Add documentation + - [ ] Document WeakMap usage patterns + - [ ] Explain when to use WeakMap vs Map + - [ ] Add comments in code + +### 8. Type-Safe Theme Definitions +Requirements: 8.1, 8.2, 8.3, 8.4, 8.5, 8.6 + +- [ ] 8.1 Add strict color palette types + - [ ] Create StrictColorPalette interface + - [ ] Make primary, secondary, surface required + - [ ] Add proper dark mode typing + +- [ ] 8.2 Add validated theme definition type + - [ ] Create ValidatedThemeDefinition interface + - [ ] Extend from ThemeDefinition + - [ ] Add stricter requirements + +- [ ] 8.3 Add type guard function + - [ ] Create isValidThemeDefinition function + - [ ] Check all required fields + - [ ] Return proper type predicate + +- [ ] 8.4 Enhance defineTheme validation + - [ ] Use type guard in defineTheme + - [ ] Provide detailed type errors + - [ ] Add suggestions for fixes + +- [ ] 8.5 Update existing themes + - [ ] Review retro theme definition + - [ ] Review blank theme definition + - [ ] Ensure both pass strict validation + +- [ ] 8.6 Add type safety tests + - [ ] Test valid theme definitions + - [ ] Test invalid theme definitions + - [ ] Test error messages are helpful + - [ ] Verify TypeScript compilation errors + +### 9. Batch DOM Operations +Requirements: 9.1, 9.2, 9.3, 9.4, 9.5, 9.6 + +- [ ] 9.1 Verify frame budget implementation + - [ ] Check FRAME_BUDGET_MS is 5ms + - [ ] Verify performance.now() usage + - [ ] Confirm budget checking in loop + +- [ ] 9.2 Optimize classList operations + - [ ] Batch multiple classes in single add call + - [ ] Use spread operator for efficiency + - [ ] Remove redundant operations + +- [ ] 9.3 Verify requestAnimationFrame usage + - [ ] Check RAF is used for chunking + - [ ] Fallback to setTimeout if RAF unavailable + - [ ] Test smooth rendering during apply + +- [ ] 9.4 Add duplicate prevention + - [ ] Verify classApplicationCache usage + - [ ] Check filter removes already-applied classes + - [ ] Test no duplicate classes applied + +- [ ] 9.5 Optimize removeThemeClasses + - [ ] Batch removal operations + - [ ] Clear cache entries + - [ ] Test cleanup is complete + +- [ ] 9.6 Add DOM operation tests + - [ ] Test frame budget respected + - [ ] Measure FPS during class application + - [ ] Verify no layout thrashing + - [ ] Test with many elements + +### 10. Precompute Theme Specificity +Requirements: 10.1, 10.2, 10.3, 10.4, 10.5, 10.6 + +- [ ] 10.1 Verify specificity computation + - [ ] Check calculateSpecificity function + - [ ] Confirm correct CSS specificity rules + - [ ] Validate scoring algorithm + +- [ ] 10.2 Ensure runtime compilation uses precomputed scores + - [ ] Review compileOverridesRuntime function + - [ ] Verify specificity stored in CompiledOverride + - [ ] Check overrides are sorted by specificity + +- [ ] 10.3 Verify resolver uses precomputed scores + - [ ] Check RuntimeResolver constructor + - [ ] Confirm no runtime specificity calculation + - [ ] Verify sort uses pre-calculated values + +- [ ] 10.4 Add specificity tests + - [ ] Test specificity calculation + - [ ] Test override sorting + - [ ] Test correct override precedence + - [ ] Verify edge cases + +- [ ] 10.5 Add build-time validation + - [ ] Validate specificity at build time + - [ ] Catch specificity errors early + - [ ] Provide clear error messages + +- [ ] 10.6 Document specificity rules + - [ ] Add comments to calculateSpecificity + - [ ] Document scoring algorithm + - [ ] Provide examples in docs + +## Cross-Cutting Tasks + +### Testing + +- [ ] Run existing test suite + - [ ] Verify all tests pass before changes + - [ ] Establish baseline performance metrics + - [ ] Document current test coverage + +- [ ] Add performance benchmarks + - [ ] Create benchmark suite for theme operations + - [ ] Measure theme load time + - [ ] Measure override resolution time + - [ ] Measure CSS generation time + - [ ] Measure stylesheet loading time + +- [ ] Add memory tests + - [ ] Monitor memory usage over operations + - [ ] Test for memory leaks + - [ ] Verify cache cleanup + - [ ] Test WeakMap garbage collection + +- [ ] Add integration tests + - [ ] Test full theme switch flow + - [ ] Test multiple theme switches + - [ ] Test concurrent operations + - [ ] Test error recovery + +- [ ] Run performance comparison + - [ ] Compare before/after metrics + - [ ] Verify all targets met + - [ ] Document improvements + - [ ] Create performance report + +### Documentation + +- [ ] Update theme system README + - [ ] Document performance improvements + - [ ] Update benchmarks section + - [ ] Add cache configuration docs + - [ ] Document best practices + +- [ ] Add inline code comments + - [ ] Comment optimization techniques + - [ ] Explain cache strategies + - [ ] Document performance considerations + - [ ] Add examples where helpful + +- [ ] Update migration guide + - [ ] Document any API changes + - [ ] Provide upgrade instructions + - [ ] Note backward compatibility + - [ ] Add troubleshooting section + +- [ ] Create performance guide + - [ ] Document performance features + - [ ] Explain cache tuning + - [ ] Provide monitoring tips + - [ ] Add optimization checklist + +### Code Quality + +- [ ] Run linter + - [ ] Fix any ESLint warnings + - [ ] Ensure consistent formatting + - [ ] Check for code smells + - [ ] Verify TypeScript strict mode + +- [ ] Review code changes + - [ ] Self-review all changes + - [ ] Check for edge cases + - [ ] Verify error handling + - [ ] Ensure backward compatibility + +- [ ] Update types + - [ ] Ensure all types are exported + - [ ] Update generated types + - [ ] Fix any type errors + - [ ] Improve type documentation + +- [ ] Optimize imports + - [ ] Remove unused imports + - [ ] Organize imports consistently + - [ ] Use tree-shaking friendly imports + - [ ] Check bundle size impact + +### Validation + +- [ ] Manual testing + - [ ] Test theme switching in browser + - [ ] Verify all themes work + - [ ] Test on different browsers + - [ ] Check mobile performance + +- [ ] Performance profiling + - [ ] Profile with Chrome DevTools + - [ ] Check for performance bottlenecks + - [ ] Verify frame rates + - [ ] Monitor memory usage + +- [ ] Bundle analysis + - [ ] Run bundle analyzer + - [ ] Verify size reduction + - [ ] Check code splitting + - [ ] Analyze lazy loading + +- [ ] User testing + - [ ] Get developer feedback + - [ ] Test theme authoring experience + - [ ] Verify error messages are helpful + - [ ] Check documentation clarity + +## Implementation Schedule + +### Week 1: Core Optimizations + +**Days 1-2**: Lazy Loading & Caching +- Task group 1: Lazy-Load Theme Definitions +- Task group 3: Cache Selector Parsing Results +- Task group 7: Implement WeakMap-based Caching + +**Days 3-4**: CSS & Stylesheet Optimization +- Task group 2: Optimize CSS Variable Generation +- Task group 6: Optimize Theme Stylesheet Loading + +**Day 5**: Testing & Validation +- Run all tests +- Performance benchmarks +- Fix any issues + +### Week 2: Runtime & Type Safety + +**Days 1-2**: Runtime Optimization +- Task group 4: Optimize Runtime Resolver Index +- Task group 5: Reduce Directive Overhead +- Task group 9: Batch DOM Operations + +**Days 3-4**: Type Safety & Precomputation +- Task group 8: Type-Safe Theme Definitions +- Task group 10: Precompute Theme Specificity + +**Day 5**: Testing & Validation +- Integration tests +- Memory leak tests +- Performance comparison + +### Week 3: Polish & Documentation + +**Days 1-2**: Final Testing +- Manual testing across browsers +- Performance profiling +- Bundle analysis +- User testing + +**Days 3-4**: Documentation +- Update README +- Add code comments +- Create performance guide +- Update migration guide + +**Day 5**: Review & Release +- Final code review +- Prepare release notes +- Merge changes +- Deploy + +## Success Metrics + +At the end of implementation, verify: + +- [ ] All existing tests pass +- [ ] Initial load time reduced by ≥ 20% +- [ ] Memory usage reduced by ≥ 30% +- [ ] Bundle size reduced by ≥ 15% +- [ ] Override resolution time < 0.5ms +- [ ] CSS generation time reduced by ≥ 30% +- [ ] Stylesheet loading time reduced by ≥ 35% +- [ ] Frame rate during operations ≥ 50fps +- [ ] Cache hit rate ≥ 80% after warmup +- [ ] No memory leaks detected +- [ ] Type safety improved +- [ ] All themes still work +- [ ] Developer feedback positive +- [ ] Documentation updated +- [ ] Code quality maintained + +## Notes + +- Each task should be implemented and tested independently +- Commit after completing each major task group +- Use feature flags if needed for gradual rollout +- Monitor for regressions after each change +- Keep backward compatibility as top priority +- Document any trade-offs or design decisions +- Get code review before merging significant changes diff --git a/types/theme-generated.d.ts b/types/theme-generated.d.ts index 10fef843..3f2100cd 100644 --- a/types/theme-generated.d.ts +++ b/types/theme-generated.d.ts @@ -1,7 +1,7 @@ /** * Auto-generated by theme compiler * Do not edit manually - changes will be overwritten - * Generated: 2025-11-27T14:29:20.175Z + * Generated: 2025-12-03T17:08:05.536Z */ /**