diff --git a/src/components/platform-entry/platformEntryTypes.ts b/src/components/platform-entry/platformEntryTypes.ts
index 081c98a8..0b19b98d 100644
--- a/src/components/platform-entry/platformEntryTypes.ts
+++ b/src/components/platform-entry/platformEntryTypes.ts
@@ -2,7 +2,7 @@ import type {
CustomWorldAgentSessionSnapshot,
} from '../../../packages/shared/src/contracts/customWorldAgent';
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
-import type { CustomWorldProfile, GameState } from '../../types';
+import type { CustomWorldProfile } from '../../types';
export type SelectionStage =
| 'platform'
@@ -34,7 +34,6 @@ export type SyncedAgentDraftResult = {
export type PlatformEntryFlowShellProps = {
selectionStage: SelectionStage;
setSelectionStage: (stage: SelectionStage) => void;
- gameState: GameState;
hasSavedGame: boolean;
savedSnapshot: HydratedSavedGameSnapshot | null;
handleContinueGame: (snapshot?: HydratedSavedGameSnapshot | null) => void;
diff --git a/src/components/rpg-creation-editor/RpgCreationEntityEditorShared.tsx b/src/components/rpg-creation-editor/RpgCreationEntityEditorShared.tsx
index d1da6dc3..7958613d 100644
--- a/src/components/rpg-creation-editor/RpgCreationEntityEditorShared.tsx
+++ b/src/components/rpg-creation-editor/RpgCreationEntityEditorShared.tsx
@@ -28,8 +28,8 @@ import { EDITOR_ITEM_CATALOG_API_PATH } from '../../editor/shared/editorApiClien
import { fetchJson } from '../../editor/shared/jsonClient';
import { useCombatFlow } from '../../hooks/useCombatFlow';
import { useNpcInteractionFlow } from '../../hooks/useNpcInteractionFlow';
-import { useRpgRuntimeStory } from '../../hooks/rpg-runtime-story';
-import { useRpgSessionBootstrap } from '../../hooks/rpg-session';
+import { useRpgRuntimeStory } from '../../hooks/rpg-runtime-story/useRpgRuntimeStory';
+import { useRpgSessionBootstrap } from '../../hooks/rpg-session/useRpgSessionBootstrap';
import { buildSkillActionPrompt } from '../../prompts/customWorldEntityActionPrompts';
import type { CustomWorldSceneImageResult } from '../../services/aiTypes';
import { resolveCustomWorldCampScene } from '../../services/customWorldCamp';
diff --git a/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx b/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx
index c50964d5..36ca1769 100644
--- a/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx
+++ b/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx
@@ -21,7 +21,6 @@ import type { AuthUser } from '../../services/authService';
import { ApiClientError } from '../../services/apiClient';
import {
clearRpgProfileBrowseHistory as clearProfileBrowseHistory,
- deleteRpgEntryWorldProfile,
getRpgEntryWorldGalleryDetail,
getRpgProfileDashboard as getProfileDashboard,
listRpgEntryWorldGallery,
@@ -47,8 +46,10 @@ import {
listPuzzleGallery,
} from '../../services/puzzle-gallery';
import { listPuzzleWorks } from '../../services/puzzle-works';
-import { getRpgEntryWorldGalleryDetailByCode } from '../../services/rpg-entry/rpgEntryLibraryClient';
-import type { GameState } from '../../types';
+import {
+ deleteRpgEntryWorldProfile,
+ getRpgEntryWorldGalleryDetailByCode,
+} from '../../services/rpg-entry/rpgEntryLibraryClient';
import {
AuthUiContext,
type PlatformSettingsSection,
@@ -130,6 +131,7 @@ vi.mock('../../services/puzzle-gallery', () => ({
}));
vi.mock('../../services/rpg-entry/rpgEntryLibraryClient', () => ({
+ deleteRpgEntryWorldProfile: vi.fn(),
getRpgEntryWorldGalleryDetailByCode: vi.fn(),
}));
@@ -495,7 +497,6 @@ function TestWrapper({
{})}
@@ -547,7 +548,7 @@ beforeEach(() => {
savedAt: '2026-04-19T12:00:00.000Z',
bottomTab: 'adventure',
currentStory: null,
- gameState: {} as GameState,
+ gameState: {},
} as HydratedSavedGameSnapshot,
});
vi.mocked(upsertProfileBrowseHistory).mockResolvedValue([]);
@@ -1450,10 +1451,13 @@ test('published puzzle detail returns to the source platform tab', async () => {
await waitFor(() => {
expect(document.getElementById('platform-tab-panel-category')).toBeTruthy();
});
+ await waitFor(() => {
+ const categoryPanel = getPlatformTabPanel('category');
+ expect(
+ within(categoryPanel).getAllByText('星桥机关').length,
+ ).toBeGreaterThan(0);
+ });
const categoryPanel = getPlatformTabPanel('category');
- expect(
- within(categoryPanel).getAllByText('星桥机关').length,
- ).toBeGreaterThan(0);
await user.click(
within(categoryPanel).getByRole('button', {
@@ -2087,7 +2091,6 @@ test('agent draft result publishes to gallery from publish panel', async () => {
{}}
@@ -2162,7 +2165,6 @@ test('agent draft result test button enters current draft without publish gate',
{}}
@@ -2778,7 +2780,7 @@ test('save tab can resume a selected archive directly into the game', async () =
currentStory: null,
gameState: {
worldType: 'CUSTOM',
- } as GameState,
+ },
} as HydratedSavedGameSnapshot,
});
diff --git a/src/components/rpg-entry/RpgEntryFlowShell.tsx b/src/components/rpg-entry/RpgEntryFlowShell.tsx
index bb4b82a1..0d22f677 100644
--- a/src/components/rpg-entry/RpgEntryFlowShell.tsx
+++ b/src/components/rpg-entry/RpgEntryFlowShell.tsx
@@ -1,4 +1,4 @@
-import { PlatformEntryFlowShell } from '../platform-entry';
+import { PlatformEntryFlowShell } from '../platform-entry/PlatformEntryFlowShell';
import type { RpgEntryFlowShellProps } from './rpgEntryTypes';
import type { SelectionStage } from './rpgEntryTypes';
diff --git a/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx b/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx
index ecf1e289..dad02e7c 100644
--- a/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx
+++ b/src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx
@@ -274,6 +274,7 @@ test('opens recharge modal and submits points product', async () => {
await user.click(screen.getByText('会员充值'));
expect(await screen.findByText('账户充值')).toBeTruthy();
+ expect(await screen.findByText('叙世币充值')).toBeTruthy();
expect(await screen.findByText('60叙世币')).toBeTruthy();
await user.click(screen.getByText('首充送60叙世币'));
diff --git a/src/components/rpg-entry/RpgEntryHomeView.tsx b/src/components/rpg-entry/RpgEntryHomeView.tsx
index dae0bb2d..6a6ff596 100644
--- a/src/components/rpg-entry/RpgEntryHomeView.tsx
+++ b/src/components/rpg-entry/RpgEntryHomeView.tsx
@@ -1306,6 +1306,9 @@ export function RpgEntryHomeView({
const [selectedCategoryTag, setSelectedCategoryTag] = useState(
null,
);
+ const [visitedTabs, setVisitedTabs] = useState>(
+ () => new Set([activeTab]),
+ );
const isAuthenticated = Boolean(authUi?.user);
const isDesktopLayout = usePlatformDesktopLayout();
const featuredShelf = useMemo(
@@ -1355,6 +1358,18 @@ export function RpgEntryHomeView({
}
}, [activeTab, onTabChange, visibleTabs]);
+ useEffect(() => {
+ setVisitedTabs((currentTabs) => {
+ if (currentTabs.has(activeTab)) {
+ return currentTabs;
+ }
+
+ const nextTabs = new Set(currentTabs);
+ nextTabs.add(activeTab);
+ return nextTabs;
+ });
+ }, [activeTab]);
+
useEffect(() => {
if (categoryGroups.length === 0) {
setSelectedCategoryTag(null);
@@ -2213,11 +2228,15 @@ export function RpgEntryHomeView({
} satisfies Record;
const tabPanels = PLATFORM_HOME_TABS.filter((tab) =>
visibleTabs.includes(tab),
- ).map((tab) => (
-
- {tabContentById[tab]}
-
- ));
+ ).map((tab) => {
+ const shouldMountPanel = tab === activeTab || visitedTabs.has(tab);
+
+ return (
+
+ {shouldMountPanel ? tabContentById[tab] : null}
+
+ );
+ });
if (!isDesktopLayout) {
return (
diff --git a/src/components/rpg-entry/useRpgEntryLibraryDetail.ts b/src/components/rpg-entry/useRpgEntryLibraryDetail.ts
index 1b65edf4..d3d5c2f4 100644
--- a/src/components/rpg-entry/useRpgEntryLibraryDetail.ts
+++ b/src/components/rpg-entry/useRpgEntryLibraryDetail.ts
@@ -15,7 +15,7 @@ import {
listRpgEntryWorldLibrary,
publishRpgEntryWorldProfile,
unpublishRpgEntryWorldProfile,
-} from '../../services/rpg-entry';
+} from '../../services/rpg-entry/rpgEntryLibraryClient';
import { ApiClientError } from '../../services/apiClient';
import type { CustomWorldProfile } from '../../types';
import {
diff --git a/src/components/rpg-runtime-panels/RpgRuntimePanelRouter.tsx b/src/components/rpg-runtime-panels/RpgRuntimePanelRouter.tsx
index 19eb14ea..cb7c8a41 100644
--- a/src/components/rpg-runtime-panels/RpgRuntimePanelRouter.tsx
+++ b/src/components/rpg-runtime-panels/RpgRuntimePanelRouter.tsx
@@ -18,10 +18,8 @@ import type {
import { getNineSliceStyle, TAB_ICONS, UI_CHROME } from '../../uiAssets';
import type { GameCanvasEntitySelection } from '../GameCanvas';
import { PixelIcon } from '../PixelIcon';
-import {
- PanelLoadingFallback,
- type RpgAdventureStatistics,
-} from '../rpg-runtime-shell';
+import { PanelLoadingFallback } from '../rpg-runtime-shell/rpgRuntimeLoaders';
+import type { RpgAdventureStatistics } from '../rpg-runtime-shell/types';
const RpgAdventurePanel = lazy(async () => {
const module = await import('./RpgAdventurePanel');
diff --git a/src/components/rpg-runtime-shell/RpgRuntimeShell.tsx b/src/components/rpg-runtime-shell/RpgRuntimeShell.tsx
index 537b7bdf..96630056 100644
--- a/src/components/rpg-runtime-shell/RpgRuntimeShell.tsx
+++ b/src/components/rpg-runtime-shell/RpgRuntimeShell.tsx
@@ -7,11 +7,17 @@ import {
} from '../../routing/appPageRoutes';
import { UI_CHROME } from '../../uiAssets';
import { useAuthUi } from '../auth/AuthUiContext';
-import { RpgRuntimeCanvasStage } from './RpgRuntimeCanvasStage';
import { RpgRuntimeStageRouter } from './RpgRuntimeStageRouter';
import type { RpgRuntimeShellProps as RpgRuntimeShellComponentProps } from './types';
import { useRpgRuntimeShellViewModel } from './useRpgRuntimeShellViewModel';
+const RpgRuntimeCanvasStage = lazy(async () => {
+ const module = await import('./RpgRuntimeCanvasStage');
+ return {
+ default: module.RpgRuntimeCanvasStage,
+ };
+});
+
const RpgRuntimeOverlayHost = lazy(async () => {
const module = await import('./RpgRuntimeOverlayHost');
return {
@@ -154,20 +160,22 @@ export function RpgRuntimeShell({
backgroundRepeat: isPlatformShell ? undefined : 'repeat',
}}
>
-
-
-
+ {gameState.worldType ? (
+
+
+
+ ) : null}
{visibleGameState.playerCharacter && !chrome?.hidePlayerLevelBadge && (
-
-
-
+ {gameState.worldType ? (
+
+
+
+ ) : null}
);
}
diff --git a/src/components/rpg-runtime-shell/RpgRuntimeStageRouter.tsx b/src/components/rpg-runtime-shell/RpgRuntimeStageRouter.tsx
index 4381adfe..c65e9d13 100644
--- a/src/components/rpg-runtime-shell/RpgRuntimeStageRouter.tsx
+++ b/src/components/rpg-runtime-shell/RpgRuntimeStageRouter.tsx
@@ -20,25 +20,25 @@ import type {
} from '../../types';
import { UI_CHROME } from '../../uiAssets';
import type { GameCanvasEntitySelection } from '../GameCanvas';
-import type { SelectionStage } from '../platform-entry';
+import type { SelectionStage } from '../platform-entry/platformEntryTypes';
import type { RpgAdventureStatistics } from './types';
const RpgEntryCharacterSelectView = lazy(async () => {
- const module = await import('../rpg-entry');
+ const module = await import('../rpg-entry/RpgEntryCharacterSelectView');
return {
default: module.RpgEntryCharacterSelectView,
};
});
const PlatformEntryFlowShell = lazy(async () => {
- const module = await import('../platform-entry');
+ const module = await import('../platform-entry/PlatformEntryFlowShell');
return {
default: module.PlatformEntryFlowShell,
};
});
const RpgRuntimePanelRouter = lazy(async () => {
- const module = await import('../rpg-runtime-panels');
+ const module = await import('../rpg-runtime-panels/RpgRuntimePanelRouter');
return {
default: module.RpgRuntimePanelRouter,
};
@@ -174,7 +174,6 @@ export function RpgRuntimeStageRouter({