diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index d1409c137..593462040 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -127,7 +127,6 @@ module.exports = {
'src/components/big-fish-result/**',
'src/components/big-fish-runtime/**',
'src/components/creation-agent/**',
- 'src/components/creation-home/**',
'src/components/creative-agent/**',
'src/components/custom-world-agent/**',
'src/components/custom-world-home/**',
diff --git a/deploy/container/nginx.conf b/deploy/container/nginx.conf
index f65af8b7e..2f1715b1b 100644
--- a/deploy/container/nginx.conf
+++ b/deploy/container/nginx.conf
@@ -141,7 +141,7 @@ http {
try_files /index.html =404;
}
- location ~* "^/(?:editor/canvas|project)/?$" {
+ location ~* "^/(?:creation|editor/canvas|project)/?$" {
try_files $uri /index.html =404;
}
# END GENARRATIVE MAIN SPA ROUTES
diff --git a/deploy/nginx/genarrative-dev-http.conf b/deploy/nginx/genarrative-dev-http.conf
index f2234416e..52aa40cfc 100644
--- a/deploy/nginx/genarrative-dev-http.conf
+++ b/deploy/nginx/genarrative-dev-http.conf
@@ -190,7 +190,7 @@ server {
try_files /index.html =404;
}
- location ~* "^/(?:editor/canvas|project)/?$" {
+ location ~* "^/(?:creation|editor/canvas|project)/?$" {
error_page 503 /maintenance.html;
if ($genarrative_maintenance) {
diff --git a/deploy/nginx/genarrative.conf b/deploy/nginx/genarrative.conf
index 7f8f1dc94..12c02618f 100644
--- a/deploy/nginx/genarrative.conf
+++ b/deploy/nginx/genarrative.conf
@@ -210,7 +210,7 @@ server {
try_files /index.html =404;
}
- location ~* "^/(?:editor/canvas|project)/?$" {
+ location ~* "^/(?:creation|editor/canvas|project)/?$" {
error_page 503 /maintenance.html;
if ($genarrative_maintenance) {
diff --git a/docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md b/docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md
index 142e30330..0db7c6fc2 100644
--- a/docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md
+++ b/docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md
@@ -1,12 +1,12 @@
# 旧创作模板业务退役方案
-更新时间:`2026-07-17`
+更新时间:`2026-07-18`
## 目标
退役整个旧创作模板体系及其专属运行态,同时保留全部相关历史持久化表、迁移白名单和必要的兼容读取定义。历史数据不删除,持久化表及字段不删除、不改名、不重排、不改变类型。
-本次执行口径是“数据壳保留,业务实现退役”:历史表继续以最小 schema 代码随 `spacetime-module` 编译和发布;创作入口、模板选择、生成、发布、公开业务详情、作品广场、排行榜和专属运行态不再进入正式编译链或运行路由。
+本次执行口径是“数据壳保留,业务实现退役”:历史表继续以最小 schema 代码随 `spacetime-module` 编译和发布;旧模板选择、生成、发布、公开业务详情、作品广场、排行榜和专属运行态不再进入正式编译链或运行路由。基于图片编辑器项目与公开素材的新版 `/creation` 创作工具主页及平台公共侧边栏继续作为现役能力保留。
## 范围
@@ -25,6 +25,7 @@
- `migration.rs` 中相关表的迁移白名单、表名兼容和字段目录。
- 为历史审计、迁移、资产归属核对所必需的最小只读表定义;不得借兼容读取重新暴露旧创作、发布、公开详情或运行接口。
- 编辑器、项目、账号、钱包、资产、HostBridge、运维和安全等平台公共能力。
+- 新版 `/creation` 创作工具主页、`creation-home` 展示组件与静态资产,以及桌面端“创作 / 项目”公共侧边栏;这些能力只读取编辑器项目和公开素材接口,不恢复旧模板入口。
- 旧页面、测试、素材、handler、service、worker、生成 bindings 和纯业务 crate 的源码目录;它们仅用于历史追溯,不属于任何正式入口或编译目标。
## 编译边界
@@ -32,11 +33,11 @@
### 前端
- 主路由、Vite 入口和运行时动态 import 不得引用旧业务目录。
-- 正式应用入口使用 `active-main.tsx`、`ActiveApp.tsx`、`routing/activeAppRoutes.tsx`、`routing/activeAppPageRoutes.ts`、`services/activeAppTitle.ts`、`platform-entry/PlatformEntryActiveFlowShell.tsx` 和 `platformEntryActiveTypes.ts`;原同名非 active 文件保持历史源码原貌并退出 Vite、TypeScript、ESLint 和 Vitest。
+- 正式应用入口使用 `active-main.tsx`、`ActiveApp.tsx`、`routing/activeAppRoutes.tsx`、`routing/activeAppPageRoutes.ts`、`services/activeAppTitle.ts`、`platform-entry/PlatformEntryActiveFlowShell.tsx`、`platformEntryActiveTypes.ts` 和 `creation-home/`;原同名非 active 文件保持历史源码原貌并退出 Vite、TypeScript、ESLint 和 Vitest。
- 旧业务源码与素材保留在仓库中;Vite 不得再引用入口或动态 import,TypeScript、ESLint、Vitest 必须明确排除旧目录和专属测试。退役源码只用于历史追溯,不允许从在运代码重新导入。
-- 原 `main.tsx`、`App.tsx`、旧路由、旧标题映射、`PlatformEntryFlowShellImpl.tsx` 与旧入口类型保持原样;正式链路由 `active-main.tsx`、`ActiveApp.tsx`、`activeApp*`、`PlatformEntryActiveFlowShell.tsx` 和 `platformEntryActiveTypes.ts` 承载,只包含项目、编辑器、账号、设置与钱包公共能力。`retired/legacy-creation-templates/frontend/original/` 另保留逐文件原样快照。
+- 原 `main.tsx`、`App.tsx`、旧路由、旧标题映射、`PlatformEntryFlowShellImpl.tsx` 与旧入口类型保持原样;正式链路由 `active-main.tsx`、`ActiveApp.tsx`、`activeApp*`、`PlatformEntryActiveFlowShell.tsx` 和 `platformEntryActiveTypes.ts` 承载,只包含新版创作主页、项目、编辑器、账号、设置与钱包公共能力。`retired/legacy-creation-templates/frontend/original/` 另保留逐文件原样快照。
- 退役 CSS 的源码过滤必须在 Tailwind/Vite 转换前执行,产物过滤留在 `generateBundle`;禁止在 `post` transform 中把 Vite 已生成的 JavaScript 样式模块重新交给 PostCSS 解析。
-- 旧 URL 统一回落到当前平台公共首页,不再提供旧业务错误页、详情页或兼容壳。
+- `/creation` 保持新版创作工具主页;生产网关对旧子路径返回 404,客户端若收到未知旧路径则回落当前平台公共首页,不再提供旧业务详情页或兼容壳。
### Rust
@@ -50,6 +51,7 @@
## 验收
- 旧 URL 不再命中旧页面或后端路由。
+- `/creation` 与 `/project` 显示公共侧边栏,新创作主页只调用编辑器项目和公开素材接口。
- Vite 构建产物和依赖图不包含旧前端业务目录。
- `cargo tree` 中不存在纯模板 crate 或专属运行态 crate。
- `spacetime-module` 编译结果仍包含全部历史表,但不包含任何旧模板 reducer、procedure 和业务 view。
diff --git a/scripts/check-pingora-gateway-smoke.mjs b/scripts/check-pingora-gateway-smoke.mjs
index 4abf31432..f07185d66 100644
--- a/scripts/check-pingora-gateway-smoke.mjs
+++ b/scripts/check-pingora-gateway-smoke.mjs
@@ -571,6 +571,17 @@ async function runSmokeCases(
response.headers['cache-control'] === 'no-cache',
},
);
+ await expectHttp(
+ baseUrl,
+ '/creation',
+ 200,
+ 'site-shell',
+ '新创作主页深链回退 index.html',
+ {
+ validate: (response) =>
+ response.headers['cache-control'] === 'no-cache',
+ },
+ );
await expectHttp(
baseUrl,
'/project',
diff --git a/scripts/vite-retired-css-plugin.test.ts b/scripts/vite-retired-css-plugin.test.ts
index e85af3d46..ef5fc1b1e 100644
--- a/scripts/vite-retired-css-plugin.test.ts
+++ b/scripts/vite-retired-css-plugin.test.ts
@@ -21,4 +21,25 @@ describe('retired creation template CSS plugin', () => {
expect(plugin).toBeDefined();
expect(plugin?.enforce).toBe('pre');
});
+
+ it('keeps the active creation landing CSS while removing retired template CSS', async () => {
+ const plugin = await resolveRetiredCssPlugin();
+ const transform = plugin?.transform;
+
+ expect(typeof transform).toBe('function');
+ if (typeof transform !== 'function') {
+ return;
+ }
+
+ const result = await transform.call(
+ {} as never,
+ "@import 'tailwindcss';\n.creation-landing { color: red; }\n.puzzle-runtime { color: blue; }",
+ '/workspace/src/index.css',
+ );
+ const code = typeof result === 'string' ? result : result?.code;
+
+ expect(code).toContain('.creation-landing');
+ expect(code).not.toContain('.puzzle-runtime');
+ expect(code).toContain('@source "./components/creation-home"');
+ });
});
diff --git a/server-rs/crates/pingora-gateway/src/main.rs b/server-rs/crates/pingora-gateway/src/main.rs
index 97ec55809..589aec4eb 100644
--- a/server-rs/crates/pingora-gateway/src/main.rs
+++ b/server-rs/crates/pingora-gateway/src/main.rs
@@ -55,7 +55,7 @@ const SHADOW_PROBE_HEADER: &str = "x-genarrative-pingora-probe";
const PAYLOAD_TOO_LARGE_CONTEXT: &str = "genarrative_payload_too_large";
const PROTECTION_STATE_TTL: Duration = Duration::from_secs(600);
const PROTECTION_CLEANUP_INTERVAL: Duration = Duration::from_secs(60);
-const MAIN_SPA_PATHS: &[&str] = &["/", "/editor/canvas", "/project"];
+const MAIN_SPA_PATHS: &[&str] = &["/", "/creation", "/editor/canvas", "/project"];
#[derive(Clone, Debug)]
struct GatewayConfig {
diff --git a/src/components/platform-entry/PlatformEntryActiveFlowShell.tsx b/src/components/platform-entry/PlatformEntryActiveFlowShell.tsx
index 3abf3088c..2d3b72bc2 100644
--- a/src/components/platform-entry/PlatformEntryActiveFlowShell.tsx
+++ b/src/components/platform-entry/PlatformEntryActiveFlowShell.tsx
@@ -1,5 +1,18 @@
-import { FolderKanban, LogIn, Settings, UserRound } from 'lucide-react';
-import { lazy, Suspense, useCallback, useEffect, useState } from 'react';
+import {
+ FolderKanban,
+ LogIn,
+ Palette,
+ Settings,
+ UserRound,
+} from 'lucide-react';
+import {
+ type ComponentType,
+ lazy,
+ Suspense,
+ useCallback,
+ useEffect,
+ useState,
+} from 'react';
import { PlatformMudPointWalletEntry } from '../../../packages/shared/src/components/PlatformMudPointWalletEntry';
import type { ProfileDashboardSummary } from '../../../packages/shared/src/contracts/runtime';
@@ -25,11 +38,87 @@ const ImageCanvasEditorView = lazy(async () => {
return { default: module.ImageCanvasEditorView };
});
+const CreationLandingView = lazy(async () => {
+ const module = await import('../creation-home/CreationLandingView');
+ return { default: module.CreationLandingView };
+});
+
const ProjectGalleryView = lazy(async () => {
const module = await import('../project/ProjectGalleryView');
return { default: module.ProjectGalleryView };
});
+type ActiveRailButtonProps = {
+ active: boolean;
+ emphasized?: boolean;
+ icon: ComponentType<{ className?: string }>;
+ iconSrc: string;
+ label: string;
+ onClick: () => void;
+};
+
+function ActiveRailButton({
+ active,
+ emphasized = false,
+ icon: Icon,
+ iconSrc,
+ label,
+ onClick,
+}: ActiveRailButtonProps) {
+ return (
+
+ );
+}
+
+function ActivePlatformBrand() {
+ return (
+
+
+
+
+
+ 陶泥儿
+
+ GENARRATIVE
+
+
+
+ );
+}
+
function LoadingPanel({ label }: { label: string }) {
return (
@@ -76,18 +165,26 @@ export function PlatformEntryFlowShellImpl({
currentUser: authUi?.user,
});
+ const openCreation = useCallback(() => {
+ pushAppHistoryPath('/creation');
+ setSelectionStage('creation-home', { path: '/creation' });
+ }, [setSelectionStage]);
+
const openProjects = useCallback(() => {
pushAppHistoryPath('/project');
setSelectionStage('project', { path: '/project' });
}, [setSelectionStage]);
const openEditorProject = useCallback(
- (projectId: string, options?: { guide?: boolean }) => {
+ (projectId: string, options?: { guide?: boolean; tool?: string }) => {
const params = new URLSearchParams();
params.set('projectid', projectId);
if (options?.guide) {
params.set('guide', 'toolbar');
}
+ if (options?.tool) {
+ params.set('tool', options.tool);
+ }
const path = `/editor/canvas?${params.toString()}`;
pushAppHistoryPath(path);
setSelectionStage('image-editor', { path });
@@ -118,76 +215,121 @@ export function PlatformEntryFlowShellImpl({
profileCenter.rechargeCenter?.walletBalance ??
dashboard?.walletBalance ??
null;
+ const isCreationStage =
+ selectionStage === 'platform' || selectionStage === 'creation-home';
return (
<>
-
-
-
-
-
- {isAuthenticated ? (
-
{
- profileCenter.setIsRechargeOpen(true);
- profileCenter.loadRechargeCenter();
- }}
- onOpenLedger={profileCenter.openWalletLedgerPanel}
- />
- ) : (
- authUi?.openLoginModal()}
+
+
+
+
-
+
+
+
+
-
- }>
-
-
-
+
+
+
+
+ {isCreationStage ? '创作工具' : '项目'}
+
+
+
+ {isAuthenticated ? (
+
{
+ profileCenter.setIsRechargeOpen(true);
+ profileCenter.loadRechargeCenter();
+ }}
+ onOpenLedger={profileCenter.openWalletLedgerPanel}
+ />
+ ) : (
+ authUi?.openLoginModal()}
+ >
+
+ 登录
+
+ )}
+
+ }
+ onClick={() => {
+ if (isAuthenticated) {
+ authUi?.openAccountModal();
+ return;
+ }
+ authUi?.openLoginModal();
+ }}
+ />
+ }
+ onClick={() => authUi?.openSettingsModal()}
+ />
+
+
+
+
+ {isCreationStage ? (
+ }
+ >
+
+
+ ) : (
+ }>
+
+
+ )}
+
+
+
+
{profileCenter.isRechargeOpen ? (
diff --git a/src/components/platform-entry/PlatformEntryFlowShell.tsx b/src/components/platform-entry/PlatformEntryFlowShell.tsx
index cb66f37f9..4233a95b1 100644
--- a/src/components/platform-entry/PlatformEntryFlowShell.tsx
+++ b/src/components/platform-entry/PlatformEntryFlowShell.tsx
@@ -8,7 +8,7 @@ export type { PlatformEntryFlowShellProps, SelectionStage };
/**
* 平台入口通用壳层。
- * 平台运行入口只承载项目、编辑器与账户公共能力。
+ * 平台运行入口承载新创作主页、项目、编辑器与账户公共能力。
*/
export function PlatformEntryFlowShell(props: PlatformEntryFlowShellProps) {
return ;
diff --git a/src/components/platform-entry/platformEntryActiveTypes.ts b/src/components/platform-entry/platformEntryActiveTypes.ts
index 1922f2c6c..3272bce12 100644
--- a/src/components/platform-entry/platformEntryActiveTypes.ts
+++ b/src/components/platform-entry/platformEntryActiveTypes.ts
@@ -1,4 +1,8 @@
-export type SelectionStage = 'platform' | 'project' | 'image-editor';
+export type SelectionStage =
+ | 'platform'
+ | 'creation-home'
+ | 'project'
+ | 'image-editor';
export type PlatformEntryFlowShellProps = {
selectionStage: SelectionStage;
diff --git a/src/routing/activeAppPageRoutes.test.ts b/src/routing/activeAppPageRoutes.test.ts
index 209198170..5cb800418 100644
--- a/src/routing/activeAppPageRoutes.test.ts
+++ b/src/routing/activeAppPageRoutes.test.ts
@@ -14,17 +14,19 @@ import {
} from './activeAppPageRoutes';
describe('appPageRoutes', () => {
- it('only resolves public platform, project, and editor stages', () => {
+ it('resolves the active creation, project, and editor stages', () => {
expect(resolveSelectionStageFromPath('/')).toBe('platform');
+ expect(resolveSelectionStageFromPath('/creation')).toBe('creation-home');
expect(resolveSelectionStageFromPath('/project')).toBe('project');
expect(resolveSelectionStageFromPath('/EDITOR/CANVAS/')).toBe(
'image-editor',
);
- expect(resolveSelectionStageFromPath('/creation')).toBe('platform');
expect(resolveSelectionStageFromPath('/creation/rpg')).toBe('platform');
expect(resolveSelectionStageFromPath('/runtime/puzzle')).toBe('platform');
+ expect(isKnownMainAppPagePath('/creation')).toBe(true);
expect(isKnownMainAppPagePath('/creation/rpg')).toBe(false);
expect(isKnownMainAppPagePath('/runtime/puzzle')).toBe(false);
+ expect(resolvePathForSelectionStage('creation-home')).toBe('/creation');
expect(resolvePathForSelectionStage('project')).toBe('/project');
});
diff --git a/src/routing/activeAppPageRoutes.ts b/src/routing/activeAppPageRoutes.ts
index b445c80ba..b5ca6e1c0 100644
--- a/src/routing/activeAppPageRoutes.ts
+++ b/src/routing/activeAppPageRoutes.ts
@@ -5,6 +5,7 @@ export const EDITOR_CANVAS_PROJECT_ID_QUERY_PARAM = 'projectid';
const STAGE_ROUTE_ENTRIES = [
['platform', '/'],
+ ['creation-home', '/creation'],
['project', '/project'],
['image-editor', '/editor/canvas'],
] as const satisfies readonly (readonly [SelectionStage, string])[];
diff --git a/src/services/activeAppTitle.test.ts b/src/services/activeAppTitle.test.ts
index a328a1837..6f079fce6 100644
--- a/src/services/activeAppTitle.test.ts
+++ b/src/services/activeAppTitle.test.ts
@@ -24,6 +24,9 @@ afterEach(() => {
describe('activeAppTitle', () => {
test('maps only active platform stages', () => {
expect(resolveAppTitleForSelectionStage('platform')).toBe(APP_HOME_TITLE);
+ expect(resolveAppTitleForSelectionStage('creation-home')).toBe(
+ APP_HOME_TITLE,
+ );
expect(resolveAppTitleForSelectionStage('project')).toBe('项目 - 陶泥儿');
expect(resolveAppTitleForSelectionStage('image-editor')).toBe(
'美术编辑器 - 陶泥儿',
diff --git a/src/services/activeAppTitle.ts b/src/services/activeAppTitle.ts
index b93717279..5acb780dc 100644
--- a/src/services/activeAppTitle.ts
+++ b/src/services/activeAppTitle.ts
@@ -7,6 +7,7 @@ export const APP_HOME_TITLE =
const APP_TITLE_BY_SELECTION_STAGE: Record = {
platform: APP_HOME_TITLE,
+ 'creation-home': APP_HOME_TITLE,
project: '项目 - 陶泥儿',
'image-editor': '美术编辑器 - 陶泥儿',
};
diff --git a/tsconfig.typecheck-guardrails.json b/tsconfig.typecheck-guardrails.json
index a389391dd..150dc4429 100644
--- a/tsconfig.typecheck-guardrails.json
+++ b/tsconfig.typecheck-guardrails.json
@@ -16,6 +16,8 @@
"src/services/activeAppTitle.test.ts",
"src/components/auth/**/*.test.ts",
"src/components/auth/**/*.test.tsx",
+ "src/components/creation-home/**/*.test.ts",
+ "src/components/creation-home/**/*.test.tsx",
"src/components/image-editor/**/*.test.ts",
"src/components/image-editor/**/*.test.tsx",
"src/components/project/**/*.test.ts",
diff --git a/vite.config.ts b/vite.config.ts
index 5f840758f..95a3528b6 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -7,7 +7,7 @@ import postcss from 'postcss';
import { defineConfig, loadEnv, type Plugin } from 'vite';
const RETIRED_TEMPLATE_CSS_PATTERN =
- /(?:baby-object|bark-battle|big-fish|child-motion|creation-(?:agent|home|landing|work)|creative-agent|custom-world|jump-hop|match3d|platform-recommend|platform-work-detail|public-work|puzzle|rpg|square-hole|unified-creation|visual-novel|wooden-fish)/iu;
+ /(?:baby-object|bark-battle|big-fish|child-motion|creation-(?:agent|work)|creative-agent|custom-world|jump-hop|match3d|platform-recommend|platform-work-detail|public-work|puzzle|rpg|square-hole|unified-creation|visual-novel|wooden-fish)/iu;
const ACTIVE_TAILWIND_SOURCES = `@import 'tailwindcss' source(none);
@source "./active-main.tsx";
@@ -15,6 +15,7 @@ const ACTIVE_TAILWIND_SOURCES = `@import 'tailwindcss' source(none);
@source "./AuthenticatedApp.tsx";
@source "./components/auth";
@source "./components/common";
+@source "./components/creation-home";
@source "./components/image-editor";
@source "./components/project";
@source "./components/platform-entry/PlatformEntryFlowShell.tsx";
@@ -33,7 +34,6 @@ const RETIRED_PUBLIC_ASSET_PATHS = [
'/bark-battle-assets',
'/character',
'/child-motion-demo',
- '/creation-home',
'/creation-type-references',
'/edutainment-baby-object',
'/generated-character-drafts',
@@ -165,7 +165,6 @@ export default defineConfig(({ mode }) => {
'**/public/anthro-cat-illustrations/**',
'**/public/bark-battle-assets/**',
'**/public/character/**',
- '**/public/creation-home/**',
'**/public/creation-type-references/**',
'**/public/edutainment-baby-object/**',
'**/public/match3d-background-references/**',
@@ -177,7 +176,7 @@ export default defineConfig(({ mode }) => {
'**/src/components/asset-studio/**',
'**/src/components/bark-battle-creation/**',
'**/src/components/big-fish-*/**',
- '**/src/components/creation-*/**',
+ '**/src/components/creation-agent/**',
'**/src/components/creative-agent/**',
'**/src/components/custom-world-*/**',
'**/src/components/edutainment-*/**',
diff --git a/vitest.config.ts b/vitest.config.ts
index 5275308e6..a60339bb7 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -26,6 +26,8 @@ export default defineConfig({
'src/services/payment/**/*.test.ts',
'src/components/auth/**/*.test.ts',
'src/components/auth/**/*.test.tsx',
+ 'src/components/creation-home/**/*.test.ts',
+ 'src/components/creation-home/**/*.test.tsx',
'src/components/image-editor/**/*.test.ts',
'src/components/image-editor/**/*.test.tsx',
'src/components/project/**/*.test.ts',