fix(router): import the Channels and Bots listing tabs eagerly again to kill the web tab-switch flicker regression from their lazy-split

This commit is contained in:
heaven 2026-05-30 13:07:13 +03:00
parent de9ab8198f
commit cdd2570ff1
2 changed files with 25 additions and 17 deletions

View file

@ -4,11 +4,14 @@ import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { isNativePlatform } from '../../utils/capacitor';
import { BOTS_PATH, CHANNELS_PATH, CHANNELS_SPACE_PATH, DIRECT_PATH } from '../../pages/paths';
// MobileTabsPager is the ONLY static importer of the Channels and Bots feature
// modules. It renders only on `mobile && native` (below), so lazy-loading it
// here removes the static edge that otherwise pinned Channels + Bots into the
// boot bundle for every target. On native the chunk streams from the local APK
// filesystem (no network), so a null Suspense fallback is imperceptible.
// MobileTabsPager statically imports the Channels and Bots feature modules and
// renders only on `mobile && native` (below), so lazy-loading it here keeps the
// pager glue (gesture/geometry/header) off non-native targets. NOTE: this no
// longer keeps Channels/Bots off the boot bundle — those listing tabs are now
// imported eagerly in `Router.tsx` (their lazy-split caused a web tab-switch
// flicker for ~5.6 KB gz; see `docs/ai/bugs.md`), so they ship in the entry on
// every target regardless. On native the pager chunk streams from the local APK
// filesystem (no network), so its null Suspense fallback is imperceptible.
const MobileTabsPager = React.lazy(() =>
import('./MobileTabsPager').then((m) => ({ default: m.MobileTabsPager }))
);

View file

@ -55,6 +55,11 @@ import { ClientBindAtoms, ClientLayout, ClientRoot } from './client';
import { HomeRouteRoomProvider } from './client/home';
import { Direct, DirectCreate, DirectRouteRoomProvider } from './client/direct';
import { ChannelPickPlaceholder } from './client/channels/ChannelPickPlaceholder';
// Bots/Channels LISTING tabs are eager (not React.lazy) — see the code-split
// comment below. Imported from the concrete source files (not the barrels) so
// the eager edge doesn't drag the still-lazy BotExperienceHost chunk into boot.
import { Bots } from './client/bots/Bots';
import { Channels, ChannelsRootNav } from './client/channels/Channels';
import { RouteSpaceProvider, Space, SpaceRouteRoomProvider, SpaceSearch } from './client/space';
import { setAfterLoginRedirectPath } from './afterLoginRedirectPath';
import { WelcomePage } from './client/WelcomePage';
@ -127,11 +132,18 @@ function UserLinkRedirect() {
// Route-level code-splitting. Each heavy page is loaded on first navigation
// instead of in the boot bundle — this pulls the timeline (Room), lobby,
// explore, bots and channels subsystems off first load. Imports point at the
// concrete source file (not the barrel) so each chunk stays tight.
// explore, create and the bot-experience host off first load. Imports point at
// the concrete source file (not the barrel) so each chunk stays tight.
// `routeSuspense` wraps every usage so only the content area shows the fallback
// while the chunk streams in — surrounding nav/chrome stays mounted.
//
// The Channels/Bots LISTING tabs are deliberately NOT here — they are imported
// eagerly (top of file). Code-splitting those two tiny chunks (~5.6 KB gz) won
// almost nothing on first load, but made every first switch to the tab suspend
// and paint a fallback in the nav column that reflowed the layout — a visible
// web-only "tab-switch flicker". Eager import restores the pre-split behavior.
// See `docs/ai/bugs.md`.
//
// NOTE: SettingsScreen is intentionally NOT lazy here. The settings UI
// (`features/settings/Settings`) is already pulled into the boot graph by
// `Direct` → `MobileSettingsHorseshoe` (the mobile settings sheet, mounted in
@ -152,16 +164,9 @@ const FeaturedRooms = React.lazy(() =>
const PublicRooms = React.lazy(() =>
import('./client/explore/Server').then((m) => ({ default: m.PublicRooms }))
);
const Bots = React.lazy(() => import('./client/bots/Bots').then((m) => ({ default: m.Bots })));
const BotExperienceHost = React.lazy(() =>
import('./client/bots/BotExperienceHost').then((m) => ({ default: m.BotExperienceHost }))
);
const Channels = React.lazy(() =>
import('./client/channels/Channels').then((m) => ({ default: m.Channels }))
);
const ChannelsRootNav = React.lazy(() =>
import('./client/channels/Channels').then((m) => ({ default: m.ChannelsRootNav }))
);
const Create = React.lazy(() =>
import('./client/create/Create').then((m) => ({ default: m.Create }))
);
@ -313,7 +318,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
<PageRoot
nav={
<MobileFriendlyPageNav path={BOTS_PATH}>
{routeSuspense(<Bots />)}
<Bots />
</MobileFriendlyPageNav>
}
>
@ -343,7 +348,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
<PageRoot
nav={
<MobileFriendlyPageNav path={CHANNELS_PATH}>
{routeSuspense(<ChannelsRootNav />)}
<ChannelsRootNav />
</MobileFriendlyPageNav>
}
>
@ -358,7 +363,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
<PageRoot
nav={
<MobileFriendlyPageNav path={CHANNELS_SPACE_PATH}>
{routeSuspense(<Channels />)}
<Channels />
</MobileFriendlyPageNav>
}
>