1. Update `app-store.js` to add project management state and actions. 2. Create `ds-project-selector.js` to handle project switching in the UI. 3. Create the 6 module placeholder files for the new architecture. 4. Update `router.js` to map routes to these new modules. 5. Update `ds-shell.js` to implement the new sidebar navigation and layout. /** * Design System Server (DSS) - App Store * * Centralized state management with reactive subscriptions * for managing application state across components and pages. */ class AppStore { constructor() { this.state = { // User & Auth user: null, team: null, role: null, // Navigation currentPage: 'dashboard', currentProject: null, // New: Selected Project Context sidebarOpen: true, // Data projects: [], // New: List of available projects tokens: [], components: [], styles: [], // Discovery discovery: null, health: null, activity: [], stats: null, // Figma figmaConnected: false, figmaFileKey: null, lastSync: null, // Configuration (loaded from server /api/config) serverConfig: null, isConfigLoading: true, configError: null, // UI State loading: {}, errors: {}, notifications: [] }; this.listeners = new Map(); this.middleware = []; } // === State Access === get(key) { return key ? this.state[key] : this.state; } // === State Updates === set(updates, silent = false) { const prevState = { ...this.state }; // Apply middleware for (const mw of this.middleware) { updates = mw(updates, prevState); } this.state = { ...this.state, ...updates }; if (!silent) { this._notify(updates, prevState); } } // === Subscriptions === subscribe(key, callback) { if (!this.listeners.has(key)) { this.listeners.set(key, new Set()); } this.listeners.get(key).add(callback); // Return unsubscribe function return () => { this.listeners.get(key)?.delete(callback); }; } subscribeAll(callback) { return this.subscribe('*', callback); } _notify(updates, prevState) { // Notify specific key listeners for (const key of Object.keys(updates)) { const listeners = this.listeners.get(key); if (listeners) { listeners.forEach(cb => cb(updates[key], prevState[key], key)); } } // Notify global listeners const globalListeners = this.listeners.get('*'); if (globalListeners) { globalListeners.forEach(cb => cb(this.state, prevState)); } } // === Middleware === use(middleware) { this.middleware.push(middleware); } // === Loading State === setLoading(key, loading = true) { this.set({ loading: { ...this.state.loading, [key]: loading } }); } isLoading(key) { return this.state.loading[key] || false; } // === Error State === setError(key, error) { this.set({ errors: { ...this.state.errors, [key]: error } }); } clearError(key) { const errors = { ...this.state.errors }; delete errors[key]; this.set({ errors }); } // === Notifications === notify(message, type = 'info', duration = 5000) { const notification = { id: Date.now(), message, type, timestamp: new Date() }; this.set({ notifications: [...this.state.notifications, notification] }); if (duration > 0) { setTimeout(() => this.dismissNotification(notification.id), duration); } return notification.id; } dismissNotification(id) { this.set({ notifications: this.state.notifications.filter(n => n.id !== id) }); } // === User & Auth === setUser(user, team = null, role = null) { this.set({ user, team, role }); } logout() { this.set({ user: null, team: null, role: null, projects: [], tokens: [], components: [], currentProject: null }); localStorage.removeItem('currentProject'); } hasPermission(permission) { const rolePermissions = { SUPER_ADMIN: ['*'], TEAM_LEAD: ['read', 'write', 'sync', 'manage_team'], DEVELOPER: ['read', 'write', 'sync'], VIEWER: ['read'] }; const perms = rolePermissions[this.state.role] || []; return perms.includes('*') || perms.includes(permission); } // === Projects (Enhanced for Phase 1) === /** * Fetch all projects from API */ async fetchProjects() { this.setLoading('projects', true); try { // Check for auth token (assuming basic auth structure or cookie based for now) // If a specific token property exists on the store, use it. // For now, we assume credentials are handled via cookies/standard fetch const headers = { 'Content-Type': 'application/json' }; const response = await fetch('/api/projects', { headers }); if (!response.ok) throw new Error(`API Error: ${response.status}`); const json = await response.json(); if (json.status === 'success') { this.setProjects(json.data.projects || []); // Auto-select first project if none selected and projects exist if (!this.state.currentProject && json.data.projects.length > 0) { // Check localStorage first const stored = localStorage.getItem('currentProject'); if (stored) { try { const parsed = JSON.parse(stored); const exists = json.data.projects.find(p => p.id === parsed.id); if (exists) this.setProject(exists); } catch (e) { console.warn('Invalid stored project', e); } } } } else { throw new Error(json.message || 'Failed to fetch projects'); } } catch (error) { console.error('Project fetch error:', error); this.setError('projects', error.message); // Fallback for dev/demo mode if API fails if (window.location.hostname === 'localhost' && this.state.projects.length === 0) { console.warn('Using mock projects for development'); this.setProjects([ { id: 'proj_default', name: 'Default Design System' }, { id: 'proj_mobile', name: 'Mobile App DS' } ]); } } finally { this.setLoading('projects', false); } } setProjects(projects) { this.set({ projects }); } setProject(project) { this.set({ currentProject: project }); if (project) { localStorage.setItem('currentProject', JSON.stringify(project)); } else { localStorage.removeItem('currentProject'); } } async getProjectConfig() { if (!this.state.currentProject) return null; this.setLoading('config', true); try { const response = await fetch(`/api/config/${this.state.currentProject.id}/resolved`); const json = await response.json(); return json.status === 'success' ? json.data.config : null; } catch (error) { console.error('Config fetch error:', error); return null; } finally { this.setLoading('config', false); } } addProject(project) { this.set({ projects: [...this.state.projects, project] }); } updateProject(id, updates) { this.set({ projects: this.state.projects.map(p => p.id === id ? { ...p, ...updates } : p ) }); } // === Figma === setFigmaConnected(connected, fileKey = null) { this.set({ figmaConnected: connected, figmaFileKey: fileKey }); } setLastSync(timestamp) { this.set({ lastSync: timestamp }); } // === Tokens === setTokens(tokens) { this.set({ tokens }); } getTokensByCategory(category) { return this.state.tokens.filter(t => t.category === category); } // === Components === setComponents(components) { this.set({ components }); } // === Discovery === setDiscovery(discovery) { this.set({ discovery }); } setHealth(health) { this.set({ health }); } setActivity(activity) { this.set({ activity }); } setStats(stats) { this.set({ stats }); } // === Persistence === persist() { const toPersist = { user: this.state.user, team: this.state.team, role: this.state.role, figmaFileKey: this.state.figmaFileKey, sidebarOpen: this.state.sidebarOpen // Note: currentProject is persisted separately in setProject }; localStorage.setItem('dss-store', JSON.stringify(toPersist)); } hydrate() { try { // Hydrate general store data const stored = localStorage.getItem('dss-store'); if (stored) { const data = JSON.parse(stored); this.set(data, true); } // Hydrate Project Context specifically const storedProject = localStorage.getItem('currentProject'); if (storedProject) { const project = JSON.parse(storedProject); this.set({ currentProject: project }, true); } } catch (e) { console.warn('Failed to hydrate store:', e); } } // === Debug === debug() { console.group('App Store State'); console.log('State:', this.state); console.log('Listeners:', Array.from(this.listeners.keys())); console.groupEnd(); } } // Logging middleware const loggingMiddleware = (updates, prevState) => { if (window.DEBUG) { console.log('[Store Update]', updates); } return updates; }; // Create and export singleton const store = new AppStore(); store.use(loggingMiddleware); store.hydrate(); // Auto-persist on important changes store.subscribe('user', () => store.persist()); store.subscribe('team', () => store.persist()); store.subscribe('figmaFileKey', () => store.persist()); export { AppStore }; export default store; /** * ds-project-selector.js * * Dropdown component for switching between Design System projects. * Persists selection to AppStore and LocalStorage. */ import store from '../../stores/app-store.js'; class DSProjectSelector extends HTMLElement { constructor() { super(); this.unsubscribe = null; } connectedCallback() { this.render(); this.setupEventListeners(); // Subscribe to store changes this.unsubscribe = store.subscribe('projects', () => this.updateOptions()); store.subscribe('currentProject', () => this.updateSelection()); store.subscribe('loading', () => this.updateLoadingState()); // Initial fetch this.fetchProjects(); } disconnectedCallback() { if (this.unsubscribe) this.unsubscribe(); } async fetchProjects() { await store.fetchProjects(); } render() { this.innerHTML = `
`; // Initial population if data exists this.updateOptions(); this.updateSelection(); } setupEventListeners() { const select = this.querySelector('#project-dropdown'); if (select) { select.addEventListener('change', (e) => { const projectId = e.target.value; const projects = store.get('projects'); const selectedProject = projects.find(p => p.id === projectId); if (selectedProject) { store.setProject(selectedProject); // Emit event for immediate listeners this.dispatchEvent(new CustomEvent('project-changed', { detail: { project: selectedProject }, bubbles: true, composed: true })); } }); } } updateOptions() { const select = this.querySelector('#project-dropdown'); if (!select) return; const projects = store.get('projects'); const currentProject = store.get('currentProject'); // Keep the placeholder as first option select.innerHTML = ''; if (projects.length === 0) { const emptyOpt = document.createElement('option'); emptyOpt.text = "No projects found"; emptyOpt.disabled = true; select.appendChild(emptyOpt); } else { projects.forEach(proj => { const option = document.createElement('option'); option.value = proj.id; option.textContent = proj.name; select.appendChild(option); }); } // Re-apply selection if exists if (currentProject) { select.value = currentProject.id; } else { select.value = ""; } } updateSelection() { const select = this.querySelector('#project-dropdown'); const currentProject = store.get('currentProject'); if (select && currentProject) { select.value = currentProject.id; } } updateLoadingState() { const isLoading = store.isLoading('projects'); const loader = this.querySelector('#project-loader'); const arrow = this.querySelector('.select-arrow'); if (loader && arrow) { loader.style.display = isLoading ? 'block' : 'none'; arrow.style.display = isLoading ? 'none' : 'block'; } } } customElements.define('ds-project-selector', DSProjectSelector); /** * ProjectsModule.js * Feature module for managing DSS projects, metadata, and selection. */ class ProjectsModule extends HTMLElement { constructor() { super(); } connectedCallback() { this.render(); } render() { this.innerHTML = `

Projects

📁

Projects Module Under Construction

This module will allow you to create new projects, edit project metadata, and manage access controls. Currently, use the selector in the top bar to switch contexts.

`; } } customElements.define('dss-projects-module', ProjectsModule); export default ProjectsModule; /** * ConfigModule.js * Feature module for managing the 3-tier cascade configuration. */ class ConfigModule extends HTMLElement { constructor() { super(); } connectedCallback() { this.render(); } render() { this.innerHTML = `

Configuration

⚙️

Configuration Module Under Construction

Manage your 3-tier configuration cascade here (Base → Brand → Project). Future updates will include visual editors for JSON/YAML config files.

`; } } customElements.define('dss-config-module', ConfigModule); export default ConfigModule; /** * ComponentsModule.js * Feature module for the Component Registry, search, and status tracking. */ class ComponentsModule extends HTMLElement { constructor() { super(); } connectedCallback() { this.render(); } render() { this.innerHTML = `

Components

🧩

Components Registry Under Construction

View component status, health, and documentation links. This module will interface with the registry to show what is available in your design system.

`; } } customElements.define('dss-components-module', ComponentsModule); export default ComponentsModule; /** * TranslationsModule.js * Feature module for managing Legacy → DSS token mappings (Principle #2). */ class TranslationsModule extends HTMLElement { constructor() { super(); } connectedCallback() { this.render(); } render() { this.innerHTML = `

Translation Dictionaries

🔄

Translations Module Under Construction

Core DSS Principle #2: Map legacy design tokens to modern DSS tokens. This interface will allow creation and validation of translation dictionaries.

`; } } customElements.define('dss-translations-module', TranslationsModule); export default TranslationsModule; /** * DiscoveryModule.js * Feature module for scanning projects, activity logs, and analysis. */ class DiscoveryModule extends HTMLElement { constructor() { super(); } connectedCallback() { this.render(); } render() { this.innerHTML = `

Discovery

🔍

Discovery Module Under Construction

Run scanners, view codebase analytics, and monitor system activity. Integration with the scanner backend is coming in the next phase.

`; } } customElements.define('dss-discovery-module', DiscoveryModule); export default DiscoveryModule; /** * AdminModule.js * Feature module for RBAC, user management, and system audit logs. */ class AdminModule extends HTMLElement { constructor() { super(); } connectedCallback() { this.render(); } render() { this.innerHTML = `

Administration

👤

Admin Module Under Construction

Manage users, permissions (RBAC), and view audit logs. Only available to users with administrative privileges.

`; } } customElements.define('dss-admin-module', AdminModule); export default AdminModule; /** * DSS Router * * Centralized hash-based routing with guards, lifecycle hooks, and * declarative route definitions for enterprise-grade navigation management. * * @module router */ import { notifyError, ErrorCode } from './messaging.js'; /** * Route configuration object * @typedef {Object} RouteConfig * @property {string} path - Route path (e.g., '/dashboard', '/projects') * @property {string} name - Route name for programmatic navigation * @property {Function} handler - Route handler function * @property {Function} [beforeEnter] - Guard called before entering route * @property {Function} [afterEnter] - Hook called after entering route * @property {Function} [onLeave] - Hook called when leaving route * @property {Object} [meta] - Route metadata */ /** * Router class for centralized route management */ class Router { constructor() { this.routes = new Map(); this.currentRoute = null; this.previousRoute = null; this.defaultRoute = 'projects'; // Updated default route for new architecture this.isNavigating = false; // Bind handlers this.handleHashChange = this.handleHashChange.bind(this); this.handlePopState = this.handlePopState.bind(this); } /** * Initialize the router */ init() { // Define Core Routes for New Architecture this.registerAll([ { path: '/projects', name: 'Projects', handler: () => this.loadModule('dss-projects-module', () => import('../modules/projects/ProjectsModule.js')) }, { path: '/config', name: 'Configuration', handler: () => this.loadModule('dss-config-module', () => import('../modules/config/ConfigModule.js')) }, { path: '/components', name: 'Components', handler: () => this.loadModule('dss-components-module', () => import('../modules/components/ComponentsModule.js')) }, { path: '/translations', name: 'Translations', handler: () => this.loadModule('dss-translations-module', () => import('../modules/translations/TranslationsModule.js')) }, { path: '/discovery', name: 'Discovery', handler: () => this.loadModule('dss-discovery-module', () => import('../modules/discovery/DiscoveryModule.js')) }, { path: '/admin', name: 'Admin', handler: () => this.loadModule('dss-admin-module', () => import('../modules/admin/AdminModule.js')) } ]); // Listen for hash changes window.addEventListener('hashchange', this.handleHashChange); window.addEventListener('popstate', this.handlePopState); // Handle initial route this.handleHashChange(); } /** * Helper to load dynamic modules into the stage * @param {string} tagName - Custom element tag name * @param {Function} importFn - Dynamic import function */ async loadModule(tagName, importFn) { try { // 1. Load the module file await importFn(); // 2. Update the stage content const stageContent = document.querySelector('#stage-workdesk-content'); if (stageContent) { stageContent.innerHTML = ''; const element = document.createElement(tagName); stageContent.appendChild(element); } } catch (error) { console.error(`Failed to load module ${tagName}:`, error); notifyError(`Failed to load module`, ErrorCode.SYSTEM_UNEXPECTED); } } /** * Register a route * @param {RouteConfig} config - Route configuration */ register(config) { if (!config.path) { throw new Error('Route path is required'); } if (!config.handler) { throw new Error('Route handler is required'); } // Normalize path (remove leading slash for hash routing) const path = config.path.replace(/^\//, ''); this.routes.set(path, { path, name: config.name || path, handler: config.handler, beforeEnter: config.beforeEnter || null, afterEnter: config.afterEnter || null, onLeave: config.onLeave || null, meta: config.meta || {}, }); return this; } /** * Register multiple routes * @param {RouteConfig[]} routes - Array of route configurations */ registerAll(routes) { routes.forEach(route => this.register(route)); return this; } /** * Set default route * @param {string} path - Default route path */ setDefaultRoute(path) { this.defaultRoute = path.replace(/^\//, ''); return this; } /** * Navigate to a route * @param {string} path - Route path or name * @param {Object} [options] - Navigation options * @param {boolean} [options.replace] - Replace history instead of push * @param {Object} [options.state] - State to pass to route */ navigate(path, options = {}) { const normalizedPath = path.replace(/^\//, ''); // Update hash if (options.replace) { window.location.replace(`#${normalizedPath}`); } else { window.location.hash = normalizedPath; } return this; } /** * Navigate to a route by name * @param {string} name - Route name * @param {Object} [options] - Navigation options */ navigateByName(name, options = {}) { const route = Array.from(this.routes.values()).find(r => r.name === name); if (!route) { notifyError(`Route not found: ${name}`, ErrorCode.SYSTEM_UNEXPECTED); return this; } return this.navigate(route.path, options); } /** * Handle hash change event */ async handleHashChange() { if (this.isNavigating) return; this.isNavigating = true; try { // Get path from hash let path = window.location.hash.replace(/^#/, '') || this.defaultRoute; // Extract route and params const { routePath, params } = this.parseRoute(path); // Find route const route = this.routes.get(routePath); if (!route) { console.warn(`Route not registered: ${routePath}, falling back to default`); this.navigate(this.defaultRoute, { replace: true }); return; } // Call onLeave hook for previous route if (this.currentRoute && this.currentRoute.onLeave) { await this.callHook(this.currentRoute.onLeave, { from: this.currentRoute, to: route }); } // Call beforeEnter guard if (route.beforeEnter) { const canEnter = await this.callGuard(route.beforeEnter, { route, params }); if (!canEnter) { // Guard rejected, stay on current route or go to default if (this.currentRoute) { this.navigate(this.currentRoute.path, { replace: true }); } else { this.navigate(this.defaultRoute, { replace: true }); } return; } } // Update route tracking this.previousRoute = this.currentRoute; this.currentRoute = route; // Call route handler await route.handler({ route, params, router: this }); // Call afterEnter hook if (route.afterEnter) { await this.callHook(route.afterEnter, { route, params, from: this.previousRoute }); } // Emit route change event this.emitRouteChange(route, params); } catch (error) { console.error('Router navigation error:', error); notifyError('Navigation failed', ErrorCode.SYSTEM_UNEXPECTED, { path: window.location.hash, error: error.message, }); } finally { this.isNavigating = false; } } /** * Handle popstate event (browser back/forward) */ handlePopState(event) { this.handleHashChange(); } /** * Parse route path and extract params * @param {string} path - Route path * @returns {Object} Route path and params */ parseRoute(path) { // For now, simple implementation - just return the path // Can be extended to support params like '/projects/:id' const [routePath, queryString] = path.split('?'); const params = {}; if (queryString) { new URLSearchParams(queryString).forEach((value, key) => { params[key] = value; }); } return { routePath, params }; } /** * Call a route guard * @param {Function} guard - Guard function * @param {Object} context - Guard context * @returns {Promise} Whether navigation should proceed */ async callGuard(guard, context) { try { const result = await guard(context); return result !== false; // Undefined or true = proceed } catch (error) { console.error('Route guard error:', error); return false; } } /** * Call a lifecycle hook * @param {Function} hook - Hook function * @param {Object} context - Hook context */ async callHook(hook, context) { try { await hook(context); } catch (error) { console.error('Route hook error:', error); } } /** * Emit route change event * @param {Object} route - Current route * @param {Object} params - Route params */ emitRouteChange(route, params) { window.dispatchEvent(new CustomEvent('route-changed', { detail: { route, params, previous: this.previousRoute, } })); } /** * Get current route * @returns {Object|null} Current route config */ getCurrentRoute() { return this.currentRoute; } /** * Get previous route * @returns {Object|null} Previous route config */ getPreviousRoute() { return this.previousRoute; } /** * Check if a route exists * @param {string} path - Route path * @returns {boolean} Whether route exists */ hasRoute(path) { const normalizedPath = path.replace(/^\//, ''); return this.routes.has(normalizedPath); } /** * Get route by path * @param {string} path - Route path * @returns {Object|null} Route config */ getRoute(path) { const normalizedPath = path.replace(/^\//, ''); return this.routes.get(normalizedPath) || null; } /** * Get all routes * @returns {Array} All registered routes */ getAllRoutes() { return Array.from(this.routes.values()); } /** * Go back in history */ back() { window.history.back(); return this; } /** * Go forward in history */ forward() { window.history.forward(); return this; } /** * Destroy the router (cleanup) */ destroy() { window.removeEventListener('hashchange', this.handleHashChange); window.removeEventListener('popstate', this.handlePopState); this.routes.clear(); this.currentRoute = null; this.previousRoute = null; } } // Singleton instance const router = new Router(); // Export both the instance and the class export { Router }; export default router; /** * Common route guards */ export const guards = { /** * Require authentication */ requireAuth({ route }) { // Check if user is authenticated // For now, always allow (implement auth later) return true; }, /** * Require specific permission */ requirePermission(permission) { return ({ route }) => { // Check if user has permission // For now, always allow (implement RBAC later) return true; }; }, /** * Require project selected */ requireProject({ route, params }) { const projectId = localStorage.getItem('currentProject'); if (!projectId) { notifyError('Please select a project first', ErrorCode.USER_ACTION_FORBIDDEN); return false; } return true; }, }; /** * ds-shell.js * Main shell component - provides IDE-style grid layout * Refactored for Phase 1 Architecture (Feature-based Modules) */ import './ds-activity-bar.js'; import './ds-panel.js'; import './ds-project-selector.js'; // New Project Selector import './ds-ai-chat-sidebar.js'; import '../admin/ds-user-settings.js'; import '../ds-notification-center.js'; import router from '../../core/router.js'; // Import Router import layoutManager from '../../core/layout-manager.js'; import notificationService from '../../services/notification-service.js'; import { useAdminStore } from '../../stores/admin-store.js'; import { useProjectStore } from '../../stores/project-store.js'; import { useUserStore } from '../../stores/user-store.js'; import '../../config/component-registry.js'; import { authReady } from '../../utils/demo-auth-init.js'; class DSShell extends HTMLElement { constructor() { super(); this.browserInitialized = false; this.currentView = 'module'; // MVP2: Initialize stores this.adminStore = useAdminStore(); this.projectStore = useProjectStore(); this.userStore = useUserStore(); } async connectedCallback() { // Render UI immediately (non-blocking) this.render(); this.setupEventListeners(); // Initialize layout manager // Keep this for now to maintain layout structure, though "workdesks" are being phased out if (layoutManager && typeof layoutManager.init === 'function') { layoutManager.init(this); } // Initialize Router router.init(); // Wait for authentication console.log('[DSShell] Waiting for authentication...'); const authResult = await authReady; console.log('[DSShell] Authentication complete:', authResult); // Initialize services notificationService.init(); // Set initial active link this.updateActiveLink(); } render() { this.innerHTML = `
`; } setupEventListeners() { this.setupMobileMenu(); this.setupHeaderActions(); this.setupNavigationHighlight(); // Listen for route changes to update UI window.addEventListener('route-changed', (e) => { this.updateActiveLink(e.detail.route.path); }); } setupNavigationHighlight() { const navItems = this.querySelectorAll('.nav-item'); navItems.forEach(item => { item.addEventListener('mouseenter', (e) => { if (!e.target.classList.contains('active')) { e.target.style.background = 'var(--vscode-list-hoverBackground, rgba(255,255,255,0.1))'; e.target.style.color = 'var(--vscode-text)'; } }); item.addEventListener('mouseleave', (e) => { if (!e.target.classList.contains('active')) { e.target.style.background = 'transparent'; e.target.style.color = 'var(--vscode-text-dim)'; } }); }); } updateActiveLink(path) { const currentPath = path || (window.location.hash.replace('#', '') || 'projects'); const navItems = this.querySelectorAll('.nav-item'); navItems.forEach(item => { const itemPath = item.dataset.path; if (itemPath === currentPath) { item.classList.add('active'); item.style.background = 'var(--vscode-selection)'; item.style.color = 'var(--vscode-accent)'; item.style.fontWeight = '500'; } else { item.classList.remove('active'); item.style.background = 'transparent'; item.style.color = 'var(--vscode-text-dim)'; item.style.fontWeight = 'normal'; } }); } setupHeaderActions() { const stageActions = this.querySelector('#stage-actions'); if (!stageActions) return; stageActions.innerHTML = `
`; // Re-attach listeners for these buttons similar to original implementation // Simplified for brevity, ensuring critical paths work const chatBtn = this.querySelector('#chat-toggle-btn'); chatBtn?.addEventListener('click', () => { const sidebar = this.querySelector('ds-ai-chat-sidebar'); if (sidebar && sidebar.toggleCollapse) sidebar.toggleCollapse(); }); const notifBtn = this.querySelector('#notification-toggle-btn'); const notifCenter = this.querySelector('ds-notification-center'); notifBtn?.addEventListener('click', (e) => { e.stopPropagation(); if (notifCenter) { if (notifCenter.hasAttribute('open')) notifCenter.removeAttribute('open'); else notifCenter.setAttribute('open', ''); } }); // Close notifications on outside click document.addEventListener('click', (e) => { if (notifCenter && !notifCenter.contains(e.target) && !notifBtn.contains(e.target)) { notifCenter.removeAttribute('open'); } }); const settingsBtn = this.querySelector('#settings-btn'); settingsBtn?.addEventListener('click', () => { // Simple navigation to admin/settings router.navigate('admin'); }); } setupMobileMenu() { const hamburgerBtn = this.querySelector('#hamburger-menu'); const sidebar = this.querySelector('ds-sidebar'); if (hamburgerBtn && sidebar) { // Update visibility based on screen size const checkSize = () => { if (window.innerWidth <= 768) { hamburgerBtn.style.display = 'block'; } else { hamburgerBtn.style.display = 'none'; sidebar.classList.remove('mobile-open'); } }; window.addEventListener('resize', checkSize); checkSize(); hamburgerBtn.addEventListener('click', () => { sidebar.classList.toggle('mobile-open'); }); } } // Getters for compatibility with any legacy code looking for these elements get sidebarContent() { return this.querySelector('.sidebar-content'); } get stageContent() { return this.querySelector('#stage-workdesk-content'); } } customElements.define('ds-shell', DSShell);