diff --git a/packages/components/src/components/settings/desktop-settings-modal.tsx b/packages/components/src/components/settings/desktop-settings-modal.tsx index d3b8e8996..2ebd99a44 100644 --- a/packages/components/src/components/settings/desktop-settings-modal.tsx +++ b/packages/components/src/components/settings/desktop-settings-modal.tsx @@ -1,7 +1,8 @@ -import { useCallback, useId } from 'react'; +import { lazy, Suspense, useCallback, useId } from 'react'; import { Bug } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { useAtom, useSetAtom } from 'jotai'; +import type { MachineId } from '@lody/shared'; import { bugReportDialogOpenAtom, settingsActiveTabAtom, @@ -24,20 +25,57 @@ import { type SettingsTabId, } from './settings-tabs'; import { SettingsAccountEntry } from './settings-account-entry'; -import { GeneralSettingsComponent } from './general-setting'; -import { AppearanceSettingsComponent } from './appearance-setting'; -import { AccountSettingsComponent } from './account-setting'; -import { BillingSettingsComponent } from './billing-setting'; -import { StatsSettingsComponent } from './stats-setting'; -import { ProjectSettingsComponent } from './project-settings'; -import { MachineAgentSettings } from './machine-agent-settings'; -import { IntegrationsSettingsComponent } from './integrations-setting'; -import { KeyboardShortcutsSetting } from './keyboard-shortcuts-setting'; -import { AboutSettingsComponent } from './about-setting'; -import { AgentRolesSetting } from './agent-roles-setting'; -import { McpSetting } from './mcp-setting'; import { FocusScope, useListKeyboardNavigation } from '@/ui/focus-scope'; +const GeneralSettingsComponent = lazy(async () => { + const module = await import('./general-setting'); + return { default: module.GeneralSettingsComponent }; +}); +const AppearanceSettingsComponent = lazy(async () => { + const module = await import('./appearance-setting'); + return { default: module.AppearanceSettingsComponent }; +}); +const AccountSettingsComponent = lazy(async () => { + const module = await import('./account-setting'); + return { default: module.AccountSettingsComponent }; +}); +const BillingSettingsComponent = lazy(async () => { + const module = await import('./billing-setting'); + return { default: module.BillingSettingsComponent }; +}); +const StatsSettingsComponent = lazy(async () => { + const module = await import('./stats-setting'); + return { default: module.StatsSettingsComponent }; +}); +const ProjectSettingsComponent = lazy(async () => { + const module = await import('./project-settings'); + return { default: module.ProjectSettingsComponent }; +}); +const MachineAgentSettings = lazy(async () => { + const module = await import('./machine-agent-settings'); + return { default: module.MachineAgentSettings }; +}); +const IntegrationsSettingsComponent = lazy(async () => { + const module = await import('./integrations-setting'); + return { default: module.IntegrationsSettingsComponent }; +}); +const KeyboardShortcutsSetting = lazy(async () => { + const module = await import('./keyboard-shortcuts-setting'); + return { default: module.KeyboardShortcutsSetting }; +}); +const AboutSettingsComponent = lazy(async () => { + const module = await import('./about-setting'); + return { default: module.AboutSettingsComponent }; +}); +const AgentRolesSetting = lazy(async () => { + const module = await import('./agent-roles-setting'); + return { default: module.AgentRolesSetting }; +}); +const McpSetting = lazy(async () => { + const module = await import('./mcp-setting'); + return { default: module.McpSetting }; +}); + /** * Desktop-only settings overlay. Mounted once at the app level (like the bug-report * dialog) and shown whenever `settingsDialogOpenAtom` is set on a non-mobile viewport. @@ -257,6 +295,26 @@ function SettingsTabContent({ tabId }: { tabId: SettingsTabId }) { // so Account shortcuts can select a machine before switching tabs. const [selectedMachineId, setSelectedMachineId] = useAtom(settingsSelectedMachineIdAtom); + return ( + + + + ); +} + +function SettingsTabBody({ + tabId, + selectedMachineId, + onSelectedMachineChange, +}: { + tabId: SettingsTabId; + selectedMachineId: MachineId | null; + onSelectedMachineChange: (machineId: MachineId | null) => void; +}) { switch (tabId) { case 'preferences': return ; @@ -279,7 +337,7 @@ function SettingsTabContent({ tabId }: { tabId: SettingsTabId }) { ); case 'agent-roles': @@ -291,7 +349,7 @@ function SettingsTabContent({ tabId }: { tabId: SettingsTabId }) { ); case 'github': diff --git a/packages/components/src/routes/$workspaceName/_auth/settings/about.tsx b/packages/components/src/routes/$workspaceName/_auth/settings/about.tsx index 94298539c..0c0a2f33f 100644 --- a/packages/components/src/routes/$workspaceName/_auth/settings/about.tsx +++ b/packages/components/src/routes/$workspaceName/_auth/settings/about.tsx @@ -1,6 +1,20 @@ +import { lazy } from 'react'; import { createFileRoute } from '@tanstack/react-router'; -import { AboutSettingsComponent } from '@/components/settings/about-setting'; +import { RouteSuspense } from '@/components/route-suspense'; + +const LazyAboutSettings = lazy(async () => { + const module = await import('@/components/settings/about-setting'); + return { default: module.AboutSettingsComponent }; +}); export const Route = createFileRoute('/$workspaceName/_auth/settings/about')({ - component: AboutSettingsComponent, + component: AboutSettingsRoute, }); + +function AboutSettingsRoute() { + return ( + + + + ); +} diff --git a/packages/components/src/routes/$workspaceName/_auth/settings/account.tsx b/packages/components/src/routes/$workspaceName/_auth/settings/account.tsx index a1313f0bb..f273e7779 100644 --- a/packages/components/src/routes/$workspaceName/_auth/settings/account.tsx +++ b/packages/components/src/routes/$workspaceName/_auth/settings/account.tsx @@ -1,6 +1,20 @@ +import { lazy } from 'react'; import { createFileRoute } from '@tanstack/react-router'; -import { AccountSettingsComponent } from '@/components/settings/account-setting'; +import { RouteSuspense } from '@/components/route-suspense'; + +const LazyAccountSettings = lazy(async () => { + const module = await import('@/components/settings/account-setting'); + return { default: module.AccountSettingsComponent }; +}); export const Route = createFileRoute('/$workspaceName/_auth/settings/account')({ - component: AccountSettingsComponent, + component: AccountSettingsRoute, }); + +function AccountSettingsRoute() { + return ( + + + + ); +} diff --git a/packages/components/src/routes/$workspaceName/_auth/settings/agent-roles.tsx b/packages/components/src/routes/$workspaceName/_auth/settings/agent-roles.tsx index 57fbd97aa..983838d56 100644 --- a/packages/components/src/routes/$workspaceName/_auth/settings/agent-roles.tsx +++ b/packages/components/src/routes/$workspaceName/_auth/settings/agent-roles.tsx @@ -1,6 +1,20 @@ +import { lazy } from 'react'; import { createFileRoute } from '@tanstack/react-router'; -import { AgentRolesSetting } from '@/components/settings/agent-roles-setting'; +import { RouteSuspense } from '@/components/route-suspense'; + +const LazyAgentRolesSetting = lazy(async () => { + const module = await import('@/components/settings/agent-roles-setting'); + return { default: module.AgentRolesSetting }; +}); export const Route = createFileRoute('/$workspaceName/_auth/settings/agent-roles')({ - component: AgentRolesSetting, + component: AgentRolesSettingsRoute, }); + +function AgentRolesSettingsRoute() { + return ( + + + + ); +} diff --git a/packages/components/src/routes/$workspaceName/_auth/settings/agents.tsx b/packages/components/src/routes/$workspaceName/_auth/settings/agents.tsx index f968971c9..e5e2871ca 100644 --- a/packages/components/src/routes/$workspaceName/_auth/settings/agents.tsx +++ b/packages/components/src/routes/$workspaceName/_auth/settings/agents.tsx @@ -1,7 +1,12 @@ -import { useCallback } from 'react'; +import { lazy, useCallback } from 'react'; import { createFileRoute, useNavigate } from '@tanstack/react-router'; import type { MachineId } from '@lody/shared'; -import { MachineAgentSettings } from '@/components/settings/machine-agent-settings'; +import { RouteSuspense } from '@/components/route-suspense'; + +const LazyMachineAgentSettings = lazy(async () => { + const module = await import('@/components/settings/machine-agent-settings'); + return { default: module.MachineAgentSettings }; +}); type AgentsSearch = { machine?: string }; @@ -29,10 +34,12 @@ function AgentsSettingsRoute() { [navigate, workspaceName] ); return ( - + + + ); } diff --git a/packages/components/src/routes/$workspaceName/_auth/settings/ai-usage.tsx b/packages/components/src/routes/$workspaceName/_auth/settings/ai-usage.tsx index 0b5713b10..f8624c29a 100644 --- a/packages/components/src/routes/$workspaceName/_auth/settings/ai-usage.tsx +++ b/packages/components/src/routes/$workspaceName/_auth/settings/ai-usage.tsx @@ -1,6 +1,20 @@ +import { lazy } from 'react'; import { createFileRoute } from '@tanstack/react-router'; -import { StatsSettingsComponent } from '@/components/settings/stats-setting'; +import { RouteSuspense } from '@/components/route-suspense'; + +const LazyStatsSettings = lazy(async () => { + const module = await import('@/components/settings/stats-setting'); + return { default: module.StatsSettingsComponent }; +}); export const Route = createFileRoute('/$workspaceName/_auth/settings/ai-usage')({ - component: StatsSettingsComponent, + component: AiUsageSettingsRoute, }); + +function AiUsageSettingsRoute() { + return ( + + + + ); +} diff --git a/packages/components/src/routes/$workspaceName/_auth/settings/appearance.tsx b/packages/components/src/routes/$workspaceName/_auth/settings/appearance.tsx index b535e18a1..21135e420 100644 --- a/packages/components/src/routes/$workspaceName/_auth/settings/appearance.tsx +++ b/packages/components/src/routes/$workspaceName/_auth/settings/appearance.tsx @@ -1,6 +1,20 @@ +import { lazy } from 'react'; import { createFileRoute } from '@tanstack/react-router'; -import { AppearanceSettingsComponent } from '@/components/settings/appearance-setting'; +import { RouteSuspense } from '@/components/route-suspense'; + +const LazyAppearanceSettings = lazy(async () => { + const module = await import('@/components/settings/appearance-setting'); + return { default: module.AppearanceSettingsComponent }; +}); export const Route = createFileRoute('/$workspaceName/_auth/settings/appearance')({ - component: AppearanceSettingsComponent, + component: AppearanceSettingsRoute, }); + +function AppearanceSettingsRoute() { + return ( + + + + ); +} diff --git a/packages/components/src/routes/$workspaceName/_auth/settings/billing.tsx b/packages/components/src/routes/$workspaceName/_auth/settings/billing.tsx index 6df94184e..50bd28c05 100644 --- a/packages/components/src/routes/$workspaceName/_auth/settings/billing.tsx +++ b/packages/components/src/routes/$workspaceName/_auth/settings/billing.tsx @@ -1,6 +1,12 @@ +import { lazy } from 'react'; import { createFileRoute, Navigate } from '@tanstack/react-router'; -import { BillingSettingsComponent } from '@/components/settings/billing-setting'; import { isNativeAppShell } from '@/lib/native-platform'; +import { RouteSuspense } from '@/components/route-suspense'; + +const LazyBillingSettings = lazy(async () => { + const module = await import('@/components/settings/billing-setting'); + return { default: module.BillingSettingsComponent }; +}); export const Route = createFileRoute('/$workspaceName/_auth/settings/billing')({ component: BillingSettingsRoute, @@ -20,5 +26,9 @@ export function BillingSettingsRoute() { ); } - return ; + return ( + + + + ); } diff --git a/packages/components/src/routes/$workspaceName/_auth/settings/github.tsx b/packages/components/src/routes/$workspaceName/_auth/settings/github.tsx index 0915b1e90..f23a73a39 100644 --- a/packages/components/src/routes/$workspaceName/_auth/settings/github.tsx +++ b/packages/components/src/routes/$workspaceName/_auth/settings/github.tsx @@ -1,6 +1,20 @@ +import { lazy } from 'react'; import { createFileRoute } from '@tanstack/react-router'; -import { IntegrationsSettingsComponent } from '@/components/settings/integrations-setting'; +import { RouteSuspense } from '@/components/route-suspense'; + +const LazyIntegrationsSettings = lazy(async () => { + const module = await import('@/components/settings/integrations-setting'); + return { default: module.IntegrationsSettingsComponent }; +}); export const Route = createFileRoute('/$workspaceName/_auth/settings/github')({ - component: IntegrationsSettingsComponent, + component: GithubSettingsRoute, }); + +function GithubSettingsRoute() { + return ( + + + + ); +} diff --git a/packages/components/src/routes/$workspaceName/_auth/settings/keyboard-shortcuts.tsx b/packages/components/src/routes/$workspaceName/_auth/settings/keyboard-shortcuts.tsx index b7cc19613..711b5470d 100644 --- a/packages/components/src/routes/$workspaceName/_auth/settings/keyboard-shortcuts.tsx +++ b/packages/components/src/routes/$workspaceName/_auth/settings/keyboard-shortcuts.tsx @@ -1,6 +1,20 @@ +import { lazy } from 'react'; import { createFileRoute } from '@tanstack/react-router'; -import { KeyboardShortcutsSetting } from '@/components/settings/keyboard-shortcuts-setting'; +import { RouteSuspense } from '@/components/route-suspense'; + +const LazyKeyboardShortcutsSetting = lazy(async () => { + const module = await import('@/components/settings/keyboard-shortcuts-setting'); + return { default: module.KeyboardShortcutsSetting }; +}); export const Route = createFileRoute('/$workspaceName/_auth/settings/keyboard-shortcuts')({ - component: KeyboardShortcutsSetting, + component: KeyboardShortcutsSettingsRoute, }); + +function KeyboardShortcutsSettingsRoute() { + return ( + + + + ); +} diff --git a/packages/components/src/routes/$workspaceName/_auth/settings/machines.tsx b/packages/components/src/routes/$workspaceName/_auth/settings/machines.tsx index f43c940c4..4f847b7a4 100644 --- a/packages/components/src/routes/$workspaceName/_auth/settings/machines.tsx +++ b/packages/components/src/routes/$workspaceName/_auth/settings/machines.tsx @@ -1,7 +1,12 @@ -import { useCallback } from 'react'; +import { lazy, useCallback } from 'react'; import { createFileRoute, useNavigate } from '@tanstack/react-router'; import type { MachineId } from '@lody/shared'; -import { MachineAgentSettings } from '@/components/settings/machine-agent-settings'; +import { RouteSuspense } from '@/components/route-suspense'; + +const LazyMachineAgentSettings = lazy(async () => { + const module = await import('@/components/settings/machine-agent-settings'); + return { default: module.MachineAgentSettings }; +}); type MachinesSearch = { machine?: string }; @@ -30,10 +35,12 @@ function MachinesSettingsRoute() { ); return ( - + + + ); } diff --git a/packages/components/src/routes/$workspaceName/_auth/settings/mcp.tsx b/packages/components/src/routes/$workspaceName/_auth/settings/mcp.tsx index 799899c73..ddb601590 100644 --- a/packages/components/src/routes/$workspaceName/_auth/settings/mcp.tsx +++ b/packages/components/src/routes/$workspaceName/_auth/settings/mcp.tsx @@ -1,6 +1,20 @@ +import { lazy } from 'react'; import { createFileRoute } from '@tanstack/react-router'; -import { McpSetting } from '@/components/settings/mcp-setting'; +import { RouteSuspense } from '@/components/route-suspense'; + +const LazyMcpSetting = lazy(async () => { + const module = await import('@/components/settings/mcp-setting'); + return { default: module.McpSetting }; +}); export const Route = createFileRoute('/$workspaceName/_auth/settings/mcp')({ - component: McpSetting, + component: McpSettingsRoute, }); + +function McpSettingsRoute() { + return ( + + + + ); +} diff --git a/packages/components/src/routes/$workspaceName/_auth/settings/people.tsx b/packages/components/src/routes/$workspaceName/_auth/settings/people.tsx index a03557c1c..1aa3cb316 100644 --- a/packages/components/src/routes/$workspaceName/_auth/settings/people.tsx +++ b/packages/components/src/routes/$workspaceName/_auth/settings/people.tsx @@ -1,10 +1,20 @@ +import { lazy } from 'react'; import { createFileRoute } from '@tanstack/react-router'; -import { AccountSettingsComponent } from '@/components/settings/account-setting'; +import { RouteSuspense } from '@/components/route-suspense'; + +const LazyAccountSettings = lazy(async () => { + const module = await import('@/components/settings/account-setting'); + return { default: module.AccountSettingsComponent }; +}); export const Route = createFileRoute('/$workspaceName/_auth/settings/people')({ component: PeopleSettingsRoute, }); function PeopleSettingsRoute() { - return ; + return ( + + + + ); } diff --git a/packages/components/src/routes/$workspaceName/_auth/settings/preferences.tsx b/packages/components/src/routes/$workspaceName/_auth/settings/preferences.tsx index 19d618c82..23ab929c5 100644 --- a/packages/components/src/routes/$workspaceName/_auth/settings/preferences.tsx +++ b/packages/components/src/routes/$workspaceName/_auth/settings/preferences.tsx @@ -1,6 +1,20 @@ +import { lazy } from 'react'; import { createFileRoute } from '@tanstack/react-router'; -import { GeneralSettingsComponent } from '@/components/settings/general-setting'; +import { RouteSuspense } from '@/components/route-suspense'; + +const LazyGeneralSettings = lazy(async () => { + const module = await import('@/components/settings/general-setting'); + return { default: module.GeneralSettingsComponent }; +}); export const Route = createFileRoute('/$workspaceName/_auth/settings/preferences')({ - component: GeneralSettingsComponent, + component: PreferencesSettingsRoute, }); + +function PreferencesSettingsRoute() { + return ( + + + + ); +} diff --git a/packages/components/src/routes/$workspaceName/_auth/settings/projects.tsx b/packages/components/src/routes/$workspaceName/_auth/settings/projects.tsx index 5a10ed706..4d209c275 100644 --- a/packages/components/src/routes/$workspaceName/_auth/settings/projects.tsx +++ b/packages/components/src/routes/$workspaceName/_auth/settings/projects.tsx @@ -1,6 +1,12 @@ +import { lazy } from 'react'; import { createFileRoute } from '@tanstack/react-router'; import type { MachineId } from '@lody/shared'; -import { ProjectSettingsComponent } from '@/components/settings/project-settings'; +import { RouteSuspense } from '@/components/route-suspense'; + +const LazyProjectSettings = lazy(async () => { + const module = await import('@/components/settings/project-settings'); + return { default: module.ProjectSettingsComponent }; +}); export const Route = createFileRoute('/$workspaceName/_auth/settings/projects')({ component: ProjectSettingsRoute, @@ -13,9 +19,11 @@ export const Route = createFileRoute('/$workspaceName/_auth/settings/projects')( function ProjectSettingsRoute() { const search = Route.useSearch(); return ( - + + + ); } diff --git a/packages/components/src/routes/$workspaceName/_auth/settings/workspace.tsx b/packages/components/src/routes/$workspaceName/_auth/settings/workspace.tsx index b0a20e90a..39bff16c1 100644 --- a/packages/components/src/routes/$workspaceName/_auth/settings/workspace.tsx +++ b/packages/components/src/routes/$workspaceName/_auth/settings/workspace.tsx @@ -1,10 +1,20 @@ +import { lazy } from 'react'; import { createFileRoute } from '@tanstack/react-router'; -import { AccountSettingsComponent } from '@/components/settings/account-setting'; +import { RouteSuspense } from '@/components/route-suspense'; + +const LazyAccountSettings = lazy(async () => { + const module = await import('@/components/settings/account-setting'); + return { default: module.AccountSettingsComponent }; +}); export const Route = createFileRoute('/$workspaceName/_auth/settings/workspace')({ component: WorkspaceGeneralSettingsRoute, }); function WorkspaceGeneralSettingsRoute() { - return ; + return ( + + + + ); } diff --git a/packages/components/tests/billing-settings-route.test.tsx b/packages/components/tests/billing-settings-route.test.tsx index 9159b184e..4ae4a7f2d 100644 --- a/packages/components/tests/billing-settings-route.test.tsx +++ b/packages/components/tests/billing-settings-route.test.tsx @@ -68,15 +68,18 @@ describe('BillingSettingsRoute', () => { it.each([ ['iOS Safari', 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15'], ['Android Chrome', 'Mozilla/5.0 (Linux; Android 15; Pixel 9) AppleWebKit/537.36 Chrome/130'], - ])('renders billing settings in mobile %s', (_browser, userAgent) => { + ])('renders billing settings in mobile %s', async (_browser, userAgent) => { Object.defineProperty(window.navigator, 'userAgent', { configurable: true, value: userAgent, }); - act(() => root.render(createElement(BillingSettingsRoute))); - - expect(container.textContent).toBe('billing-settings'); + await act(async () => { + root.render(createElement(BillingSettingsRoute)); + }); + await vi.waitFor(() => { + expect(container.textContent).toBe('billing-settings'); + }); expect(routerState.navigateProps).toBeNull(); });