From c56da8dc26fc4d2da3bd606071adc217f6d59ebb Mon Sep 17 00:00:00 2001 From: kdletters Date: Sat, 18 Jul 2026 15:41:55 +0800 Subject: [PATCH] =?UTF-8?q?=E6=81=A2=E5=A4=8D=E6=96=B0=E7=89=88=E5=88=9B?= =?UTF-8?q?=E4=BD=9C=E4=B8=BB=E9=A1=B5=E4=B8=8E=E5=B9=B3=E5=8F=B0=E4=BE=A7?= =?UTF-8?q?=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 恢复 /creation 新版创作工具主页及创作、项目公共侧栏。 将 creation-home 重新纳入 Vite、TypeScript、ESLint 与 Vitest 活动编译链。 恢复 /creation 的 Nginx 与 Pingora SPA 白名单并保留旧子路径下线边界。 补充路由、标题、CSS 过滤和网关 smoke 回归验证。 --- .eslintrc.cjs | 1 - deploy/container/nginx.conf | 2 +- deploy/nginx/genarrative-dev-http.conf | 2 +- deploy/nginx/genarrative.conf | 2 +- ...构下线】旧创作模板业务退役方案-2026-07-17.md | 12 +- scripts/check-pingora-gateway-smoke.mjs | 11 + scripts/vite-retired-css-plugin.test.ts | 21 ++ server-rs/crates/pingora-gateway/src/main.rs | 2 +- .../PlatformEntryActiveFlowShell.tsx | 278 +++++++++++++----- .../platform-entry/PlatformEntryFlowShell.tsx | 2 +- .../platformEntryActiveTypes.ts | 6 +- src/routing/activeAppPageRoutes.test.ts | 6 +- src/routing/activeAppPageRoutes.ts | 1 + src/services/activeAppTitle.test.ts | 3 + src/services/activeAppTitle.ts | 1 + tsconfig.typecheck-guardrails.json | 2 + vite.config.ts | 7 +- vitest.config.ts | 2 + 18 files changed, 275 insertions(+), 86 deletions(-) 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()} + > + + )} +
+
+ +
+ {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',