diff --git a/frontend/src/components/plugins/PluginLoader.tsx b/frontend/src/components/plugins/PluginLoader.tsx index d874416..46c84fd 100644 --- a/frontend/src/components/plugins/PluginLoader.tsx +++ b/frontend/src/components/plugins/PluginLoader.tsx @@ -91,6 +91,62 @@ class PluginErrorBoundary extends Component & { default?: React.ComponentType }; +type LazyComponentFactory = () => Promise<{ default: React.ComponentType }>; + +/** + * Normalize a module namespace to the { default } shape React.lazy expects: + * uses the default export when present, otherwise the first named export + * (same fallback as the runtime dynamic-import path below). + */ +function normalizeModule(m: ComponentModule): { default: React.ComponentType } { + const Comp = (m.default ?? m[Object.keys(m)[0]]) as React.ComponentType; + return { default: Comp }; +} + +const STATIC_COMPONENT_MAP: Record = { + // Pages + '@/pages/AgentDashboard': () => import('@/pages/AgentDashboard').then(normalizeModule), + // NOTE: @/pages/AIAssistant is declared in the backend manifest but does + // not exist as a frontend page — ghost component, see PROGRESS.md. + '@/pages/AISettings': () => import('@/pages/AISettings').then(normalizeModule), + '@/pages/AutomationDashboard': () => import('@/pages/AutomationDashboard').then(normalizeModule), + '@/pages/AutomationSettings': () => import('@/pages/AutomationSettings').then(normalizeModule), + '@/pages/Calendar': () => import('@/pages/Calendar').then(normalizeModule), + '@/pages/Communication': () => import('@/pages/Communication').then(normalizeModule), + '@/pages/Dms': () => import('@/pages/Dms').then(normalizeModule), + '@/pages/DmsTrash': () => import('@/pages/DmsTrash').then(normalizeModule), + '@/pages/GlobalSearchResults': () => import('@/pages/GlobalSearchResults').then(normalizeModule), + '@/pages/ImportExport': () => import('@/pages/ImportExport').then(normalizeModule), + '@/pages/Mail': () => import('@/pages/Mail').then(normalizeModule), + '@/pages/MailSettings': () => import('@/pages/MailSettings').then(normalizeModule), + '@/pages/ProactiveAISettings': () => import('@/pages/ProactiveAISettings').then(normalizeModule), + '@/pages/Reports': () => import('@/pages/Reports').then(normalizeModule), + '@/pages/SettingsGroups': () => import('@/pages/SettingsGroups').then(normalizeModule), + '@/pages/SettingsNotifications': () => import('@/pages/SettingsNotifications').then(normalizeModule), + '@/pages/SettingsRoles': () => import('@/pages/SettingsRoles').then(normalizeModule), + '@/pages/SettingsUsers': () => import('@/pages/SettingsUsers').then(normalizeModule), + '@/pages/Tasks': () => import('@/pages/Tasks').then(normalizeModule), + '@/pages/Wiki': () => import('@/pages/Wiki').then(normalizeModule), + '@/pages/Workflows': () => import('@/pages/Workflows').then(normalizeModule), + // NOTE: The backend manifests also declare contact detail tabs + // (@/components/contact/Contact*Tab) whose components do not exist in the + // frontend yet — they are intentionally NOT registered here; they fall + // through to the runtime fallback and surface via the error boundary. + // See PROGRESS.md "Ghost components" finding. +}; + // ── Lazy Component Cache ──────────────────────────────────────────────── const componentCache = new Map>>(); @@ -100,8 +156,16 @@ function getLazyComponent(componentPath: string): React.LazyExoticComponent