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
12 KiB
Comprehensive Admin UI Architecture Redesign - Complete
Status: ✅ PHASES 1-4 IMPLEMENTED Date: 2025-12-08 Root Cause Identified: Cascading module initialization failure from eager singleton creation Solution Applied: Lazy initialization with proper separation of concerns
Executive Summary
The Design System Admin UI had a critical architectural flaw causing a cascading initialization failure that made the project selector "not working at all." Through deep Zen ThinkDeep analysis and systematic redesign, we've identified and fixed the root cause by refactoring module initialization patterns across 4 critical phases.
The Core Problem
Before: Incognito-detector was a singleton class instantiated at module load time with a detect() method call that could fail. If it failed, it poisoned all dependent modules (context-store → api-client → ds-project-selector), causing cascading failures across the entire application.
After: Incognito-detector now uses lazy initialization with exported functions. Detection only happens on first storage access, eliminating cascading failures.
Phase 1: Refactor Incognito-Detector (COMPLETED)
File Modified
/home/overbits/dss/admin-ui/js/utils/incognito-detector.js
Changes Made
BEFORE (Eager Singleton Pattern):
class IncognitoDetector {
constructor() {
this.detect(); // ❌ Executes at module load - CASCADING FAILURE
}
}
const incognitoDetector = new IncognitoDetector();
export default incognitoDetector;
AFTER (Lazy Initialization Pattern):
let isIncognitoResult = null; // Memoized result
function detectIncognito() {
try {
localStorage.setItem(testKey, testValue);
const retrieved = localStorage.getItem(testKey);
localStorage.removeItem(testKey);
isIncognitoResult = retrieved !== testValue;
} catch (e) {
isIncognitoResult = true; // Graceful fallback
}
}
// Export individual functions instead of singleton
export function getStorage() {
if (isIncognitoResult === null) {
detectIncognito(); // ✅ Only runs on first call
}
return isIncognitoResult ? sessionStorage : localStorage;
}
export function checkIncognito() { ... }
export function getItem(key) { ... }
export function setItem(key, value) { ... }
export function removeItem(key) { ... }
export function clear() { ... }
Key Benefits
- ✅ Detection deferred until actually needed
- ✅ No cascading failures if initialization fails
- ✅ Graceful fallback to sessionStorage
- ✅ Memoization caches result after first call
Phase 2: Refactor Context-Store (COMPLETED)
File Modified
/home/overbits/dss/admin-ui/js/stores/context-store.js
Changes Made
Removed Dependency Chain:
- ❌ Removed:
import incognitoDetector from '../utils/incognito-detector.js' - ✅ All constructor calls changed from
incognitoDetector.getItem()tolocalStorage.getItem() - ✅ All storage calls in methods changed to use
localStoragedirectly
Lines Modified
- Line 1-10: Updated file header with architecture note
- Line 38-40: Constructor - replaced incognitoDetector.getItem() with localStorage.getItem()
- Line 45-48: adminSettings - replaced incognitoDetector.getItem() with localStorage.getItem()
- Line 53: currentProjectSkin - replaced incognitoDetector.getItem() with localStorage.getItem()
- Line 205: setProject() - replaced incognitoDetector.setItem() with localStorage.setItem()
- Line 214: setTeam() - replaced incognitoDetector.setItem() with localStorage.setItem()
- Line 285: setCurrentProjectSkin() - replaced incognitoDetector.setItem() with localStorage.setItem()
Architecture Rationale
Separation of Concerns:
incognito-detector: Handles storage type selection (localStorage vs sessionStorage)api-client: Uses incognito-detector for token persistencecontext-store: Uses localStorage directly for context persistence (no incognito handling needed)ds-project-selector: Calls apiClient which handles auth + storage
This prevents circular dependencies and removes module load-time coupling.
Phase 3: Refactor API-Client (COMPLETED)
File Modified
/home/overbits/dss/admin-ui/js/services/api-client.js
Changes Made
Updated Import Pattern:
// BEFORE
import incognitoDetector from '../utils/incognito-detector.js';
// AFTER
import { getItem, setItem, removeItem } from '../utils/incognito-detector.js';
Lines Modified
- Line 8: Changed from default import to named imports
- Line 21:
incognitoDetector.getItem()→getItem() - Line 35:
incognitoDetector.setItem()→setItem() - Line 47:
incognitoDetector.removeItem()→removeItem() - Lines 34-41: Changed saveTokensToStorage to use try-catch instead of checking return boolean
Key Benefits
- ✅ Properly uses lazy-initialized functions
- ✅ Cleaner error handling with try-catch
- ✅ No coupling to class structure
- ✅ Incognito detection triggered only on first token access
Phase 4: Simplify Project Selector Component (COMPLETED)
File Modified
/home/overbits/dss/admin-ui/js/components/layout/ds-project-selector.js
Changes Made
Simplify Auth-Change Listener
- ❌ REMOVED: Duplicate window.addEventListener('auth-change')
- ✅ KEPT: Single document.addEventListener('auth-change')
- ✅ Updated disconnectedCallback to only remove document listener
Lines Modified:
- Line 32-38: Removed window listener, kept document listener
- Line 47-48: Updated disconnectedCallback to only remove from document
Fix Memory Leak in Event Listeners
BEFORE (Memory Leak):
setupEventListeners() {
// ...
const closeDropdown = (e) => { ... };
document.addEventListener('click', closeDropdown); // ❌ Never removed!
}
AFTER (Proper Cleanup):
setupEventListeners() {
// ...
if (!this.closeDropdownHandler) {
this.closeDropdownHandler = (e) => { ... };
document.addEventListener('click', this.closeDropdownHandler);
}
}
disconnectedCallback() {
if (this.closeDropdownHandler) {
document.removeEventListener('click', this.closeDropdownHandler); // ✅ Cleaned up
}
}
Simplify Button Event Listeners
- ❌ REMOVED:
button.cloneNode()workaround for removing listeners - ✅ KEPT: Clean addEventListener pattern
- ✅ Listeners are properly re-attached during renderDropdown
Lines Modified:
- Line 109-115: Removed cloneNode workaround
- Line 128-135: Added closeDropdownHandler storage for cleanup
- Line 52-54: Added cleanup for closeDropdownHandler
Key Benefits
- ✅ Fixed memory leak in document click listener
- ✅ Simplified event listener pattern
- ✅ Proper cleanup of all listeners in disconnectedCallback
- ✅ Reduced redundant event propagation
Architecture Overview: After Redesign
HTML Page (index.html)
↓
ds-shell.js (top-level component)
├── Uses: context-store (localStorage directly)
│ └── No incognito-detector dependency
│
├── Uses: api-client (authenticated API calls)
│ └── Uses: incognito-detector lazy functions (only on token access)
│
└── Uses: ds-project-selector (component)
├── Uses: context-store (subscription)
├── Uses: api-client (authenticated requests)
└── Listens to: auth-change events (triggers reload)
Key Architectural Improvements
- Lazy Initialization: Modules don't execute expensive operations at load time
- No Cascading Failures: If one layer fails, others aren't affected
- Clear Separation of Concerns: Each module has a single responsibility
- Proper Resource Cleanup: All listeners are tracked and removed
- Graceful Fallbacks: Storage operations have error handling
- Reduced Complexity: Simplified event listener patterns
Testing Recommendations
Phase 5: Verification Testing
To verify the fixes work end-to-end:
# Test 1: Module Loading
- Verify modules load in correct order
- Verify no console errors on page load
- Verify no cascading failures
# Test 2: Normal Mode
- Load admin UI in normal browser
- Verify projects load with Authorization headers
- Verify project selection works
# Test 3: Incognito Mode
- Load admin UI in incognito/private mode
- Verify sessionStorage is used for tokens
- Verify projects load correctly
- Verify selected project persists in session
# Test 4: Auth State Changes
- Log in/out
- Verify projects reload on auth-change event
- Verify token refresh works
# Test 5: Component Cleanup
- Navigate away from page
- Verify event listeners are cleaned up
- Verify no memory leaks
Files Changed Summary
| File | Phase | Changes |
|---|---|---|
| incognito-detector.js | 1 | Refactored to lazy initialization with exported functions |
| context-store.js | 2 | Removed incognito-detector dependency, use localStorage directly |
| api-client.js | 3 | Updated to import lazy functions from incognito-detector |
| ds-project-selector.js | 4 | Fixed memory leak, simplified event listeners |
Migration Notes
For Developers
-
Import Pattern Change:
// Old (no longer works) import incognitoDetector from './incognito-detector.js'; const item = incognitoDetector.getItem('key'); // New import { getItem } from './incognito-detector.js'; const item = getItem('key'); -
Direct Storage Access:
- For context persistence (not auth): Use
localStoragedirectly - For token persistence (auth): Use
getItem/setItemfrom incognito-detector - For session data (incognito): Use
getStorage()to get appropriate storage
- For context persistence (not auth): Use
-
Event Listener Pattern:
- Always store listener references for cleanup
- Always remove listeners in disconnectedCallback
- Avoid anonymous function listeners that can't be removed
Performance Impact
- ✅ Faster Initial Load: Incognito detection deferred
- ✅ Fewer Cascading Errors: Isolated failure points
- ✅ Reduced Memory: Proper cleanup prevents listener leaks
- ✅ Cleaner Code: Simpler module imports and usage
Rollback Plan
If issues are discovered:
- Phase 1 (incognito-detector): Revert to class-based singleton
- Phase 2 (context-store): Add back incognito-detector import
- Phase 3 (api-client): Change to default import
- Phase 4 (ds-project-selector): Restore button cloneNode pattern
However, given the root cause analysis, these changes address the core issue and should eliminate the "not working at all" problem.
Next Steps (Optional)
Phase 5: Create Application Bootstrapper
- Create
/js/app-bootstrap.jsfor explicit initialization control - Verify module health before allowing app to load
- Provide better error reporting for initialization failures
Phase 6: Add Error Boundaries
- Wrap component initialization in try-catch
- Gracefully handle partial failures
- Display user-friendly error messages
Phase 7: Add Initialization Monitoring
- Log module initialization order
- Track initialization timing
- Detect initialization bottlenecks
Conclusion
The comprehensive redesign successfully addresses the root cause of the cascading initialization failure that was making the project selector "not working at all." By refactoring module initialization patterns across 4 critical phases:
- ✅ Incognito-detector now uses lazy initialization
- ✅ Context-store eliminated circular dependency
- ✅ API-client properly imports lazy functions
- ✅ Project selector fixed memory leaks and simplified logic
The application should now:
- Load reliably in all contexts (normal, incognito, etc.)
- Handle authentication changes gracefully
- Properly clean up resources
- Have clearer separation of concerns
- Be more maintainable and testable
Architecture Redesign Status: ✅ COMPLETE Ready for Testing: YES Production Ready: Pending verification testing