From 23dec91bd6dadcd2f617e8851ca6768c51d3266b Mon Sep 17 00:00:00 2001 From: kdletters Date: Sun, 31 May 2026 14:46:32 +0000 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E5=8F=A3=E7=BB=9F=E4=B8=80=E5=88=9B?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E7=A8=8B=E4=B8=80=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .hermes/shared-memory/decision-log.md | 29 ++- .hermes/shared-memory/pitfalls.md | 34 +++- ...玩法创作】创作æµç¨‹ç»Ÿä¸€æ€»è®¡åˆ’-2026-05-30.md | 22 ++- ...³•创作】跳一跳俯视角玩法模æ¿PRD-2026-05-19.md | 2 +- ...玩法创作】平å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md | 2 +- quality-gates/README.md | 3 +- .../ã€çŽ©æ³•åˆ›ä½œã€‘ç»Ÿä¸€åˆ›ä½œé¡µé—¨ç¦-2026-05-29.md | 7 +- ...Ž©æ³•åˆ›ä½œã€‘è·¨çŽ©æ³•å›žå½’ä¸Žå†’çƒŸé—¨ç¦-2026-05-30.md | 2 +- .../src/creation_entry_config.rs | 21 +- .../common/CreativeImageInputPanel.test.tsx | 91 +++++++++ .../common/CreativeImageInputPanel.tsx | 35 +++- .../Match3DDraftReadyView.tsx | 105 ---------- .../PlatformEntryFlowShellImpl.tsx | 160 ++++++--------- .../puzzle-agent/puzzleCreationTemplates.ts | 94 --------- .../puzzle-result/PuzzleResultView.tsx | 6 +- ...gEntryFlowShell.agent.interaction.test.tsx | 8 +- .../UnifiedCreationPage.test.tsx | 29 ++- .../unified-creation/UnifiedCreationPage.tsx | 34 +++- .../UnifiedCreationWorkspace.test.tsx | 184 ++++++++++++++++++ .../UnifiedCreationWorkspace.tsx | 125 ++++++++++++ .../UnifiedGenerationPage.test.tsx | 18 ++ .../PuzzleHistoryAssetPickerDialog.tsx | 8 +- .../shared}/PuzzleImageModelPicker.tsx | 0 .../shared}/puzzleImageModelOptions.ts | 0 .../unifiedCreationSpecs.test.ts | 11 +- .../unified-creation/unifiedCreationSpecs.ts | 57 ++++++ .../unified-creation/unifiedGenerationCopy.ts | 6 + .../JumpHopCreationWorkspace.test.tsx} | 29 ++- .../workspaces/JumpHopCreationWorkspace.tsx} | 47 +++-- ...h3DCreationWorkspace.interaction.test.tsx} | 14 +- .../workspaces/Match3DCreationWorkspace.tsx} | 13 +- ...zleCreationWorkspace.interaction.test.tsx} | 45 +++-- .../workspaces/PuzzleCreationWorkspace.tsx} | 29 +-- .../WoodenFishCreationWorkspace.test.tsx} | 52 ++++- .../WoodenFishCreationWorkspace.tsx} | 56 ++++-- src/services/creationEntryConfigService.ts | 10 +- 36 files changed, 919 insertions(+), 469 deletions(-) delete mode 100644 src/components/match3d-creation/Match3DDraftReadyView.tsx delete mode 100644 src/components/puzzle-agent/puzzleCreationTemplates.ts create mode 100644 src/components/unified-creation/UnifiedCreationWorkspace.test.tsx create mode 100644 src/components/unified-creation/UnifiedCreationWorkspace.tsx rename src/components/{puzzle-agent => unified-creation/shared}/PuzzleHistoryAssetPickerDialog.tsx (95%) rename src/components/{puzzle-agent => unified-creation/shared}/PuzzleImageModelPicker.tsx (100%) rename src/components/{puzzle-agent => unified-creation/shared}/puzzleImageModelOptions.ts (100%) rename src/components/{jump-hop-creation/JumpHopWorkspace.test.tsx => unified-creation/workspaces/JumpHopCreationWorkspace.test.tsx} (71%) rename src/components/{jump-hop-creation/JumpHopWorkspace.tsx => unified-creation/workspaces/JumpHopCreationWorkspace.tsx} (89%) rename src/components/{match3d-creation/Match3DAgentWorkspace.interaction.test.tsx => unified-creation/workspaces/Match3DCreationWorkspace.interaction.test.tsx} (95%) rename src/components/{match3d-creation/Match3DAgentWorkspace.tsx => unified-creation/workspaces/Match3DCreationWorkspace.tsx} (97%) rename src/components/{puzzle-agent/PuzzleAgentWorkspace.interaction.test.tsx => unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx} (96%) rename src/components/{puzzle-agent/PuzzleAgentWorkspace.tsx => unified-creation/workspaces/PuzzleCreationWorkspace.tsx} (96%) rename src/components/{wooden-fish-creation/WoodenFishWorkspace.test.tsx => unified-creation/workspaces/WoodenFishCreationWorkspace.test.tsx} (72%) rename src/components/{wooden-fish-creation/WoodenFishWorkspace.tsx => unified-creation/workspaces/WoodenFishCreationWorkspace.tsx} (87%) diff --git a/.hermes/shared-memory/decision-log.md b/.hermes/shared-memory/decision-log.md index 90379d91..493dde8d 100644 --- a/.hermes/shared-memory/decision-log.md +++ b/.hermes/shared-memory/decision-log.md @@ -43,12 +43,29 @@ ## 2026-05-29 一期统一创作页必须æä¾›å¯è§ç»Ÿä¸€å¤–壳 - 背景:`UnifiedCreationPage` é¦–ç‰ˆåªæš´éœ²éšè— spec 元数æ®å¹¶åŒ…裹旧玩法工作å°ï¼Œç”¨æˆ·æ‰“开拼图创作页时ä»åªèƒ½çœ‹åˆ°æ—§å·¥ä½œå°å¤–观,无法验收“统一创作页â€ã€‚ -- å†³ç­–ï¼šä¸€æœŸç»Ÿä¸€åˆ›ä½œé¡µï¼ˆæ‹¼å›¾ã€æŠ“å¤§é¹…ã€æ•²æœ¨é±¼ï¼‰å¿…须由 `UnifiedCreationPage` æä¾›ç»Ÿä¸€æ ‡é¢˜æ ã€å†…容区和éšè—字段契约;字段元信æ¯åªç•™ç»™æµ‹è¯•和代ç ï¼Œä¸å†é¢å¤–作为å¯è§ chip å ç”¨é¦–å±ã€‚玩法工作å°åªæ‰¿è½½å…·ä½“输入控件ã€ä¸Šä¼ ã€åކå²ç´ æã€æ ¡éªŒå’Œæäº¤ï¼Œä¸å†å„è‡ªæ¸²æŸ“å·¨å¤§å…¥å£æ ‡é¢˜ã€‚拼图继续å¤ç”¨ `PuzzleAgentWorkspace` 的上传ã€è£å‰ªã€åކå²å›¾ã€AI é‡ç»˜å’Œæäº¤é€»è¾‘,抓大鹅继续å¤ç”¨ `Match3DAgentWorkspace` 的题æä¸Žéš¾åº¦è¡¨å•逻辑;二者在统一壳内å¯ç”¨ `unifiedChrome`,收起旧标题与外层壳。敲木鱼å³ä¾§éŸ³æ•ˆå’ŒåŠŸå¾·é¢æ¿ä¸å¾—å†å¥—内部滚动容器,移动端应自然跟éšé¡µé¢æ»šåŠ¨ã€‚ -- 追加决策:`UnifiedCreationPage` ä¸åˆ›å»ºè‡ªå·±çš„çºµå‘æ»šåŠ¨çª—ï¼›æ‹¼å›¾ã€æŠ“å¤§é¹…å’Œæ•²æœ¨é±¼ä¸‰ä¸ªç»Ÿä¸€åˆ›ä½œå…¥å£ç”±å¹³å° stage 承担整页滚动,竖å±ç§»åŠ¨ç«¯å¿…é¡»èƒ½ä»Žç»Ÿä¸€æ ‡é¢˜ã€è¡¨å•控件一路滑到æäº¤æŒ‰é’®ï¼Œé¿å…工作å°å†…部或å³ä¾§é¢æ¿å½¢æˆå¥—滚动。 -- å½±å“范围:`src/components/unified-creation/UnifiedCreationPage.tsx`ã€`src/components/puzzle-agent/PuzzleAgentWorkspace.tsx`ã€`src/components/wooden-fish-creation/WoodenFishWorkspace.tsx`ã€`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`ã€çŽ©æ³•é“¾è·¯æ–‡æ¡£ã€‚ +- å†³ç­–ï¼šä¸€æœŸç»Ÿä¸€åˆ›ä½œé¡µï¼ˆæ‹¼å›¾ã€æŠ“å¤§é¹…ã€æ•²æœ¨é±¼ï¼‰å¿…须由 `UnifiedCreationPage` æä¾›ç»Ÿä¸€æ ‡é¢˜æ ã€å†…容区ã€é¡µé¢çº§çºµå‘滚动和éšè—字段契约;字段元信æ¯åªç•™ç»™æµ‹è¯•和代ç ï¼Œä¸å†é¢å¤–作为å¯è§ chip å ç”¨é¦–å±ã€‚玩法工作å°åªæ‰¿è½½å…·ä½“输入控件ã€ä¸Šä¼ ã€åކå²ç´ æã€æ ¡éªŒå’Œæäº¤ï¼Œä¸å†å„è‡ªæ¸²æŸ“å·¨å¤§å…¥å£æ ‡é¢˜ã€‚æ‹¼å›¾ã€æŠ“å¤§é¹…ä¸Žæ•²æœ¨é±¼çš„å®žçŽ°å·²ç»ç»Ÿä¸€æ”¶å£åˆ° `src/components/unified-creation/workspaces/`,统一壳åªä¾èµ– `UnifiedCreationWorkspace`。敲木鱼å³ä¾§éŸ³æ•ˆå’ŒåŠŸå¾·é¢æ¿ä¸å¾—å†å¥—内部滚动容器,移动端应自然跟éšé¡µé¢æ»šåŠ¨ã€‚ +- 追加决策:`UnifiedCreationPage` 自己负责页é¢çº§æ»šåŠ¨ï¼›æ‹¼å›¾ã€æŠ“å¤§é¹…ã€è·³ä¸€è·³å’Œæ•²æœ¨é±¼å››æ¡ç»Ÿä¸€åˆ›ä½œå…¥å£å¿…须在åŒä¸€é¡µé¢å£³å†…从统一标题ã€è¡¨å•控件一路滑到æäº¤æŒ‰é’®ï¼Œé¿å…工作å°å†…部或å³ä¾§é¢æ¿å½¢æˆå¥—滚动。 +- å½±å“范围:`src/components/unified-creation/UnifiedCreationPage.tsx`ã€`src/components/unified-creation/UnifiedCreationWorkspace.tsx`ã€`src/components/unified-creation/workspaces/PuzzleCreationWorkspace.tsx`ã€`src/components/unified-creation/workspaces/Match3DCreationWorkspace.tsx`ã€`src/components/unified-creation/workspaces/WoodenFishCreationWorkspace.tsx`ã€`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`ã€çŽ©æ³•é“¾è·¯æ–‡æ¡£ã€‚ - éªŒè¯æ–¹å¼ï¼š`UnifiedCreationPage` 测试应断言éšè—契约ä»åœ¨ä½† UI ä¸å†å‡ºçŽ°å­—æ®µ chipï¼›æ‹¼å›¾å’ŒæŠ“å¤§é¹…å·¥ä½œå°æµ‹è¯•应断言 `unifiedChrome=true` æ—¶ä¸å†æ¸²æŸ“旧巨大标题且ä»ä¿ç•™è¡¨å•è¾“å…¥ï¼›æœ¨é±¼å·¥ä½œå°æµ‹è¯•æˆ–æ‰‹æµ‹åº”ç¡®è®¤æ•²å‡»éŸ³æ•ˆå’ŒåŠŸå¾·è¯æ¡ä¸å†åœç•™åœ¨ç‹¬ç«‹æ»šåŠ¨çª—å†…ã€‚ - å…³è”æ–‡æ¡£ï¼š`docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md`。 +## 2026-05-31 统一创作壳扩展到跳一跳并接管页é¢çº§æ»šåЍ + +- 背景:最åˆçš„ç»Ÿä¸€åˆ›ä½œé¡µåªæ”¶å£æ‹¼å›¾ã€æŠ“大鹅和敲木鱼,跳一跳ä»é€šè¿‡ç‹¬ç«‹å·¥ä½œå°å£³ä¸Žç‹¬ç«‹ç”Ÿæˆå£³æ¸²æŸ“,导致用户在 `/creation/jump-hop` 看到的å¯è§å¤–壳与其它统一入å£ä¸ä¸€è‡´ã€‚ +- 决策:`jump-hop` 也纳入统一创作壳与统一生æˆå£³ï¼›`UnifiedCreationPage` 现在承担页é¢çº§æ»šåŠ¨å’Œç»Ÿä¸€æ ‡é¢˜æ ï¼Œæ‹¼å›¾ã€æŠ“大鹅ã€è·³ä¸€è·³ã€æ•²æœ¨é±¼å››æ¡å…¥å£éƒ½é€šè¿‡åŒä¸€å¤–壳承载å„自工作å°ã€‚`JumpHopCreationWorkspace`ã€`WoodenFishCreationWorkspace` 也补了 `unifiedChrome` / `showBackButton` å—æŽ§èƒ½åŠ›ï¼Œé¿å…åŒæ ‡é¢˜æˆ–åŒè¿”回按钮。 +- 追加决策:`UnifiedCreationPage` 的统一页头现在承载唯一返回入å£ï¼Œå·¥ä½œå°å†…部的返回按钮全部关闭,é¿å…åŒä¸€é¡µé¢å‡ºçްåŒè¿”回按钮;`UnifiedCreationWorkspace` 统一把 `onBack` é€ä¼ ç»™é¡µå¤´ã€‚ +- è¿½åŠ å†³ç­–ï¼šç»Ÿä¸€åˆ›ä½œé¡µå†…å®¹åŒºå¿…é¡»ä¿æŒè‡ªç„¶é«˜åº¦ï¼Œé¡µé¢çº§æ»šåЍåªç”± `UnifiedCreationPage` 外层承担,工作å°å†…部åªè´Ÿè´£å†…容展开,ä¸å†é¢å¤–包滚动壳。 +- å½±å“范围:`src/components/unified-creation/UnifiedCreationPage.tsx`ã€`src/components/unified-creation/unifiedCreationSpecs.ts`ã€`src/components/unified-creation/unifiedGenerationCopy.ts`ã€`src/components/unified-creation/workspaces/JumpHopCreationWorkspace.tsx`ã€`src/components/unified-creation/workspaces/WoodenFishCreationWorkspace.tsx`ã€`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`ã€`server-rs/crates/shared-contracts/src/creation_entry_config.rs`。 +- éªŒè¯æ–¹å¼ï¼š`npm run test -- src/components/unified-creation/unifiedCreationSpecs.test.ts src/components/unified-creation/UnifiedCreationPage.test.tsx src/components/unified-creation/UnifiedGenerationPage.test.tsx src/components/unified-creation/workspaces/JumpHopCreationWorkspace.test.tsx src/components/unified-creation/workspaces/WoodenFishCreationWorkspace.test.tsx`ï¼›`npm run test -- src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx src/components/unified-creation/workspaces/Match3DCreationWorkspace.interaction.test.tsx`ï¼›`npm run test -- src/routing/appPageRoutes.test.ts`。 + +## 2026-05-31 统一创作编排层必须由 UnifiedCreationWorkspace ç»Ÿä¸€æ”¶å£ + +- 背景:`PlatformEntryFlowShellImpl` ä»ç›´æŽ¥ lazy import 并渲染四个旧工作å°åˆ†æ”¯ï¼Œè™½ç„¶ç»Ÿä¸€åˆ›ä½œé¡µå·²å­˜åœ¨ï¼Œä½†å…¥å£å£³å±‚ä»ç„¶ä¾èµ–旧工作å°åˆ†æ”¯ã€‚ +- 决策:新增 `UnifiedCreationWorkspace` 作为平å°å£³å”¯ä¸€ä¾èµ–的统一创作编排层,由它内部按 `playId` 选择四æ¡å…¥å£çš„真实工作å°ï¼›å¹³å°å£³å±‚åªå†æŒ‚这一层,ä¸å†ç›´æŽ¥ä¾èµ–旧工作å°ç»„件。旧工作å°å·²ç§»å…¥ `src/components/unified-creation/workspaces/`,ä¸å†ä½œä¸ºå¹³å°å…¥å£ç¼–排事实æºã€‚ +- å½±å“范围:`src/components/unified-creation/UnifiedCreationWorkspace.tsx`ã€`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`ã€ç»Ÿä¸€åˆ›ä½œé¡µç›¸å…³æµ‹è¯•与åŽç»­å…¥å£æŽ¥å…¥ã€‚ +- éªŒè¯æ–¹å¼ï¼šå¹³å°å£³æºç ä¸­ä¸åº”å†ç›´æŽ¥å‡ºçŽ°å››ä¸ªæ—§å·¥ä½œå°çš„入壿¸²æŸ“分支;创作 Tab 与 `/creation/` ä»å¯æ­£å¸¸è¿›å…¥å¯¹åº”工作å°ã€‚ +- å…³è”æ–‡æ¡£ï¼š`docs/planning/ã€çŽ©æ³•åˆ›ä½œã€‘åˆ›ä½œæµç¨‹ç»Ÿä¸€æ€»è®¡åˆ’-2026-05-30.md`ã€`docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md`。 + ## 2026-05-27 生æˆé¡µæ€»è¿›åº¦åœ†å¼§é”定固定 SVG åæ ‡ç³» - 背景:多轮圆环角度微调åŽï¼Œ`GenerationProgressHero` çš„ SVG 圆弧ä»ä¼šå‡ºçŽ°åº•éƒ¨å¼€å£åæ–œçš„é—®é¢˜ï¼›åŽæ¥çª„å±éªŒæ”¶åˆå‘现固定 `400px` 外层宽度会让等待页å³ä¾§è¢«è£åˆ‡ã€‚ @@ -591,8 +608,8 @@ - 背景:抓大鹅è‰ç¨¿ç´ æç”Ÿæˆå·²ç»æ”¶æ•›ä¸ºå¤šè§†è§’ 2D 图片素æï¼Œä½†å…¥å£é¡µå’Œæ—§å‚è€ƒå›¾ä»æ²¿ç”¨é»åœŸã€ä½Žå¤šè¾¹å½¢ã€å¡‘æ–™ã€æœ¨é›•ã€ä½“ç´ ã€é‡‘属等å 3D ç´ æè¯­è¨€ï¼Œå®¹æ˜“让åŽç»­ç”Ÿæˆé“¾è·¯å’Œç”¨æˆ·é¢„期继续漂移。 - å†³ç­–ï¼šæŠ“å¤§é¹…åˆ›ä½œå…¥å£ `2Dç´ æé£Žæ ¼` 固定为 `æ‰å¹³å›¾æ ‡ / èµ›ç’ç’å¡é€š / åƒç´ å¤å¤ / 手绘水彩 / 贴纸æè¾¹ / 厚涂图标 / 自定义`;默认风格为 `flat-icon`。入å£å‚考图统一由 `npm run assets:match3d-style-references -- --live` 调用 VectorEngine `gpt-image-2` 生æˆï¼Œè¾“出到 `public/match3d-style-references/`。旧 3D 风格å‚考图ä¸å†ä¿ç•™ä¸ºå…¥å£èµ„产。 -- å½±å“范围:`Match3DAgentWorkspace`ã€æŠ“å¤§é¹…å…¥å£äº¤äº’测试ã€Match3D PRDã€ç´ æç”Ÿæˆæµæ°´çº¿æŠ€æœ¯æ–‡æ¡£ã€F1 入壿–‡æ¡£å’Œ `public/match3d-style-references/` 陿€èµ„产。 -- éªŒè¯æ–¹å¼ï¼šæ‰§è¡Œ `npm run test -- src\components\match3d-creation\Match3DAgentWorkspace.interaction.test.tsx`ã€`cargo test -p shared-contracts match3d --manifest-path server-rs\Cargo.toml`ã€`npm run typecheck`ã€`npm run check:encoding`,并人工抽查 `.tmp/match3d-style-preview.png`。 +- å½±å“范围:抓大鹅统一创作工作å°ã€æŠ“大鹅入å£äº¤äº’测试ã€Match3D PRDã€ç´ æç”Ÿæˆæµæ°´çº¿æŠ€æœ¯æ–‡æ¡£ã€F1 入壿–‡æ¡£å’Œ `public/match3d-style-references/` 陿€èµ„产。 +- éªŒè¯æ–¹å¼ï¼šæ‰§è¡Œ `npm run test -- src/components/unified-creation/workspaces/Match3DCreationWorkspace.interaction.test.tsx`ã€`cargo test -p shared-contracts match3d --manifest-path server-rs\Cargo.toml`ã€`npm run typecheck`ã€`npm run check:encoding`,并人工抽查 `.tmp/match3d-style-preview.png`。 - å…³è”æ–‡æ¡£ï¼š`docs/prd/AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md`ã€`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`ã€`docs/technical/MATCH3D_F1_CREATION_ENTRY_AND_AGENT_UI_2026-04-30.md`。 ## 2026-05-12 拼图与抓大鹅è‰ç¨¿èƒŒæ™¯éŸ³ä¹æŒ‰çº¯éŸ³ä¹è‡ªåŠ¨ç”Ÿæˆ @@ -1109,7 +1126,7 @@ - 背景:敲木鱼工作å°åªåº”ä¿ç•™ç”Ÿæˆæ‰€éœ€è¾“å…¥ï¼Œä½œå“æ ‡é¢˜ã€ç®€ä»‹å’Œä¸»é¢˜æ ‡ç­¾é€‚åˆæ”¾åœ¨ç”Ÿæˆè‰ç¨¿åŽçš„补录阶段。 - 决策:敲木鱼的 `workTitle`ã€`workDescription` å’Œ `themeTags` 从工作å°é¦–å±ç§»åˆ°ç»“果页;结果页编辑åŽåœ¨è¯•玩或å‘布å‰å…ˆè°ƒç”¨ `update-work-meta` 写回当å‰ä½œå“ä¿¡æ¯ã€‚主题标签编辑样å¼å¯¹é½æ‹¼å›¾ç»“果页的胶囊标签编辑器。 -- å½±å“范围:`WoodenFishWorkspace`ã€`WoodenFishResultView`ã€`PlatformEntryFlowShellImpl`ã€æ•²æœ¨é±¼ PRD 和平å°å…¥å£é“¾è·¯æ–‡æ¡£ã€‚ +- å½±å“范围:敲木鱼统一创作工作å°ã€`WoodenFishResultView`ã€`PlatformEntryFlowShellImpl`ã€æ•²æœ¨é±¼ PRD 和平å°å…¥å£é“¾è·¯æ–‡æ¡£ã€‚ - éªŒè¯æ–¹å¼ï¼šå·¥ä½œå°é¦–å±ä¸å†å‡ºçŽ°æ ‡é¢˜ / 简介 / 标签输入;结果页修改åŽç‚¹è¯•玩或å‘布会先写回当å‰ä½œå“ä¿¡æ¯ã€‚ - å…³è”æ–‡æ¡£ï¼š`docs/prd/ã€çŽ©æ³•åˆ›ä½œã€‘æ•²æœ¨é±¼çŽ©æ³•æ¨¡æ¿PRD-2026-05-20.md`ã€`docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md`。 diff --git a/.hermes/shared-memory/pitfalls.md b/.hermes/shared-memory/pitfalls.md index 9c55d74f..e2d6e2a0 100644 --- a/.hermes/shared-memory/pitfalls.md +++ b/.hermes/shared-memory/pitfalls.md @@ -142,6 +142,22 @@ - 验è¯ï¼šæµè§ˆå™¨é‡Œè¿™ä¸‰é¡µçš„æ ¹åŒºåº”ä»ä¿ç•™ `platform-remap-surface`,但ä¸å†å‡ºçް `platform-page-stage`ï¼›è‰ç¨¿é¡µé¡¶éƒ¨ç­›é€‰æ ·å¼åº”å’Œå‘çŽ°é¡µé¢‘é“æ ‡ç­¾ä¸€è‡´ã€‚ - å…³è”:`src/components/custom-world-home/CustomWorldCreationHub.tsx`ã€`src/components/custom-world-home/CustomWorldWorkTabs.tsx`ã€`src/components/rpg-entry/RpgEntryHomeView.tsx`ã€`src/index.css`。 +## ç»Ÿä¸€åˆ›ä½œå£³çŽ°åœ¨è‡ªå·±è´Ÿè´£é¡µé¢æ»šåŠ¨å’Œå››æ¡å…¥å£å¤–壳 + +- 现象:统一创作页最åˆåªåŒ…使‹¼å›¾ã€æŠ“大鹅和敲木鱼的工作å°å†…容,跳一跳ä»ç„¶ä¿ç•™ç‹¬ç«‹å·¥ä½œå°å£³ï¼Œé¡µé¢çº§æ»šåЍèŒè´£ä¹Ÿæ•£è½åœ¨å¹³å°å…¥å£ motion wrapper 里,导致移动端ä¸åŒå…¥å£çš„å¯è§å¤–壳ä¸ä¸€è‡´ã€‚ +- 原因:`UnifiedCreationPage` åªåšäº†æ ‡é¢˜å’Œéšè—契约,入å£å£³è¿˜åœ¨å„自工作å°é‡Œä¿ç•™ `platform-remap-surface` / `overflow-y-auto`,`jump-hop` 也没进入统一 spec。 +- 处ç†ï¼šæŠŠ `jump-hop` 纳入 `unifiedCreationSpec`,让 `UnifiedCreationPage` 自己承担页é¢çº§æ»šåŠ¨ä¸Žç»Ÿä¸€æ ‡é¢˜æ ï¼›`JumpHopCreationWorkspace`ã€`WoodenFishCreationWorkspace` è¡¥ `unifiedChrome` / `showBackButton`,平å°å£³ä¸å†ç»™è¿™å‡ æ¡ç»Ÿä¸€å…¥å£å¥—é¢å¤–滚动壳。 +- 验è¯ï¼š`npm run test -- src/components/unified-creation/unifiedCreationSpecs.test.ts src/components/unified-creation/UnifiedCreationPage.test.tsx src/components/unified-creation/UnifiedGenerationPage.test.tsx src/components/unified-creation/workspaces/JumpHopCreationWorkspace.test.tsx src/components/unified-creation/workspaces/WoodenFishCreationWorkspace.test.tsx` 通过åŽï¼Œ`/creation/puzzle`ã€`/creation/match3d`ã€`/creation/jump-hop`ã€`/creation/wooden-fish` 都应由åŒä¸€å¥—统一创作页外壳承载。 +- å…³è”:`src/components/unified-creation/UnifiedCreationPage.tsx`ã€`src/components/unified-creation/unifiedCreationSpecs.ts`ã€`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`。 + +## 统一创作编排层ä¸è¦å†è®©å¹³å°å£³ç›´æŒ‚æ—§å·¥ä½œå° + +- 现象:平å°å…¥å£å£³å·²ç»åˆ‡åˆ°ç»Ÿä¸€åˆ›ä½œå¤–壳,但æºç é‡Œä»ç›´æŽ¥ lazy import 并渲染四个旧工作å°åˆ†æ”¯ï¼Œçœ‹èµ·æ¥è¿˜æ˜¯å››å¥—å…¥å£ç¼–排。 +- åŽŸå› ï¼šç»Ÿä¸€åˆ›ä½œé¡µåªæ”¶å£äº†å¯è§å¤–壳,入å£å±‚æ²¡æœ‰å†æŠ½ä¸€å±‚ç»Ÿä¸€åˆ›ä½œç¼–æŽ’ç»„ä»¶ï¼Œå¯¼è‡´å¹³å°å£³ä¾æ—§è¦è®¤è¯†å„玩法旧工作å°ã€‚ +- 处ç†ï¼šæ–°å¢ž `UnifiedCreationWorkspace`,由它内部按 `playId` 选择真实工作å°ï¼›å¹³å°å£³åªä¾èµ–这一层,ä¸å†ç›´æŽ¥æŒ‚旧工作å°åˆ†æ”¯ã€‚旧工作å°å·²è¿å…¥ `src/components/unified-creation/workspaces/`,ä¸å†æ˜¯å…¥å£ç¼–排事实æºã€‚ +- 验è¯ï¼š`PlatformEntryFlowShellImpl.tsx` 中ä¸åº”å†å‡ºçŽ°å››ä¸ªæ—§å·¥ä½œå°çš„入壿¸²æŸ“分支,创作 Tab 与 `/creation/` ä»èƒ½æ­£å¸¸è¿›å…¥å¯¹åº”工作å°ã€‚ +- å…³è”:`src/components/unified-creation/UnifiedCreationWorkspace.tsx`ã€`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`ã€`docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md`。 + ## Jenkinsfile 开头ä¸èƒ½å¸¦ UTF-8 BOM @@ -255,7 +271,7 @@ - 原因:通用图åƒè¾“å…¥æ˜¯å—æŽ§è¾“å…¥é¢æ¿ï¼Œä¸æ˜¯åªæœåŠ¡å•é¡µçš„ä¸´æ—¶å®žçŽ°ï¼›å›¾ç‰‡ã€æç¤ºè¯ã€å‚考图数组ã€é‡ç»˜å¼€å…³ç­‰ä¸šåŠ¡çœŸç›¸åº”ç”±å¤–å±‚é¡µé¢æŒæœ‰ï¼Œç»„ä»¶æœ€å¤šæŒæœ‰å‚考图预览ã€åˆ é™¤ç¡®è®¤è¿™ç±»çŸ­ç”Ÿå‘½å‘¨æœŸ UI 状æ€ã€‚ - 处ç†ï¼šæŠ½ `CreativeImageInputPanel` 时,ä¿ç•™ä¸Šä¼ å¡ã€å‚考图入å£ã€ç¼©ç•¥å›¾ã€é¢„览弹层ã€åˆ é™¤ç¡®è®¤å’Œæäº¤æŒ‰é’®çš„统一壳,但把主图文件读å–ã€è£å‰ªã€åކå²ç´ æã€è®¡è´¹ç¡®è®¤å’Œå…·ä½“æäº¤åŠ¨ä½œç•™ç»™å¤–å±‚é¡µé¢ï¼›åŽç»­é¡µé¢æŽ¥å…¥æ—¶åªä¼ ä¸šåŠ¡å›žè°ƒå’Œæ–‡æ¡ˆã€‚ - 验è¯ï¼šæ‹¼å›¾å…¥å£æµ‹è¯•ä»å¯é€šè¿‡ï¼Œä¸”新组件å¯é€šè¿‡ä¸åŒé¡µé¢å¤ç”¨è€Œä¸éœ€è¦å¤åˆ¶ä¸Šä¼ å¡å®žçŽ°ã€‚ -- å…³è”:`src/components/common/CreativeImageInputPanel.tsx`ã€`src/components/puzzle-agent/PuzzleAgentWorkspace.tsx`。 +- å…³è”:`src/components/common/CreativeImageInputPanel.tsx`ã€`src/components/unified-creation/workspaces/PuzzleCreationWorkspace.tsx`。 ## RPG å‘布ä¸èƒ½åªä¾èµ– agent session seed_text @@ -693,7 +709,7 @@ - 原因:首图生æˆåªé€šè¿‡ `compile_puzzle_draft.referenceImageSrc` 临时传 Data URLï¼Œä¸æŒä¹…化到 SpacetimeDBï¼›ç»“æžœé¡µé‡æ–°ç”Ÿæˆåˆ™è¦æŠŠå½“å‰ä¸Šä¼ å›¾æˆ–å…³å¡ `pictureReference` 作为 `generate_puzzle_images.referenceImageSrc` ç»§ç»­ä¼ ç»™åŽç«¯ã€‚ - 处ç†ï¼šæµè§ˆå™¨ Network 里确认 action payload 带 `referenceImageSrc`ï¼›api-server 日志按åŒä¸€ `session_id` 查看 `拼图å‚考图解æžå®Œæˆ`ã€`拼图 VectorEngine å›¾ç‰‡ç”Ÿæˆ HTTP 返回`ã€`拼图 VectorEngine 图片下载完æˆ`ã€`拼图生æˆå›¾ç‰‡å·²å†™å…¥ OSS 与资产索引`,å¯å®šä½æ…¢åœ¨å‚考图读å–ã€VectorEngineã€ä¸‹è½½æˆ– OSS。 - 验è¯ï¼šå‰ç«¯æµ‹è¯•覆盖上传图 + AI é‡ç»˜ã€ç»“果页ä¿å­˜çš„ `pictureReference` 釿–°ç”Ÿæˆï¼›åŽç«¯å•测覆盖 VectorEngine 请求体 `image` 字段。 -- å…³è”:`src/components/puzzle-agent/PuzzleAgentWorkspace.tsx`ã€`src/components/puzzle-result/PuzzleResultView.tsx`ã€`server-rs/crates/api-server/src/puzzle.rs`。 +- å…³è”:`src/components/unified-creation/workspaces/PuzzleCreationWorkspace.tsx`ã€`src/components/puzzle-result/PuzzleResultView.tsx`ã€`server-rs/crates/api-server/src/puzzle.rs`。 ## 拼图首图生æˆåŽè¦æŠŠå…¥å£å‚考图写回 `pictureReference` @@ -1447,16 +1463,16 @@ - 现象:拼图创作页或结果页打开“选择历å²å›¾ç‰‡â€åŽï¼Œåކå²åˆ—表显示 `è´¦å· user-1` ä¹‹ç±»å½’å±žæ–‡æ¡ˆè€Œä¸æ˜¯å›¾ç‰‡åï¼›`1713686400.000000Z` 这类时间显示为未知;选中åŽé¢„览或生æˆå‚考图å¯èƒ½è¢«æ€€ç–‘ä¸å¯ç”¨ã€‚ - 原因:`/api/assets/history?kind=puzzle_cover_image` 返回的 `ownerLabel` 是资产归属账å·ï¼Œä¸æ˜¯å›¾ç‰‡æ ‡é¢˜ï¼›`createdAt` å¯èƒ½æ˜¯ SpacetimeDB / shared-kernel 秒级时间字符串,ä¸èƒ½åªç”¨æµè§ˆå™¨ `new Date(value)` è§£æžã€‚历å²å›¾çš„ `imageSrc` 是 `/generated-*` ç§æœ‰å…¼å®¹è·¯å¾„,æµè§ˆå™¨é¢„览必须æ¢ç­¾ã€‚ - 处ç†ï¼šå‰ç«¯æ ‡é¢˜å’Œé€‰ä¸­æ ‡ç­¾ä»Ž `imageSrc` 路径末尾推导,例如 `image.png`;时间解æžå…¼å®¹ ISO 与 `1713686400.000000Z`;创作页主图ã€åކå²åˆ—表图和结果页å‚考图继续用 `ResolvedAssetImage`,æäº¤ç»™åŽç«¯æ—¶ä»ä¿ç•™åŽŸå§‹ `imageSrc`。 -- 验è¯ï¼š`npm run test -- src/components/puzzle-agent/PuzzleAgentWorkspace.interaction.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`,并执行 `npm run check:encoding`。 -- å…³è”:`src/services/puzzle-works/puzzleHistoryAsset.ts`ã€`src/components/puzzle-agent/PuzzleHistoryAssetPickerDialog.tsx`ã€`docs/technical/ASSET_HISTORY_PUZZLE_COVER_KIND_FIX_2026-04-27.md`。 +- 验è¯ï¼š`npm run test -- src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`,并执行 `npm run check:encoding`。 +- å…³è”:`src/services/puzzle-works/puzzleHistoryAsset.ts`ã€`src/components/unified-creation/shared/PuzzleHistoryAssetPickerDialog.tsx`ã€`docs/technical/ASSET_HISTORY_PUZZLE_COVER_KIND_FIX_2026-04-27.md`。 ## 拼图历å²å›¾å…³é—­ AI é‡ç»˜ä¸è¦å¼ºåˆ¶ Data URL - 现象:拼图创作页从历å²ç”Ÿæˆå›¾ç‰‡ä¸­é€‰æ‹©ä¸»å›¾ï¼Œå†å…³é—­ AI é‡ç»˜ç”Ÿæˆè‰ç¨¿æ—¶ï¼ŒåŽç«¯æŠ¥â€œä¸Šä¼ å›¾å¿…须是图片 Data URLâ€ã€‚ - 原因:历å²å›¾ `imageSrc` 是 `/generated-puzzle-assets/...` ç§æœ‰å…¼å®¹è·¯å¾„ï¼›AI é‡ç»˜å¼€å¯æ—¶åŽç«¯å‚考图分支会解æžè¯¥è·¯å¾„,但关闭 AI é‡ç»˜çš„“直用上传图â€åˆ†æ”¯æ—§å®žçްåªè°ƒç”¨ `parse_puzzle_image_data_url`。 - 处ç†ï¼šå…³é—­ AI é‡ç»˜æ—¶ä¹Ÿå¤ç”¨æ‹¼å›¾å‚考图解æžå…¥å£ï¼Œå…许 Data URL 与 `/generated-*` 历å²è·¯å¾„ç»Ÿä¸€è½¬æˆ `PuzzleDownloadedImage` åŽæŒä¹…化;å‰ç«¯ä¸éœ€è¦ä¸‹è½½åކå²å›¾å†è½¬ base64。 -- 验è¯ï¼š`npm run test -- src/components/puzzle-agent/PuzzleAgentWorkspace.interaction.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`ã€`cargo test -p api-server puzzle_uploaded_cover_can_reuse_resolved_history_image --manifest-path server-rs\Cargo.toml`ã€`npm run dev:api-server` åŽæ£€æŸ¥ `/healthz`。 -- å…³è”:`server-rs/crates/api-server/src/puzzle/draft.rs`ã€`server-rs/crates/api-server/src/puzzle/vector_engine.rs`ã€`src/components/puzzle-agent/PuzzleAgentWorkspace.interaction.test.tsx`。 +- 验è¯ï¼š`npm run test -- src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`ã€`cargo test -p api-server puzzle_uploaded_cover_can_reuse_resolved_history_image --manifest-path server-rs\Cargo.toml`ã€`npm run dev:api-server` åŽæ£€æŸ¥ `/healthz`。 +- å…³è”:`server-rs/crates/api-server/src/puzzle/draft.rs`ã€`server-rs/crates/api-server/src/puzzle/vector_engine.rs`ã€`src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx`。 ## 拼图结果页局部生图ä¸è¦æ±¡æŸ“è‰ç¨¿ç”Ÿæˆæ€ @@ -1474,7 +1490,7 @@ - 原因:上传图直用路径应把 Data URL 或 `/generated-*` 历å²å›¾è§£æžåŽæŒä¹…化为 `sourceType=uploaded` 的正å¼å€™é€‰ï¼Œå†ç»§ç»­ç”Ÿæˆ 9:16 å…³å¡ç”»é¢ã€UI spritesheet å’Œçº¯èƒŒæ™¯ï¼›å¦‚æžœåªæŠŠ `aiRedraw=false` 当作“ä¸å‚考图片生æˆâ€ï¼Œå°±ä¼šè¯¯èµ°é¦–图生æˆã€‚ - 处ç†ï¼šå…¥å£é¡µç”¨ payload çš„ `aiRedraw` 写入生æˆé¡µ metadata,`puzzleAiRedraw=false` 时进度跳过 `ç”Ÿæˆæ‹¼å›¾é¦–图`ï¼›åŽç«¯ `compile_puzzle_draft` 和结果页 `generate_puzzle_images` 都在 `aiRedraw=false && referenceImageSrc éžç©º` 时走上传图直用候选。结果页关å¡è¯¦æƒ…å¿…é¡»å¤ç”¨ `CreativeImageInputPanel`,ä¸è¦æŠŠæ­£å¼å›¾å½“æˆå¯é‡ç»˜å‚考图;本次上传或历å²é€‰æ‹©çš„å›¾æ‰æ˜¾ç¤º AI é‡ç»˜å¼€å…³å¹¶å¯åˆ é™¤ã€‚ - 验è¯ï¼š`npm run test -- src/services/miniGameDraftGenerationProgress.test.ts src/components/puzzle-result/PuzzleResultView.test.tsx`ã€`cargo test -p api-server puzzle_result_level_direct_upload_skips_cover_image_generation --manifest-path server-rs\Cargo.toml`。 -- å…³è”:`src/services/miniGameDraftGenerationProgress.ts`ã€`src/components/puzzle-agent/PuzzleAgentWorkspace.tsx`ã€`src/components/puzzle-result/PuzzleResultView.tsx`ã€`server-rs/crates/api-server/src/puzzle/draft.rs`ã€`server-rs/crates/api-server/src/puzzle/generation.rs`。 +- å…³è”:`src/services/miniGameDraftGenerationProgress.ts`ã€`src/components/unified-creation/workspaces/PuzzleCreationWorkspace.tsx`ã€`src/components/puzzle-result/PuzzleResultView.tsx`ã€`server-rs/crates/api-server/src/puzzle/draft.rs`ã€`server-rs/crates/api-server/src/puzzle/generation.rs`。 ## Jenkins æ•°æ®åº“导入导出脚本先补 Node 工具链 PATH @@ -1550,8 +1566,8 @@ - çŽ°è±¡ï¼šç«–å±æ‰“å¼€æ‹¼å›¾ã€æŠ“å¤§é¹…æˆ–æ•²æœ¨é±¼åˆ›ä½œé¡µæ—¶ï¼Œæµè§ˆå™¨é¡µé¢æœ¬èº«æ— æ³•æ»šåŠ¨ï¼Œç”ŸæˆæŒ‰é’®æˆ–å³ä¾§è¡¨å•颿¿è½åˆ°è§†å£å¤–ï¼›æœ¨é±¼çš„æ•²å‡»éŸ³æ•ˆå’ŒåŠŸå¾·è¯æ¡çœ‹èµ·æ¥åƒè¢«å¡žè¿›å•独滑动窗å£ã€‚ - åŽŸå› ï¼šå¹³å°æ ¹å£³å›ºå®šä¸€å±å¹¶éšè—溢出,`UnifiedCreationPage` åˆä½¿ç”¨ `h-full min-h-0 overflow-hidden` 和内容区 `overflow-y-auto`,导致滚动责任è½åˆ°å†…éƒ¨å†…å®¹çª—ï¼Œè€Œä¸æ˜¯æ•´ä¸ªåˆ›ä½œ stage。 -- 处ç†ï¼š`UnifiedCreationPage` åªä¿ç•™ç»Ÿä¸€æ ‡é¢˜ã€éšè—字段契约和内容包装,ä¸å†è®¾ç½®å†…éƒ¨çºµå‘æ»šåŠ¨ï¼›æ‹¼å›¾ã€æŠ“å¤§é¹…å’Œæ•²æœ¨é±¼ä¸‰ä¸ªç»Ÿä¸€åˆ›ä½œå…¥å£çš„ `motion.div` stage è´Ÿè´£ `overflow-y-auto overflow-x-hidden`。拼图和抓大鹅在 `unifiedChrome` 下收起旧 `h-full overflow-hidden` 外壳,让表å•ä¸»ä½“è·Ÿéš stage 滚动。 -- 验è¯ï¼šç”¨ç«–屿µè§ˆå™¨è§†å£æ‰“å¼€ `/creation/wooden-fish`ã€`/creation/puzzle` å’Œ `/creation/match3d`,页é¢çº§ stage åº”å¯æ»šåŠ¨åˆ°ç”ŸæˆæŒ‰é’®ï¼›`.unified-creation-page__content` ä¸åº”åŒ…å« `overflow-y-auto`,木鱼工作å°å†…部也ä¸åº”å‡ºçŽ°ç‹¬ç«‹çºµå‘æ»šåŠ¨å®¹å™¨ï¼Œæ‹¼å›¾ / 抓大鹅å¯è§æ ‡é¢˜ä¸åº”é‡å¤ã€‚ +- 处ç†ï¼š`UnifiedCreationPage` 统一负责标题ã€éšè—字段契约ã€å†…容包装和页é¢çº§çºµå‘æ»šåŠ¨ï¼›æ‹¼å›¾ã€æŠ“å¤§é¹…ã€è·³ä¸€è·³å’Œæ•²æœ¨é±¼çš„外层 `motion.div` ä¸å†é¢å¤–包 `overflow-y-auto`。å„工作å°åœ¨ `unifiedChrome` 下收起旧 `h-full overflow-hidden` 外壳,让表å•主体跟éšç»Ÿä¸€é¡µé¢æ»šåŠ¨ã€‚ +- 验è¯ï¼šç”¨ç«–屿µè§ˆå™¨è§†å£æ‰“å¼€ `/creation/wooden-fish`ã€`/creation/puzzle`ã€`/creation/match3d` å’Œ `/creation/jump-hop`ï¼Œç»Ÿä¸€åˆ›ä½œé¡µåº”å¯æ»šåŠ¨åˆ°ç”ŸæˆæŒ‰é’®ï¼›`.unified-creation-page` 应包å«é¡µé¢çº§ `overflow-y-auto`,木鱼工作å°å†…部也ä¸åº”å‡ºçŽ°ç‹¬ç«‹çºµå‘æ»šåŠ¨å®¹å™¨ï¼Œæ‹¼å›¾ / 抓大鹅å¯è§æ ‡é¢˜ä¸åº”é‡å¤ã€‚ - 验è¯ï¼š`npm run test -- src/components/bark-battle-creation/BarkBattleConfigEditor.test.tsx`ã€`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "create tab shows template tabs"`ã€ç§»åŠ¨ç«¯è§†å£æ£€æŸ¥æœ€åŽä¸€ä¸ªè¾“入框与“生æˆè‰ç¨¿â€æŒ‰é’®ä¸é‡å ã€‚ - å…³è”:`src/components/bark-battle-creation/BarkBattleConfigEditor.tsx`ã€`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`ã€`docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md`。 diff --git a/docs/planning/ã€çŽ©æ³•åˆ›ä½œã€‘åˆ›ä½œæµç¨‹ç»Ÿä¸€æ€»è®¡åˆ’-2026-05-30.md b/docs/planning/ã€çŽ©æ³•åˆ›ä½œã€‘åˆ›ä½œæµç¨‹ç»Ÿä¸€æ€»è®¡åˆ’-2026-05-30.md index d0f22c29..58a8132b 100644 --- a/docs/planning/ã€çŽ©æ³•åˆ›ä½œã€‘åˆ›ä½œæµç¨‹ç»Ÿä¸€æ€»è®¡åˆ’-2026-05-30.md +++ b/docs/planning/ã€çŽ©æ³•åˆ›ä½œã€‘åˆ›ä½œæµç¨‹ç»Ÿä¸€æ€»è®¡åˆ’-2026-05-30.md @@ -9,7 +9,7 @@ | 总轮次 | 5 | | 当å‰è½®æ¬¡ | Round 4(已收å£ï¼‰ | | 当å‰é˜¶æ®µ | Phase 6 | -| 当å‰çŠ¶æ€ | Phase 0~6 已收å£ï¼›è·¨çŽ©æ³•å›žå½’ã€ç§»åŠ¨ç«¯ç«–å± smokeã€API smoke å’Œå†»ç»“ææ–™å·²è¡¥é½ | +| 当å‰çŠ¶æ€ | Phase 0~6 已收å£ï¼›ç»Ÿä¸€åˆ›ä½œé¡µå·²å‡çº§ä¸º `UnifiedCreationWorkspace`,平å°å£³ä¸å†ç›´æŽ¥ä¾èµ–æ—§å·¥ä½œå°æ–‡ä»¶ | | 当å‰å¹¶è¡Œæ³¢æ¬¡ | 波次 D(验收与冻结) | | 当å‰é‡ç‚¹ | 以跨玩法门ç¦ä½œä¸ºåŽç»­æ–°å¢žçŽ©æ³•å’Œå›žå½’çš„å¸¸è§„è´¨é‡åŸºçº¿ | @@ -28,7 +28,8 @@ | 阶段 | çŠ¶æ€ | 说明 | | --- | --- | --- | | Phase 0 æ€»è®¡åˆ’ä¸Žé—¨ç¦ | å·²å®Œæˆ | 本文档ã€`docs/planning/README.md`ã€`docs/README.md` å’Œ `.hermes/shared-memory/document-map.md` 已补é½å…¥å£ï¼›åŽç»­æŒ‰ phase 扩展门ç¦ã€‚ | -| Phase 1 首批统一壳 | å·²æ”¶å£ | `puzzle`ã€`match3d`ã€`wooden-fish` 已接入 `UnifiedCreationPage` / `UnifiedGenerationPage`ï¼Œç«–å±æ»šåŠ¨å’Œå­—æ®µå¥‘çº¦å·²å›žå½’ã€‚ | +| Phase 1 首批统一壳 | å·²æ”¶å£ | `puzzle`ã€`match3d`ã€`jump-hop`ã€`wooden-fish` 已接入 `UnifiedCreationPage` / `UnifiedGenerationPage`ï¼Œç«–å±æ»šåŠ¨å’Œå­—æ®µå¥‘çº¦å·²å›žå½’ã€‚ | +| Phase 1 补充统一壳 | å·²æ”¶å£ | `jump-hop` 也已接入 `UnifiedCreationPage` / `UnifiedGenerationPage`ï¼Œç»Ÿä¸€åˆ›ä½œé¡µçŽ°åœ¨æŽ¥ç®¡æ‹¼å›¾ã€æŠ“å¤§é¹…ã€è·³ä¸€è·³å’Œæ•²æœ¨é±¼å››æ¡å…¥å£çš„å¯è§å¤–壳与滚动。 | | Phase 2 契约与é…ç½®æ²»ç† | å·²å®Œæˆ | `creationTypes[].unifiedCreationSpec`ã€å‰ç«¯ fallbackã€åŽå°é…置校验和文档门ç¦å·²æŒ‰çŽ°æœ‰æµ‹è¯•ä¸Ž schema 检查收å£ã€‚ | | Phase 3 剩余表å•/å›¾ç‰‡å·¥ä½œå°æŽ¥å…¥ | å·²æ”¶å£ | 跳一跳ã€å®è´è¯†ç‰©ã€æ–¹æ´žç»“果页与首批普通工作å°å›žå½’已通过;方洞ã€å¤§é±¼æŒ‰å½“å‰å½¢æ€çº³å…¥æœ€å°å›žå½’,åŽç»­è‹¥è¿ç§»å·¥ä½œå°å†å•独立项。 | | Phase 4 ç‰¹æ®Šå·¥ä½œå°æŽ¥å…¥ç­–ç•¥ | å·²æ”¶å£ | RPGã€è§†è§‰å°è¯´ã€æ±ªæ±ªå£°æµªçš„æœ€å°ä¾‹å¤–/闭环回归已通过,例外å£å¾„å·²è½åˆ°å¹³å°æ€»é“¾è·¯æ–‡æ¡£ã€‚ | @@ -69,12 +70,12 @@ ### Phase 1ï¼šé¦–æ‰¹ç»Ÿä¸€å£³æ”¶å£ -目标:用低风险的三æ¡é“¾è·¯éªŒè¯ç»Ÿä¸€åˆ›ä½œ/生æˆå£³ã€‚ +目标:用低风险的四æ¡é“¾è·¯éªŒè¯ç»Ÿä¸€åˆ›ä½œ/生æˆå£³ã€‚ -- 范围:`puzzle`ã€`match3d`ã€`wooden-fish`。 +- 范围:`puzzle`ã€`match3d`ã€`jump-hop`ã€`wooden-fish`。 - 创作页统一ç»è¿‡ `UnifiedCreationPage`,工作å°ä¿ç•™å„自真实输入能力。 - 生æˆé¡µç»Ÿä¸€ç»è¿‡ `UnifiedGenerationPage` å’Œ `CustomWorldGenerationView`。 -- ç«–å±æ»šåŠ¨ç”±å¤–å±‚ stage 承担,é¿å…内层滚动窗。 +- ç«–å±æ»šåŠ¨ç”±ç»Ÿä¸€åˆ›ä½œé¡µæ‰¿æ‹…ï¼Œé¿å…内层滚动窗。 状æ€ï¼šå·²æ”¶å£ã€‚ @@ -113,6 +114,7 @@ - 自动素æç”Ÿæˆèµ°ç»Ÿä¸€ç”Ÿæˆé¡µï¼›æ²¡æœ‰è‡ªåŠ¨ç”Ÿæˆçš„çŽ©æ³•éœ€è¦æ˜Žç¡®è·³è¿‡ç”Ÿæˆé¡µçš„阶段策略。 - 结果页和 runtime ä¸å› è¿ç§»åˆ›ä½œé¡µè€Œæ”¹ä¸šåŠ¡çœŸç›¸ã€‚ - `square-hole`ã€`big-fish` 先评估是å¦ä¿ç•™ Agent å½¢æ€è¿˜æ˜¯è¿åˆ°è¡¨å•/图片工作å°ï¼Œå†å†³å®šæ˜¯å¦è¿›å…¥ç›´æŽ¥è¿ç§»å®žçŽ°ã€‚ +- `jump-hop` 已纳入统一创作壳,åŽç»­è‹¥è¦è°ƒæ•´å­—段或视觉,åªèƒ½åœ¨ç»Ÿä¸€å£³ä¸Žå·¥ä½œå°ä¹‹é—´ååŒæ”¹ï¼Œä¸å†æ¢å¤ç‹¬ç«‹å…¥å£å£³ã€‚ 退出æ¡ä»¶ï¼š @@ -189,7 +191,7 @@ 目标:形æˆåŽç»­æ–°å¢žçŽ©æ³•å¯å¤ç”¨çš„稳定门ç¦ã€‚ - 按玩法输出创作入å£ã€ç”Ÿæˆé¡µã€ç»“果页ã€è¯•玩ã€å‘布ã€ä½œå“æž¶ã€å¹¿åœºã€runtime smoke 矩阵。 -- 增补 `quality-gates/README.md` 与跨玩法回归 / 冒烟门ç¦ï¼Œä¸å†åªè¦†ç›–首批三æ¡é“¾è·¯ã€‚ +- 增补 `quality-gates/README.md` 与跨玩法回归 / 冒烟门ç¦ï¼Œä¸å†åªè¦†ç›–首批四æ¡é“¾è·¯ã€‚ - 固化移动端竖å±ä¼˜å…ˆéªŒæ”¶ï¼Œæ¡Œé¢ç«¯ä½œä¸ºå…¼å®¹éªŒè¯ã€‚ - è¡¥é½â€œæ–°å¢žçŽ©æ³•æŽ¥å…¥ PRD 检查å—â€å’Œä»£ç è¯„审检查清å•。 @@ -217,7 +219,7 @@ Phase 6 ä¸å†ç»§ç»­æ‹†æ–°æ³¢æ¬¡ï¼Œå½“å‰åªæŠŠ Phase 2 到 Phase 5 的最å°éªŒ | 阶段 | 最å°å‘½ä»¤ | 说明 | | --- | --- | --- | | Phase 2 | `npm run check:encoding`ã€`npm run typecheck`ã€`npm run admin-web:typecheck`ã€`npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts src/components/unified-creation/unifiedCreationSpecs.test.ts src/components/unified-creation/UnifiedCreationPage.test.tsx src/components/unified-creation/UnifiedGenerationPage.test.tsx` | 校验入å£é…ç½®ã€ç»Ÿä¸€å­—段 specã€ç»Ÿä¸€åˆ›ä½œé¡µå’Œç»Ÿä¸€ç”Ÿæˆé¡µã€‚ | -| Phase 3 | `npm run test -- src/components/puzzle-agent/PuzzleAgentWorkspace.interaction.test.tsx src/components/match3d-creation/Match3DAgentWorkspace.interaction.test.tsx src/components/wooden-fish-creation/WoodenFishWorkspace.test.tsx src/components/jump-hop-creation/JumpHopWorkspace.test.tsx src/components/jump-hop-result/JumpHopResultView.test.tsx src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx src/components/edutainment-creation/BabyObjectMatchWorkspace.test.tsx src/components/edutainment-result/BabyObjectMatchResultView.test.tsx src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.test.tsx src/components/big-fish-creation/BigFishAgentWorkspace.interaction.test.tsx src/components/big-fish-result/BigFishResultView.test.tsx src/components/big-fish-runtime/BigFishRuntimeShell.test.tsx`ï¼›`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "direct jump hop result route"` | æ ¡éªŒæ™®é€šè¡¨å• / 图片 / 音频工作å°ä»æŒ‰ç»“构化 payload æäº¤ï¼Œè·³ä¸€è·³ç»“果页直达æ¢å¤ä¸ç™½å±ï¼Œå¹¶æŠŠ BabyObjectMatch / BigFish 一并纳入最å°å›žå½’。 | +| Phase 3 | `npm run test -- src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx src/components/unified-creation/workspaces/Match3DCreationWorkspace.interaction.test.tsx src/components/unified-creation/workspaces/WoodenFishCreationWorkspace.test.tsx src/components/unified-creation/workspaces/JumpHopCreationWorkspace.test.tsx src/components/jump-hop-result/JumpHopResultView.test.tsx src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx src/components/edutainment-creation/BabyObjectMatchWorkspace.test.tsx src/components/edutainment-result/BabyObjectMatchResultView.test.tsx src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.test.tsx src/components/big-fish-creation/BigFishAgentWorkspace.interaction.test.tsx src/components/big-fish-result/BigFishResultView.test.tsx src/components/big-fish-runtime/BigFishRuntimeShell.test.tsx`ï¼›`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "direct jump hop result route"` | æ ¡éªŒæ™®é€šè¡¨å• / 图片 / 音频工作å°ä»æŒ‰ç»“构化 payload æäº¤ï¼Œè·³ä¸€è·³ç»“果页直达æ¢å¤ä¸ç™½å±ï¼Œå¹¶æŠŠ BabyObjectMatch / BigFish 一并纳入最å°å›žå½’。 | | Phase 4 | `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "opening RPG agent workspace does not refetch session snapshot in a render loop|create tab resumes agent workspace when draft has no compiled result yet|create tab resumes agent workspace when session has no draft profile even if summary counts look compiled|opening a compiled draft with a missing agent session falls back to draft hub"`ã€`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "create tab opens bark battle entry form from the template card|bark battle draft result can test before publish and publish to work detail|direct bark battle runtime public code opens published runtime"`ã€`npm run check:visual-novel-vn11` | 校验特殊工作å°ä¾‹å¤–ã€Bark Battle 公开闭环和视觉å°è¯´è´Ÿå‘é—¨ç¦ã€‚ | | Phase 5 | `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "agent draft result publishes to gallery from publish panel|creation hub published work enters existing detail view|creation hub published work experience button enters world directly|creation hub published work start uses loaded detail profile instead of library summary"`ã€`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "published puzzle works appear on home and mobile game category channel|published big fish works stay hidden from platform home and mobile game category channel|home recommendation Match3D runtime keeps profile generated models when card summary is stale|home recommendation Match3D runtime passes top-level UI background assets|home recommendation Match3D runtime reloads detail when card only has UI assets"`ã€`npm run test -- src/components/platform-entry/PlatformWorkDetailView.test.tsx src/components/platform-entry/PlatformErrorDialog.test.tsx src/components/platform-entry/PlatformFeedbackView.test.tsx src/components/rpg-entry/rpgEntryWorldPresentation.test.ts`ã€`npm run test -- src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx` | 校验结果页ã€å‘布ã€ä½œå“æž¶ã€å…¬å¼€è¯¦æƒ…ã€æŽ¨è runtime 和公开 read model,并补公开详情作者展示å£å¾„ä¸Žä½œå“æž¶æ¢å¤çŸ©é˜µã€‚ | @@ -232,7 +234,7 @@ Phase 6 ä¸å†ç»§ç»­æ‹†æ–°æ³¢æ¬¡ï¼Œå½“å‰åªæŠŠ Phase 2 到 Phase 5 的最å°éªŒ | T2-1 | 是 | å·²å®Œæˆ | `unifiedCreationSpec` 契约审计 | 字段ç§ç±»ã€å¿…å¡«ã€é˜¶æ®µæ˜ å°„ã€fallback 规则 | T0-1 | 契约 owner | | T2-2 | 是 | å·²å®Œæˆ | åŽå°å…¥å£é…ç½®æ²»ç† | åŽå°é…置校验与é…置说明 | T2-1 | åŽå° owner | | T2-3 | 是 | å·²å®Œæˆ | å‰ç«¯å…¥å£è¯»å–与 fallback 测试 | å…¥å£é…ç½®å•æµ‹ã€å¼‚常兜底测试 | T2-1 | å‰ç«¯ owner | -| T3-1 | 是 | 自动回归通过 | 跳一跳统一接入方案与实现 | 创作页/生æˆé¡µè¿ç§»ã€éªŒæ”¶ | T2-1 | 玩法 owner A | +| T3-1 | 是 | 自动回归通过 | 跳一跳统一接入方案与实现 | 统一创作工作å°ã€ç”Ÿæˆé¡µè¿ç§»ã€éªŒæ”¶ | T2-1 | 玩法 owner A | | T3-2 | 是 | 自动回归通过 | å®è´è¯†ç‰©ç»Ÿä¸€æŽ¥å…¥æ–¹æ¡ˆä¸Žå®žçް | 创作页/生æˆé¡µè¿ç§»ã€éªŒæ”¶ | T2-1 | 玩法 owner B | | T3-3 | 是 | 最å°å›žå½’通过 | 方洞工作å°å½¢æ€è¯„估与è¿ç§»æ–¹æ¡ˆ | ä¿ç•™ Agent å½¢æ€è¿˜æ˜¯è¿è¡¨å•的决策ã€è¾¹ç•Œå’Œé£Žé™©æ¸…å• | T2-1 | 玩法 owner C | | T3-4 | 是 | 最å°å›žå½’通过 | 大鱼工作å°å½¢æ€è¯„估与è¿ç§»æ–¹æ¡ˆ | ä¿ç•™ Agent å½¢æ€è¿˜æ˜¯è¿è¡¨å•的决策ã€è¾¹ç•Œå’Œé£Žé™©æ¸…å• | T2-1 | 玩法 owner D | @@ -243,6 +245,7 @@ Phase 6 ä¸å†ç»§ç»­æ‹†æ–°æ³¢æ¬¡ï¼Œå½“å‰åªæŠŠ Phase 2 到 Phase 5 的最å°éªŒ | T5-2 | 是 | 最å°å›žå½’通过 | ä½œå“æž¶æ¢å¤çŸ©é˜µ | 生æˆä¸­ã€å¤±è´¥ã€å¾…å‘布ã€å·²å‘布æ¢å¤éªŒæ”¶ | T3/T4 方案稳定 | ä½œå“æž¶ owner | | T5-3 | 是 | 最å°å›žå½’通过 | 公开 read model å¯¹é½ | 广场/详情/分享ç ç¼ºå£ä¸Žæ‰§è¡Œæ¸…å• | T3/T4 方案稳定 | åŽç«¯ owner | | T5-4 | 是 | 最å°å›žå½’通过 | 统一错误与完æˆå馈回归 | `PlatformErrorDialog`ã€`PlatformTaskCompletionDialog` 覆盖 | T3/T4 方案稳定 | å¹³å°å£³ owner | +| T5-5 | 是 | 自动回归通过 | ç»Ÿä¸€åˆ›ä½œå£³æ»šåŠ¨æ”¶å£ | `UnifiedCreationPage` 统一接管四æ¡å…¥å£æ»šåЍã€è·³ä¸€è·³çº³å…¥ç»Ÿä¸€å£³ | T3/T4 方案稳定 | å¹³å°å£³ owner | | T6-1 | å¦ | å·²å®Œæˆ | 全链路质é‡é—¨ç¦æ‰©å±• | `quality-gates/README.md`ã€è·¨çŽ©æ³•å›žå½’ / 冒烟门ç¦ã€Phase 2 到 Phase 5 最å°éªŒè¯é›†åˆ | T3/T4/T5 å®Œæˆ | QA owner | | T6-2 | å¦ | å·²å®Œæˆ | å…¨é‡éªŒæ”¶ä¸Žå†»ç»“ | 状æ€è¡¨ã€æœªæŽ¥å…¥å£°æ˜Žã€æœ€ç»ˆæµ‹è¯•记录 | T6-1 | Release owner | @@ -262,7 +265,7 @@ Phase 6 ä¸å†ç»§ç»­æ‹†æ–°æ³¢æ¬¡ï¼Œå½“å‰åªæŠŠ Phase 2 到 Phase 5 的最å°éªŒ | P2-A 契约包 | å¯ä¸Ž P2-Bã€P2-C 并行 | å·²å®Œæˆ | 固定 `unifiedCreationSpec` 字段类型ã€å¿…å¡«ã€é˜¶æ®µæ˜ å°„ã€fallback 规则 | ä¸æŽ¥æ–°çŽ©æ³•ï¼Œä¸æ”¹ UI è®¾è®¡æ–¹å‘ | å‰åŽç«¯å¥‘约ã€é…置字段文档ã€å¥‘约测试 | `npm run test -- src/components/unified-creation/unifiedCreationSpecs.test.ts src/components/platform-entry/platformEntryCreationTypes.test.ts`ï¼›æ¶‰åŠ schema 时追加 `npm run check:spacetime-schema` | | P2-B åŽå°é…置包 | å¯ä¸Ž P2-Aã€P2-C 并行 | å·²å®Œæˆ | åŽå°å…¥å£é…ç½®èƒ½ç¼–è¾‘ã€æ ¡éªŒã€ä¿å­˜ç»Ÿä¸€åˆ›ä½œå¥‘约 | ä¸åšçŽ©æ³•å·¥ä½œå°è¿ç§» | åŽå°è¡¨å•ã€ä¿å­˜æ ¡éªŒã€å¼‚常æç¤ºã€åŽå°å•测 | `npm run admin-web:typecheck`ï¼›`npm run test -- apps/admin-web/src/pages/AdminCreationEntrySwitchPage.test.tsx` | | P2-C å‰å°è¯»å–包 | å¯ä¸Ž P2-Aã€P2-B 并行 | å·²å®Œæˆ | å‰å°ä»Ž `/api/creation-entry/config` 读å–统一 spec,旧åŽç«¯åªèµ°å…œåº• | ä¸æŠŠ fallback 当事实æºï¼Œä¸æ¢å¤ç¡¬ç¼–ç å…¥å£ | 入壿´¾ç”Ÿã€fallback 啿µ‹ã€ç»Ÿä¸€åˆ›ä½œ/生æˆé¡µå›žå½’ | `npm run test -- src/components/unified-creation/UnifiedCreationPage.test.tsx src/components/unified-creation/UnifiedGenerationPage.test.tsx` | -| P3-A 跳一跳接入包 | å¯ä¸Ž P3-Bã€P3-Cã€P3-D 并行 | 自动回归通过 | `jump-hop` 接入统一创作壳ã€ç”Ÿæˆé¡µæˆ–明确跳过策略 | 䏿”¹æ­£å¼ runtime 规则真相,ä¸é‡åšä½œå“æž¶ | å…¥å£é˜¶æ®µæ˜ å°„ã€å·¥ä½œå°æŽ¥å…¥ã€ç”Ÿæˆ/结果跳转ã€ç«–å±éªŒæ”¶ | è·³ä¸€è·³ç›¸å…³å•æµ‹ã€ç»Ÿä¸€åˆ›ä½œé¡µå›žå½’ã€ç§»åŠ¨ç«¯ `/creation/jump-hop` smoke | +| P3-A 跳一跳接入包 | å¯ä¸Ž P3-Bã€P3-Cã€P3-D 并行 | 自动回归通过 | `jump-hop` 接入统一创作壳ã€ç”Ÿæˆé¡µæˆ–明确跳过策略 | 䏿”¹æ­£å¼ runtime 规则真相,ä¸é‡åšä½œå“æž¶ | å…¥å£é˜¶æ®µæ˜ å°„ã€ç»Ÿä¸€å·¥ä½œå°æŽ¥å…¥ã€ç”Ÿæˆ/结果跳转ã€ç«–å±éªŒæ”¶ | è·³ä¸€è·³ç›¸å…³å•æµ‹ã€ç»Ÿä¸€åˆ›ä½œé¡µå›žå½’ã€ç§»åŠ¨ç«¯ `/creation/jump-hop` smoke | | P3-B å®è´è¯†ç‰©æŽ¥å…¥åŒ… | å¯ä¸Ž P3-Aã€P3-Cã€P3-D 并行 | 自动回归通过 | `baby-object-match` 接入统一创作壳ã€ç”Ÿæˆé¡µæˆ–明确跳过策略 | ä¸å¤åˆ¶ä¸Šä¼ /历å²ç´ æé€»è¾‘ | å·¥ä½œå°æŽ¥å…¥ã€èµ„产槽ä½å¤ç”¨ã€ç»“果跳转ã€ç«–å±éªŒæ”¶ | å®è´è¯†ç‰©ç›¸å…³å•测ã€ç»Ÿä¸€åˆ›ä½œé¡µå›žå½’ã€ç§»åŠ¨ç«¯ `/creation/baby-object-match` smoke | | P3-C 方洞评估包 | å¯ä¸Ž P3-Aã€P3-Bã€P3-D 并行 | 部分回归通过 | 判断 `square-hole` ä¿ç•™ Agent å½¢æ€è¿˜æ˜¯è¿è¡¨å•/å›¾ç‰‡å·¥ä½œå° | ä¸ç›´æŽ¥å¤§æ”¹å®žçް | 例外或è¿ç§»æ–¹æ¡ˆã€å­—段清å•ã€é£Žé™©ã€éªŒæ”¶ç”¨ä¾‹ | 文档评审通过;若改代ç ï¼Œè¡¥å¯¹åº”å·¥ä½œå°æµ‹è¯• | | P3-D 大鱼评估包 | å¯ä¸Ž P3-Aã€P3-Bã€P3-C 并行 | 部分回归通过 | 判断 `big-fish` ä¿ç•™ Agent å½¢æ€è¿˜æ˜¯è¿è¡¨å•/å›¾ç‰‡å·¥ä½œå° | ä¸ç›´æŽ¥å¤§æ”¹å®žçް | 例外或è¿ç§»æ–¹æ¡ˆã€å­—段清å•ã€é£Žé™©ã€éªŒæ”¶ç”¨ä¾‹ | 文档评审通过;若改代ç ï¼Œè¡¥å¯¹åº”å·¥ä½œå°æµ‹è¯• | @@ -307,6 +310,7 @@ Phase 6 ä¸å†ç»§ç»­æ‹†æ–°æ³¢æ¬¡ï¼Œå½“å‰åªæŠŠ Phase 2 到 Phase 5 的最å°éªŒ 状æ€ï¼šè‡ªåŠ¨å›žå½’å·²é€šè¿‡ï¼Œé—留形æ€è¯„估按缺å£ä»»åŠ¡ç»§ç»­è·Ÿè¸ªã€‚ - `T3-1` `jump-hop` 统一接入 +- `T5-5` ç»Ÿä¸€åˆ›ä½œå£³æ»šåŠ¨æ”¶å£ - `T3-2` `baby-object-match` 统一接入 - `T3-3` `square-hole` 工作å°å½¢æ€è¯„ä¼° - `T3-4` `big-fish` 工作å°å½¢æ€è¯„ä¼° diff --git a/docs/prd/ã€çŽ©æ³•åˆ›ä½œã€‘è·³ä¸€è·³ä¿¯è§†è§’çŽ©æ³•æ¨¡æ¿PRD-2026-05-19.md b/docs/prd/ã€çŽ©æ³•åˆ›ä½œã€‘è·³ä¸€è·³ä¿¯è§†è§’çŽ©æ³•æ¨¡æ¿PRD-2026-05-19.md index a3ab635a..33dea4b7 100644 --- a/docs/prd/ã€çŽ©æ³•åˆ›ä½œã€‘è·³ä¸€è·³ä¿¯è§†è§’çŽ©æ³•æ¨¡æ¿PRD-2026-05-19.md +++ b/docs/prd/ã€çŽ©æ³•åˆ›ä½œã€‘è·³ä¸€è·³ä¿¯è§†è§’çŽ©æ³•æ¨¡æ¿PRD-2026-05-19.md @@ -80,7 +80,7 @@ jump-hop-gallery-detail 新增å‰ç«¯ç»„件建议: -1. `src/components/jump-hop-creation/JumpHopWorkspace.tsx`ï¼› +1. `src/components/unified-creation/workspaces/JumpHopCreationWorkspace.tsx`ï¼› 2. `src/components/jump-hop-result/JumpHopResultView.tsx`ï¼› 3. `src/components/jump-hop-runtime/JumpHopRuntimeShell.tsx`ï¼› 4. `src/services/jump-hop/jumpHopClient.ts`。 diff --git a/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md b/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md index 001acce2..4c9d9197 100644 --- a/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md +++ b/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md @@ -10,7 +10,7 @@ 创作æ¢å¤å‚æ•°åªä¿ç•™ `sessionId`ã€`profileId`ã€`draftId`ã€`workId` è¿™å››ä¸ªç§æœ‰ query。它们åªå…许在åŒä¸€æ¡åˆ›ä½œé“¾è·¯çš„结果页ã€ç”Ÿæˆé¡µã€å·¥ä½œå°ä¹‹é—´ä¿ç•™ï¼›åˆ‡åˆ°é¦–页ã€å…¬å¼€ä½œå“详情ã€runtime 或å¦ä¸€æ¡çŽ©æ³•é“¾è·¯æ—¶å¿…é¡»æ¸…æŽ‰ã€‚ç”Ÿæˆé¡µç­‰å¾…时间统一以生æˆçжæ€é‡Œçš„ `startedAtMs` ä¸ºå‡†ï¼›åˆ›å»ºè¯¥çŠ¶æ€æ—¶ä¼˜å…ˆä½¿ç”¨åŽç«¯ session 下å‘çš„æ—¶é—´æˆ³ï¼Œä½œå“æ‘˜è¦é‡Œçš„ `updatedAt` ä»åªç”¨äºŽæŽ’åºä¸Žæ‘˜è¦å±•示,ä¸ä½œä¸ºå‰ç«¯è‡ªè¡ŒæŽ¨å¯¼ä¸šåŠ¡çŠ¶æ€çš„真相。 -一期创作æµç¨‹ç»Ÿä¸€åŒ–åªè¦†ç›–æ‹¼å›¾ã€æŠ“å¤§é¹…å’Œæ•²æœ¨é±¼ã€‚ä¸‰è€…åœ¨å‰ç«¯ç»Ÿä¸€ç»è¿‡ `UnifiedCreationPage` å’Œ `UnifiedGenerationPage`:创作页字段清å•ç”±åŽç«¯åœ¨ `GET /api/creation-entry/config` çš„ `creationTypes[].unifiedCreationSpec` 下å‘,å‰ç«¯ä»…在该扩展ä½ç¼ºå¤±æ—¶å›žé€€åˆ°æœ¬åœ°é»˜è®¤ spec;首期字段类型åªä¿ç•™ `text`ã€`select`ã€`image`ã€`audio`。`UnifiedCreationPage` åªæä¾›ç»Ÿä¸€æ ‡é¢˜æ ã€å†…容区和éšè—字段契约,ä¸åœ¨ UI 中é¢å¤–展示字段说明 chip,也ä¸åˆ›å»ºè‡ªå·±çš„çºµå‘æ»šåŠ¨çª—ï¼›ä¸‰æ¡ç»Ÿä¸€åˆ›ä½œå…¥å£ç”±å¹³å° stage æ‰¿æ‹…æ•´é¡µçºµå‘æ»šåŠ¨ï¼Œç«–å±ç§»åŠ¨ç«¯å¿…é¡»èƒ½ä»Žæ ‡é¢˜ã€è¡¨å•一路滑到æäº¤æŒ‰é’®ã€‚å„玩法工作å°è´Ÿè´£æ¸²æŸ“真实输入控件ã€ä¸Šä¼ ã€åކå²ç´ æã€æ ¡éªŒå’Œæäº¤ã€‚拼图工作å°ä»å¤ç”¨æ—¢æœ‰ `PuzzleAgentWorkspace` 的上传ã€è£å‰ªã€åކå²å›¾ã€AI é‡ç»˜å’Œæäº¤é€»è¾‘,抓大鹅继续å¤ç”¨ `Match3DAgentWorkspace` 的题æä¸Žéš¾åº¦è¡¨å•逻辑,但二者在统一壳内都åªå±•示表å•主体,ä¿è¯æ‰“开创作页时能看到统一创作页外观且ä¸å‡ºçŽ°åŒæ ‡é¢˜æˆ–æ—§å¤–å±‚å£³ã€‚æ•²æœ¨é±¼çš„éŸ³æ•ˆå’ŒåŠŸå¾·è¯æ¡é¢æ¿ä¸å¾—放进独立内部滚动容器,移动端应跟éšé¡µé¢è‡ªç„¶æ»šåŠ¨å±•å¼€ã€‚ç”Ÿæˆé¡µç»Ÿä¸€å±•示阶段ã€å½“剿­¥éª¤ã€æ€»è¿›åº¦ã€é”™è¯¯å’Œé‡è¯•动作。视觉å°è¯´ã€`airp`ã€`component`ã€æ±ªæ±ªå£°æµªã€æ–¹æ´žã€å¤§é±¼ã€è·³ä¸€è·³å’Œå®è´è¯†ç‰©ä¸è¿›å…¥ä¸€æœŸæŽ¥çº¿èŒƒå›´ï¼Œå·²æœ‰é“¾è·¯ä¿æŒçŽ°çŠ¶ã€‚ +一期创作æµç¨‹ç»Ÿä¸€åŒ–è¦†ç›–æ‹¼å›¾ã€æŠ“å¤§é¹…ã€è·³ä¸€è·³å’Œæ•²æœ¨é±¼ã€‚四者在å‰ç«¯ç»Ÿä¸€ç»è¿‡ `UnifiedCreationWorkspace` å’Œ `UnifiedGenerationPage`:`UnifiedCreationWorkspace` 作为平å°å£³å”¯ä¸€ä¾èµ–的统一创作编排层,å†å†…部调用 `src/components/unified-creation/workspaces/` 下的 `PuzzleCreationWorkspace`ã€`Match3DCreationWorkspace`ã€`JumpHopCreationWorkspace` å’Œ `WoodenFishCreationWorkspace`;创作页字段清å•ç”±åŽç«¯åœ¨ `GET /api/creation-entry/config` çš„ `creationTypes[].unifiedCreationSpec` 下å‘,å‰ç«¯ä»…在该扩展ä½ç¼ºå¤±æ—¶å›žé€€åˆ°æœ¬åœ°é»˜è®¤ spec;首期字段类型åªä¿ç•™ `text`ã€`select`ã€`image`ã€`audio`。`UnifiedCreationPage` æä¾›ç»Ÿä¸€æ ‡é¢˜æ ã€ç»Ÿä¸€è¿”回入å£ã€é¡µé¢çº§çºµå‘滚动ã€å†…容区和éšè—字段契约,ä¸åœ¨ UI 中é¢å¤–展示字段说明 chip;竖å±ç§»åŠ¨ç«¯å¿…é¡»èƒ½ä»Žæ ‡é¢˜ã€è¡¨å•一路滑到æäº¤æŒ‰é’®ã€‚å„玩法工作å°è´Ÿè´£æ¸²æŸ“真实输入控件ã€ä¸Šä¼ ã€åކå²ç´ æã€æ ¡éªŒå’Œæäº¤ï¼Œä½†è¿”回按钮åªä¿ç•™åœ¨ç»Ÿä¸€é¡µå¤´ï¼Œå·¥ä½œå°å†…部ä¸å†é‡å¤æ¸²æŸ“ã€‚æ‹¼å›¾ã€æŠ“å¤§é¹…ã€è·³ä¸€è·³å’Œæ•²æœ¨é±¼çš„工作å°å®žçŽ°éƒ½å·²æ”¶å£åˆ°ç»Ÿä¸€ç›®å½•,åªä¿ç•™å„自输入逻辑ã€ç´ æé€‰æ‹©å’Œæäº¤æ ¡éªŒï¼Œä¸å†ç”±å¹³å°å£³ç›´æŽ¥ä¾èµ–æ—§å·¥ä½œå°æ–‡ä»¶ã€‚æ•²æœ¨é±¼çš„éŸ³æ•ˆå’ŒåŠŸå¾·è¯æ¡é¢æ¿ä¸å¾—放进独立内部滚动容器,移动端应跟éšé¡µé¢è‡ªç„¶æ»šåŠ¨å±•å¼€ã€‚ç”Ÿæˆé¡µç»Ÿä¸€å±•示阶段ã€å½“剿­¥éª¤ã€æ€»è¿›åº¦ã€é”™è¯¯å’Œé‡è¯•动作。视觉å°è¯´ã€`airp`ã€`component`ã€æ±ªæ±ªå£°æµªã€æ–¹æ´žã€å¤§é±¼å’Œå®è´è¯†ç‰©ä¸è¿›å…¥ä¸€æœŸæŽ¥çº¿èŒƒå›´ï¼Œå·²æœ‰é“¾è·¯ä¿æŒçŽ°çŠ¶ã€‚ åˆ›ä½œè¡¨å•æäº¤å‰çš„æ³¥ç‚¹ä½™é¢å‰ç½®æ ¡éªŒåªå…许用独立弹窗æç¤ºå¤±è´¥åŽŸå› ï¼Œä¸å¾—æŠŠç”¨æˆ·é€€å›žåˆ›ä½œå…¥å£æˆ–玩法模æ¿åˆ—表,也ä¸å¾—清空当å‰è¡¨å•状æ€ã€‚当å‰é€‚ç”¨æ‹¼å›¾ã€æŠ“å¤§é¹…å’Œæ±ªæ±ªå£°æµªç­‰ä¼šåœ¨å‰ç«¯æäº¤å‰æ ¡éªŒæ³¥ç‚¹çš„生æˆå…¥å£ï¼›ä½™é¢ä¸è¶³ã€ä½™é¢è¯»å–失败都应åœç•™åœ¨å½“å‰å·¥ä½œå°ï¼Œç”±ç”¨æˆ·å…³é—­æç¤ºåŽç»§ç»­ç¼–辑或自行补足泥点。 diff --git a/quality-gates/README.md b/quality-gates/README.md index e35f0927..ebe5f074 100644 --- a/quality-gates/README.md +++ b/quality-gates/README.md @@ -16,7 +16,8 @@ npm run check:encoding npm run typecheck npm run admin-web:typecheck npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts src/components/unified-creation/unifiedCreationSpecs.test.ts src/components/unified-creation/UnifiedCreationPage.test.tsx src/components/unified-creation/UnifiedGenerationPage.test.tsx -npm run test -- src/components/puzzle-agent/PuzzleAgentWorkspace.interaction.test.tsx src/components/match3d-creation/Match3DAgentWorkspace.interaction.test.tsx src/components/wooden-fish-creation/WoodenFishWorkspace.test.tsx +npm run test -- src/components/unified-creation/UnifiedCreationWorkspace.test.tsx +npm run test -- src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx src/components/unified-creation/workspaces/Match3DCreationWorkspace.interaction.test.tsx src/components/unified-creation/workspaces/WoodenFishCreationWorkspace.test.tsx ``` ## 使用说明 diff --git a/quality-gates/ã€çŽ©æ³•åˆ›ä½œã€‘ç»Ÿä¸€åˆ›ä½œé¡µé—¨ç¦-2026-05-29.md b/quality-gates/ã€çŽ©æ³•åˆ›ä½œã€‘ç»Ÿä¸€åˆ›ä½œé¡µé—¨ç¦-2026-05-29.md index bd3d7d6c..84bfb3d7 100644 --- a/quality-gates/ã€çŽ©æ³•åˆ›ä½œã€‘ç»Ÿä¸€åˆ›ä½œé¡µé—¨ç¦-2026-05-29.md +++ b/quality-gates/ã€çŽ©æ³•åˆ›ä½œã€‘ç»Ÿä¸€åˆ›ä½œé¡µé—¨ç¦-2026-05-29.md @@ -4,13 +4,14 @@ ```bash npm run test -- src/components/unified-creation/unifiedCreationSpecs.test.ts -npm run test -- src/components/wooden-fish-creation/WoodenFishWorkspace.test.tsx +npm run test -- src/components/unified-creation/UnifiedCreationWorkspace.test.tsx +npm run test -- src/components/unified-creation/workspaces/WoodenFishCreationWorkspace.test.tsx ``` ## 体验检查 -- æ‹¼å›¾ã€æŠ“å¤§é¹…ã€æ•²æœ¨é±¼å‡ä»Ž `/creation/` 进入创作工作å°ã€‚ -- 三æ¡é“¾è·¯éƒ½ç»è¿‡ `UnifiedCreationPage`,字段 spec åªåŒ…å« `text`ã€`select`ã€`image`ã€`audio` 四类。 +- æ‹¼å›¾ã€æŠ“å¤§é¹…ã€è·³ä¸€è·³ã€æ•²æœ¨é±¼å‡ä»Ž `/creation/` 进入创作工作å°ã€‚ +- å››æ¡é“¾è·¯éƒ½ç»è¿‡ `UnifiedCreationPage`,字段 spec åªåŒ…å« `text`ã€`select`ã€`image`ã€`audio` 四类。 - 拼图å‚考图ä»èµ° `CreativeImageInputPanel`ï¼Œä¸æ–°å¢žä¸“属上传入å£ã€‚ - 敲木鱼音频槽ä½èµ° `CreativeAudioInputPanel`,上传ã€å½•音ã€é‡ç½®å’Œé»˜è®¤éŸ³æ•ˆçжæ€å¯ç”¨ã€‚ - æŠ“å¤§é¹…éš¾åº¦åªæ˜¾ç¤ºè½»æ¾ã€æ ‡å‡†ã€è¿›é˜¶ã€ç¡¬æ ¸å››æ¡£ï¼Œæäº¤ payload 仿´¾ç”Ÿ `clearCount` å’Œ `difficulty`。 diff --git a/quality-gates/ã€çŽ©æ³•åˆ›ä½œã€‘è·¨çŽ©æ³•å›žå½’ä¸Žå†’çƒŸé—¨ç¦-2026-05-30.md b/quality-gates/ã€çŽ©æ³•åˆ›ä½œã€‘è·¨çŽ©æ³•å›žå½’ä¸Žå†’çƒŸé—¨ç¦-2026-05-30.md index 54f3b8bf..5c5f1b64 100644 --- a/quality-gates/ã€çŽ©æ³•åˆ›ä½œã€‘è·¨çŽ©æ³•å›žå½’ä¸Žå†’çƒŸé—¨ç¦-2026-05-30.md +++ b/quality-gates/ã€çŽ©æ³•åˆ›ä½œã€‘è·¨çŽ©æ³•å›žå½’ä¸Žå†’çƒŸé—¨ç¦-2026-05-30.md @@ -29,7 +29,7 @@ npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts ## Phase 3ï¼šæ™®é€šå·¥ä½œå° ```bash -npm run test -- src/components/puzzle-agent/PuzzleAgentWorkspace.interaction.test.tsx src/components/match3d-creation/Match3DAgentWorkspace.interaction.test.tsx src/components/wooden-fish-creation/WoodenFishWorkspace.test.tsx src/components/jump-hop-creation/JumpHopWorkspace.test.tsx src/components/jump-hop-result/JumpHopResultView.test.tsx src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx src/components/edutainment-creation/BabyObjectMatchWorkspace.test.tsx src/components/edutainment-result/BabyObjectMatchResultView.test.tsx src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.test.tsx src/components/big-fish-creation/BigFishAgentWorkspace.interaction.test.tsx src/components/big-fish-result/BigFishResultView.test.tsx src/components/big-fish-runtime/BigFishRuntimeShell.test.tsx src/components/square-hole-result/SquareHoleResultView.test.tsx +npm run test -- src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx src/components/unified-creation/workspaces/Match3DCreationWorkspace.interaction.test.tsx src/components/unified-creation/workspaces/WoodenFishCreationWorkspace.test.tsx src/components/unified-creation/workspaces/JumpHopCreationWorkspace.test.tsx src/components/jump-hop-result/JumpHopResultView.test.tsx src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx src/components/edutainment-creation/BabyObjectMatchWorkspace.test.tsx src/components/edutainment-result/BabyObjectMatchResultView.test.tsx src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.test.tsx src/components/big-fish-creation/BigFishAgentWorkspace.interaction.test.tsx src/components/big-fish-result/BigFishResultView.test.tsx src/components/big-fish-runtime/BigFishRuntimeShell.test.tsx src/components/square-hole-result/SquareHoleResultView.test.tsx npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "direct jump hop result route" ``` diff --git a/server-rs/crates/shared-contracts/src/creation_entry_config.rs b/server-rs/crates/shared-contracts/src/creation_entry_config.rs index 47404df8..832570ed 100644 --- a/server-rs/crates/shared-contracts/src/creation_entry_config.rs +++ b/server-rs/crates/shared-contracts/src/creation_entry_config.rs @@ -99,6 +99,21 @@ pub fn build_phase1_unified_creation_spec(play_id: &str) -> Option ( + "jump-hop-workspace", + "jump-hop-generating", + "jump-hop-result", + vec![ + unified_creation_field("workTitle", "text", "ä½œå“æ ‡é¢˜", true), + unified_creation_field("workDescription", "text", "作å“简介", true), + unified_creation_field("themeTags", "text", "主题标签", true), + unified_creation_field("difficulty", "select", "难度", true), + unified_creation_field("stylePreset", "select", "风格", true), + unified_creation_field("characterPrompt", "text", "角色æç¤ºè¯", true), + unified_creation_field("tilePrompt", "text", "åœ°å—æç¤ºè¯", true), + unified_creation_field("endMoodPrompt", "text", "终点氛围", false), + ], + ), "wooden-fish" => ( "wooden-fish-workspace", "wooden-fish-generating", @@ -231,7 +246,7 @@ mod tests { use super::*; #[test] - fn phase1_unified_creation_specs_only_cover_three_templates() { + fn phase1_unified_creation_specs_cover_four_templates() { let puzzle = build_phase1_unified_creation_spec("puzzle").expect("puzzle spec"); assert_eq!(puzzle.fields[0].id, "pictureDescription"); assert_eq!(puzzle.fields[1].kind, "image"); @@ -246,6 +261,10 @@ mod tests { 1 ); + let jump_hop = build_phase1_unified_creation_spec("jump-hop").expect("jump-hop spec"); + assert!(jump_hop.fields.iter().any(|field| field.id == "stylePreset")); + assert!(jump_hop.fields.iter().any(|field| field.id == "endMoodPrompt")); + let wooden_fish = build_phase1_unified_creation_spec("wooden-fish").expect("wooden-fish spec"); assert!(wooden_fish.fields.iter().any(|field| field.kind == "audio")); diff --git a/src/components/common/CreativeImageInputPanel.test.tsx b/src/components/common/CreativeImageInputPanel.test.tsx index 22532783..c5d8dacf 100644 --- a/src/components/common/CreativeImageInputPanel.test.tsx +++ b/src/components/common/CreativeImageInputPanel.test.tsx @@ -101,6 +101,97 @@ test('creative image input panel handles reference uploads and preview', () => { expect(onSubmit).toHaveBeenCalledTimes(1); }); +test('creative image input panel can opt out of filling the parent height', () => { + const { container } = render( + {}} + onMainImageRemove={() => {}} + onAiRedrawChange={() => {}} + onPromptChange={() => {}} + onSubmit={() => {}} + />, + ); + + const panel = container.querySelector('.creative-image-input-panel'); + const body = container.querySelector('.creative-image-input-panel__body'); + const section = container.querySelector('.creative-image-input-panel__section'); + expect(panel?.className).toContain('flex-none'); + expect(panel?.className).not.toContain('flex-1'); + expect(body?.className).toContain('flex-none'); + expect(body?.className).not.toContain('overflow-y-auto'); + expect(section?.className).toContain('flex-none'); + expect(section?.className).not.toContain('overflow-hidden'); +}); + +test('creative image input panel fills the parent height by default', () => { + const { container } = render( + {}} + onMainImageRemove={() => {}} + onAiRedrawChange={() => {}} + onPromptChange={() => {}} + onSubmit={() => {}} + />, + ); + + const panel = container.querySelector('.creative-image-input-panel'); + const body = container.querySelector('.creative-image-input-panel__body'); + const section = container.querySelector('.creative-image-input-panel__section'); + expect(panel?.className).toContain('flex-1'); + expect(panel?.className).not.toContain('flex-none'); + expect(body?.className).toContain('flex-1'); + expect(body?.className).toContain('overflow-y-auto'); + expect(section?.className).toContain('flex-1'); + expect(section?.className).toContain('overflow-hidden'); +}); + test('creative image input panel confirms before removing uploaded image', () => { const onMainImageRemove = vi.fn(); diff --git a/src/components/common/CreativeImageInputPanel.tsx b/src/components/common/CreativeImageInputPanel.tsx index 5c6a7721..1271dbd3 100644 --- a/src/components/common/CreativeImageInputPanel.tsx +++ b/src/components/common/CreativeImageInputPanel.tsx @@ -33,6 +33,7 @@ export type CreativeImageInputPanelLabels = { export type CreativeImageInputPanelProps = { className?: string; + fillHeight?: boolean; disabled?: boolean; isSubmitting?: boolean; mainImageMode?: 'edit' | 'preview'; @@ -77,6 +78,7 @@ const DEFAULT_PROMPT_REFERENCE_LIMIT = 5; export function CreativeImageInputPanel({ className = '', + fillHeight = true, disabled = false, isSubmitting = false, mainImageMode = 'edit', @@ -143,29 +145,48 @@ export function CreativeImageInputPanel({ } }, [previewReferenceImage, promptReferenceImages]); + const bodyClassName = fillHeight + ? 'creative-image-input-panel__body puzzle-creation-form-body flex min-h-0 flex-1 flex-col overflow-hidden pr-0 lg:overflow-y-auto lg:pr-1' + : 'creative-image-input-panel__body puzzle-creation-form-body flex flex-none flex-col overflow-visible pr-0 lg:pr-1'; + const sectionClassName = fillHeight + ? 'creative-image-input-panel__section puzzle-creation-form-section flex min-h-0 flex-1 flex-col overflow-hidden lg:overflow-visible' + : 'creative-image-input-panel__section puzzle-creation-form-section flex flex-none flex-col overflow-visible'; + const gridSizeClassName = fillHeight ? 'min-h-0 flex-1' : 'flex-none'; + const imageFieldClassName = fillHeight + ? 'creative-image-input-panel__image-field puzzle-image-field flex min-h-0 min-w-0 flex-1 flex-col' + : 'creative-image-input-panel__image-field puzzle-image-field flex min-w-0 flex-none flex-col'; + const imageFrameClassName = fillHeight + ? 'creative-image-input-panel__image-frame puzzle-image-card-frame flex min-h-0 flex-1 items-center justify-center' + : 'creative-image-input-panel__image-frame puzzle-image-card-frame flex flex-none items-center justify-center'; + const imageCardClassName = fillHeight + ? 'creative-image-input-panel__image-card puzzle-image-upload-card relative aspect-square h-full min-h-[14rem] max-h-full max-w-full overflow-hidden rounded-[1.25rem] border border-[var(--platform-subpanel-border)] bg-white/90 shadow-[0_12px_28px_rgba(15,23,42,0.08)] transition sm:min-h-[18rem] lg:h-auto lg:w-full' + : 'creative-image-input-panel__image-card puzzle-image-upload-card relative aspect-square w-full min-h-[14rem] max-w-full overflow-hidden rounded-[1.25rem] border border-[var(--platform-subpanel-border)] bg-white/90 shadow-[0_12px_28px_rgba(15,23,42,0.08)] transition sm:min-h-[18rem]'; + return (
-
-
+
+
{labels.imageField}
-
-
+
+
{canEditMainImage ? ( <> void; -}; - -export function Match3DDraftReadyView({ - session, - isBusy = false, - error = null, - onBack, -}: Match3DDraftReadyViewProps) { - const draft = session.draft; - const title = draft?.gameName || '抓大鹅è‰ç¨¿'; - - return ( -
-
- -
- -
-
-
- -
- -
-
- {title} -
-
- {draft?.summaryText ?? session.lastAssistantReply ?? ''} -
- - {draft ? ( -
-
-
- 题æ -
-
- {draft.themeText} -
-
-
-
- ç‰©å“ -
-
- {draft.totalItemCount ?? draft.clearCount * 3} ä»¶ -
-
-
-
- 难度 -
-
- {draft.difficulty} -
-
-
- ) : null} - - {error ? ( -
- {error} -
- ) : null} -
-
-
- -
- -
-
- ); -} - -export default Match3DDraftReadyView; diff --git a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx index 0992c546..1df46027 100644 --- a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx +++ b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx @@ -2856,10 +2856,10 @@ const CustomWorldGenerationView = lazy(async () => { }; }); -const UnifiedCreationPage = lazy(async () => { - const module = await import('../unified-creation/UnifiedCreationPage'); +const UnifiedCreationWorkspace = lazy(async () => { + const module = await import('../unified-creation/UnifiedCreationWorkspace'); return { - default: module.UnifiedCreationPage, + default: module.UnifiedCreationWorkspace, }; }); @@ -2907,13 +2907,6 @@ const BigFishRuntimeShell = lazy(async () => { }; }); -const Match3DAgentWorkspace = lazy(async () => { - const module = await import('../match3d-creation/Match3DAgentWorkspace'); - return { - default: module.Match3DAgentWorkspace, - }; -}); - const Match3DResultView = lazy(async () => { const module = await import('../match3d-result/Match3DResultView'); return { @@ -2951,13 +2944,6 @@ const SquareHoleRuntimeShell = lazy(async () => { }; }); -const JumpHopWorkspace = lazy(async () => { - const module = await import('../jump-hop-creation/JumpHopWorkspace'); - return { - default: module.JumpHopWorkspace, - }; -}); - const JumpHopResultView = lazy(async () => { const module = await import('../jump-hop-result/JumpHopResultView'); return { @@ -2972,13 +2958,6 @@ const JumpHopRuntimeShell = lazy(async () => { }; }); -const WoodenFishWorkspace = lazy(async () => { - const module = await import('../wooden-fish-creation/WoodenFishWorkspace'); - return { - default: module.WoodenFishWorkspace, - }; -}); - const WoodenFishResultView = lazy(async () => { const module = await import('../wooden-fish-result/WoodenFishResultView'); return { @@ -3032,13 +3011,6 @@ const CustomWorldCreationHub = lazy(async () => { }; }); -const PuzzleAgentWorkspace = lazy(async () => { - const module = await import('../puzzle-agent/PuzzleAgentWorkspace'); - return { - default: module.PuzzleAgentWorkspace, - }; -}); - const CreativeAgentWorkspace = lazy(async () => { const module = await import('../creative-agent/CreativeAgentWorkspace'); return { @@ -15732,37 +15704,33 @@ export function PlatformEntryFlowShellImpl({ initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -12 }} - className="flex h-full min-h-0 flex-col overflow-y-auto overflow-x-hidden" + className="flex h-full min-h-0 flex-col" > } > - - { - void executeMatch3DAction(payload); - }} - initialFormPayload={match3dFormDraftPayload} - title={null} - unifiedChrome - onCreateFromForm={(payload) => { - runProtectedAction(() => { - void createMatch3DDraftFromForm(payload); - }); - }} - /> - + session={match3dSession} + isBusy={isStreamingMatch3DReply} + error={match3dError} + onBack={leaveMatch3DFlow} + onExecuteAction={(payload) => { + void executeMatch3DAction(payload); + }} + initialFormPayload={match3dFormDraftPayload} + onCreateFromForm={(payload) => { + runProtectedAction(() => { + void createMatch3DDraftFromForm(payload); + }); + }} + /> )} @@ -16398,7 +16366,12 @@ export function PlatformEntryFlowShellImpl({ } > - } > - @@ -16540,26 +16504,24 @@ export function PlatformEntryFlowShellImpl({ initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -12 }} - className="flex h-full min-h-0 flex-col overflow-y-auto overflow-x-hidden" + className="flex h-full min-h-0 flex-col" > } > - - { - void compileWoodenFishSession(result, payload); - }} - /> - + isBusy={isWoodenFishBusy} + error={woodenFishError} + onBack={leaveWoodenFishFlow} + onSubmitted={(result, payload) => { + void compileWoodenFishSession(result, payload); + }} + /> )} @@ -16695,39 +16657,35 @@ export function PlatformEntryFlowShellImpl({ initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -12 }} - className="flex h-full min-h-0 flex-col overflow-y-auto overflow-x-hidden" + className="flex h-full min-h-0 flex-col" > } > - - { - void submitPuzzleMessage(payload); - }} - onExecuteAction={(payload) => { - executePuzzleWorkspaceAction(payload); - }} - initialFormPayload={puzzleFormDraftPayload} - title={null} - unifiedChrome - onCreateFromForm={(payload) => { - void createPuzzleDraftFromForm(payload); - }} - onAutoSaveForm={(payload) => { - void savePuzzleFormDraft(payload); - }} - /> - + session={puzzleSession} + isBusy={isStreamingPuzzleReply} + error={puzzleError} + onBack={leavePuzzleFlow} + onSubmitMessage={(payload) => { + void submitPuzzleMessage(payload); + }} + onExecuteAction={(payload) => { + executePuzzleWorkspaceAction(payload); + }} + initialFormPayload={puzzleFormDraftPayload} + onCreateFromForm={(payload) => { + void createPuzzleDraftFromForm(payload); + }} + onAutoSaveForm={(payload) => { + void savePuzzleFormDraft(payload); + }} + /> )} diff --git a/src/components/puzzle-agent/puzzleCreationTemplates.ts b/src/components/puzzle-agent/puzzleCreationTemplates.ts deleted file mode 100644 index c586d816..00000000 --- a/src/components/puzzle-agent/puzzleCreationTemplates.ts +++ /dev/null @@ -1,94 +0,0 @@ -export type PuzzleCreationTemplate = { - id: string; - title: string; - imageSrc: string; - prompt: string; -}; - -// 中文注释:模æ¿åªæœåС入å£å¿«é€Ÿå¡«è¯ï¼Œæ­£å¼ä½œå“ä¿¡æ¯ä»åœ¨ç»“果页补全。 -export const PUZZLE_CREATION_TEMPLATES: PuzzleCreationTemplate[] = [ - { - id: 'couple-memory', - title: '情侣åˆç…§æ‹¼å›¾', - imageSrc: '/puzzle-creation-templates/couple-memory.webp', - prompt: - '温暖自然光下的一对情侣纪念åˆç…§ï¼ŒåŸŽå¸‚å’–å•¡é¦†çª—è¾¹ï¼Œæ¡Œé¢æœ‰èбæŸå’Œä¸¤æ¯çƒ­é¥®ï¼Œäººç‰©ç¥žæƒ…自然,画é¢ä¸»ä½“清晰,å‰ä¸­åŽæ™¯å±‚次明确。', - }, - { - id: 'family-keepsake', - title: '家庭纪念拼图', - imageSrc: '/puzzle-creation-templates/family-keepsake.webp', - prompt: - '三代家人在客厅沙å‘å‰çš„家庭纪念åˆç…§ï¼ŒæŸ”å’ŒåˆåŽé˜³å…‰ï¼Œå­©å­æŠ±ç€ç”Ÿæ—¥è›‹ç³•ï¼Œé•¿è¾ˆå¾®ç¬‘ï¼Œç”»é¢æ¸©æš–å®Œæ•´ï¼Œç»†èŠ‚ä¸°å¯Œä½†ä¸æ‚乱。', - }, - { - id: 'friends-party', - title: '朋å‹èšä¼šæ‹¼å›¾', - imageSrc: '/puzzle-creation-templates/friends-party.webp', - prompt: - '朋å‹ä»¬åœ¨éœ²å°å¤œæ™šèšä¼šï¼Œå½©ç¯ã€æ¡Œä¸Šé›¶é£Ÿå’Œä¸¾æ¯çž¬é—´ï¼Œäººç‰©åˆ†å¸ƒæœ‰å±‚次,中央焦点清楚,氛围轻æ¾çƒ­é—¹ã€‚', - }, - { - id: 'festival-card', - title: 'èŠ‚æ—¥è´ºå¡æ‹¼å›¾', - imageSrc: '/puzzle-creation-templates/festival-card.webp', - prompt: - 'èŠ‚æ—¥é¤æ¡Œä¸Žç¤¼ç‰©å¸ƒç½®ï¼Œæš–色ç¯å…‰ã€å½©å¸¦ã€èœ¡çƒ›å’Œçª—外烟花,画é¢åƒæ— å­—è´ºå¡ï¼Œä¸»ä½“集中,边角细节å¯è¾¨ã€‚', - }, - { - id: 'knowledge-summary', - title: '知识总结拼图', - imageSrc: '/puzzle-creation-templates/knowledge-summary.webp', - prompt: - '一张无文字的知识学习主题æ’画,书桌上有打开的笔记本ã€ä¾¿ç­¾ã€å’–å•¡ã€å°ç¯å’Œæ€ç»´å¯¼å›¾å¼å›¾å½¢å…ƒç´ ï¼Œæž„图整æ´ï¼Œé‡ç‚¹æ˜Žç¡®ã€‚', - }, - { - id: 'product-detail', - title: '商å“细节拼图', - imageSrc: '/puzzle-creation-templates/product-detail.webp', - prompt: - '精致商å“é™ç‰©å±•示,一åªé«˜è´¨æ„Ÿé¦™æ°´ç“¶æ”¾åœ¨ä¸ç»¸ä¸ŽèŠ±ç“£ä¹‹é—´ï¼ŒçŽ»ç’ƒå光清晰,包装和æè´¨ç»†èŠ‚ä¸°å¯Œï¼ŒèƒŒæ™¯å¹²å‡€ã€‚', - }, - { - id: 'healing-landscape', - title: '治愈风景拼图', - imageSrc: '/puzzle-creation-templates/healing-landscape.webp', - prompt: - '治愈风景æ’画,清晨湖边ã€è–„雾ã€è¿œå±±ã€æœ¨æ ˆé“和一ç›å°ç¯ï¼Œè‰²å½©æŸ”和。', - }, - { - id: 'cute-pet', - title: '宠物å¯çˆ±æ‹¼å›¾', - imageSrc: '/puzzle-creation-templates/cute-pet.webp', - prompt: - '一åªå¯çˆ±çš„æ©˜çŒ«è¶´åœ¨é˜³å…‰çª—å°ä¸Šï¼Œæ—边有绿æ¤ã€æ¯›çº¿çƒå’Œå°æ¯¯å­ï¼ŒçŒ«çš„è¡¨æƒ…æ¸…æ¥šï¼Œç”»é¢æ¸©æŸ”干净。', - }, - { - id: 'hot-topic-poster', - title: '热点海报拼图', - imageSrc: '/puzzle-creation-templates/hot-topic-poster.webp', - prompt: - '电影感热点海报风æ’画,雨夜街头ã€éœ“虹åå…‰ã€å¥”跑的人影和远处光æŸï¼Œå¼ºçƒˆè§†è§‰ç„¦ç‚¹ï¼Œç”»é¢æ— æ–‡å­—。', - }, - { - id: 'event-invitation', - title: '活动邀请拼图', - imageSrc: '/puzzle-creation-templates/event-invitation.webp', - prompt: - '活动邀请主题æ’画,展厅入å£ã€èŠ±è‰ºè£…ç½®ã€ç­¾åˆ°å°å’ŒæŸ”å’Œç¯å¸¦ï¼Œäººç¾¤å‰ªå½±è‡ªç„¶åˆ†å¸ƒï¼Œç”»é¢é«˜çº§å¹²å‡€ï¼Œæ— æ–‡å­—。', - }, - { - id: 'daily-challenge', - title: 'æ¯æ—¥æŒ‘战拼图', - imageSrc: '/puzzle-creation-templates/daily-challenge.webp', - prompt: - 'æ¯æ—¥æŒ‘战主题æ’画,清爽桌é¢ä¸Šæ‘†æ”¾ç›¸æœºã€æ˜Žä¿¡ç‰‡ã€è®¡æ—¶å™¨å’Œå°å¥–ç« ï¼Œè‰²å½©æ˜Žäº®ï¼Œæž„å›¾æœ‰è¶£ï¼Œç»†èŠ‚å¯æ‹†è§£ã€‚', - }, - { - id: 'children-learning', - title: '儿童认知拼图', - imageSrc: '/puzzle-creation-templates/children-learning.webp', - prompt: - '儿童认知学习æ’画,木质桌é¢ä¸Šæœ‰ç§¯æœ¨ã€å½©è‰²å½¢çжã€åŠ¨ç‰©çŽ©å¶å’Œå°ä¹¦æœ¬ï¼Œè‰²å½©æ˜Žå¿«ï¼Œå…ƒç´ è¾¹ç•Œæ¸…晰,无文字。', - }, -]; diff --git a/src/components/puzzle-result/PuzzleResultView.tsx b/src/components/puzzle-result/PuzzleResultView.tsx index 093b529a..ce517815 100644 --- a/src/components/puzzle-result/PuzzleResultView.tsx +++ b/src/components/puzzle-result/PuzzleResultView.tsx @@ -24,12 +24,12 @@ import { getPuzzleHistoryAssetReferenceLabel } from '../../services/puzzle-works import { readPuzzleReferenceImageAsDataUrl } from '../../services/puzzleReferenceImage'; import { useAuthUi } from '../auth/AuthUiContext'; import { CreativeImageInputPanel } from '../common/CreativeImageInputPanel'; -import PuzzleHistoryAssetPickerDialog from '../puzzle-agent/PuzzleHistoryAssetPickerDialog'; +import PuzzleHistoryAssetPickerDialog from '../unified-creation/shared/PuzzleHistoryAssetPickerDialog'; import { PUZZLE_IMAGE_MODEL_GPT_IMAGE_2, type PuzzleImageModelId, -} from '../puzzle-agent/puzzleImageModelOptions'; -import { PuzzleImageModelPicker } from '../puzzle-agent/PuzzleImageModelPicker'; +} from '../unified-creation/shared/puzzleImageModelOptions'; +import { PuzzleImageModelPicker } from '../unified-creation/shared/PuzzleImageModelPicker'; import { ResolvedAssetImage } from '../ResolvedAssetImage'; type PuzzleResultViewProps = { diff --git a/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx b/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx index 761d3acb..602d9470 100644 --- a/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx +++ b/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx @@ -803,8 +803,8 @@ vi.mock('../../services/puzzle-agent', () => ({ streamPuzzleAgentMessage: vi.fn(), })); -vi.mock('../puzzle-agent/PuzzleAgentWorkspace', () => ({ - PuzzleAgentWorkspace: ({ +vi.mock('../unified-creation/workspaces/PuzzleCreationWorkspace', () => ({ + PuzzleCreationWorkspace: ({ session, isBusy, error, @@ -1007,8 +1007,8 @@ vi.mock('../match3d-result/Match3DResultView', () => ({ ), })); -vi.mock('../match3d-creation/Match3DAgentWorkspace', () => ({ - Match3DAgentWorkspace: ({ +vi.mock('../unified-creation/workspaces/Match3DCreationWorkspace', () => ({ + Match3DCreationWorkspace: ({ session, isBusy, error, diff --git a/src/components/unified-creation/UnifiedCreationPage.test.tsx b/src/components/unified-creation/UnifiedCreationPage.test.tsx index a63a2a2e..ec6c0c07 100644 --- a/src/components/unified-creation/UnifiedCreationPage.test.tsx +++ b/src/components/unified-creation/UnifiedCreationPage.test.tsx @@ -1,15 +1,20 @@ /* @vitest-environment jsdom */ -import { render, screen } from '@testing-library/react'; -import { describe, expect, test } from 'vitest'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { describe, expect, test, vi } from 'vitest'; import { UnifiedCreationPage } from './UnifiedCreationPage'; import { getUnifiedCreationSpec } from './unifiedCreationSpecs'; describe('UnifiedCreationPage', () => { test('按åŽç«¯å­—段 spec 暴露统一创作页字段契约', () => { + const onBack = vi.fn(); + render( - +
敲木鱼工作å°
, ); @@ -41,6 +46,8 @@ describe('UnifiedCreationPage', () => { expect(screen.getByTestId('unified-creation-play-badge').textContent).toBe( 'wooden-fish', ); + fireEvent.click(screen.getByRole('button', { name: '返回' })); + expect(onBack).toHaveBeenCalledTimes(1); expect(screen.queryByLabelText('创作字段')).toBeNull(); expect(screen.queryByTestId('unified-creation-visible-field')).toBeNull(); expect( @@ -48,7 +55,19 @@ describe('UnifiedCreationPage', () => { .getByText('敲木鱼工作å°') .closest('.unified-creation-page__content') ?.className, - ).not.toContain('overflow-y-auto'); - expect(root?.className).not.toContain('overflow-hidden'); + ).toContain('flex'); + expect( + screen + .getByText('敲木鱼工作å°') + .closest('.unified-creation-page__content') + ?.className, + ).toContain('min-h-max'); + expect( + screen + .getByText('敲木鱼工作å°') + .closest('.unified-creation-page__content') + ?.className, + ).not.toContain('min-h-0'); + expect(root?.className).toContain('overflow-y-auto'); }); }); diff --git a/src/components/unified-creation/UnifiedCreationPage.tsx b/src/components/unified-creation/UnifiedCreationPage.tsx index 030c37c1..ddfc2580 100644 --- a/src/components/unified-creation/UnifiedCreationPage.tsx +++ b/src/components/unified-creation/UnifiedCreationPage.tsx @@ -1,3 +1,4 @@ +import { ArrowLeft } from 'lucide-react'; import type { ReactNode } from 'react'; import type { UnifiedCreationSpec } from './unifiedCreationSpecs'; @@ -5,15 +6,19 @@ import type { UnifiedCreationSpec } from './unifiedCreationSpecs'; type UnifiedCreationPageProps = { spec: UnifiedCreationSpec; children: ReactNode; + onBack?: () => void; + isBackDisabled?: boolean; }; export function UnifiedCreationPage({ spec, children, + onBack, + isBackDisabled = false, }: UnifiedCreationPageProps) { return (
field.kind).join(',')} data-workspace-stage={spec.workspaceStage} @@ -21,10 +26,22 @@ export function UnifiedCreationPage({ data-result-stage={spec.resultStage} >
-
-

- {spec.title} -

+
+ {onBack ? ( + + ) : ( +
+
+

+ {spec.title} +

+

{spec.title}

@@ -49,7 +71,7 @@ export function UnifiedCreationPage({ ))}
-
+
{children}
diff --git a/src/components/unified-creation/UnifiedCreationWorkspace.test.tsx b/src/components/unified-creation/UnifiedCreationWorkspace.test.tsx new file mode 100644 index 00000000..29e0e1cf --- /dev/null +++ b/src/components/unified-creation/UnifiedCreationWorkspace.test.tsx @@ -0,0 +1,184 @@ +/* @vitest-environment jsdom */ + +import { render, screen } from '@testing-library/react'; +import { describe, expect, test, vi } from 'vitest'; + +import { UnifiedCreationWorkspace } from './UnifiedCreationWorkspace'; +import { getUnifiedCreationSpec } from './unifiedCreationSpecs'; + +vi.mock('./workspaces/PuzzleCreationWorkspace', () => ({ + PuzzleCreationWorkspace: ({ + unifiedChrome, + }: { + unifiedChrome?: boolean; + }) => ( +
+ æ‹¼å›¾å·¥ä½œå° +
+ ), +})); + +vi.mock('./workspaces/Match3DCreationWorkspace', () => ({ + Match3DCreationWorkspace: ({ + unifiedChrome, + }: { + unifiedChrome?: boolean; + }) => ( +
+ æŠ“å¤§é¹…å·¥ä½œå° +
+ ), +})); + +vi.mock('./workspaces/JumpHopCreationWorkspace', () => ({ + JumpHopCreationWorkspace: ({ + unifiedChrome, + }: { + unifiedChrome?: boolean; + }) => ( +
+ è·³ä¸€è·³å·¥ä½œå° +
+ ), +})); + +vi.mock('./workspaces/WoodenFishCreationWorkspace', () => ({ + WoodenFishCreationWorkspace: ({ + unifiedChrome, + }: { + unifiedChrome?: boolean; + }) => ( +
+ æ•²æœ¨é±¼å·¥ä½œå° +
+ ), +})); + +describe('UnifiedCreationWorkspace', () => { + test('统一承载四æ¡é¦–批创作入å£', () => { + const onBack = vi.fn(); + + const puzzleResult = render( + {}} + onExecuteAction={() => {}} + onCreateFromForm={() => {}} + onAutoSaveForm={() => {}} + initialFormPayload={null} + />, + ); + const puzzleWorkspace = screen + .getByText('拼图工作å°') + .closest('[data-unified-chrome]'); + const puzzlePage = screen + .getByText('拼图工作å°') + .closest('.unified-creation-page'); + expect(puzzleWorkspace?.getAttribute('data-unified-chrome')).toBe('true'); + expect(puzzlePage?.getAttribute('data-play-id')).toBe('puzzle'); + expect(screen.getByRole('button', { name: '返回' })).toBeTruthy(); + puzzleResult.unmount(); + + const match3dResult = render( + {}} + onSubmitMessage={() => {}} + onCreateFromForm={() => {}} + initialFormPayload={null} + />, + ); + const match3dWorkspace = screen + .getByText('抓大鹅工作å°') + .closest('[data-unified-chrome]'); + const match3dPage = screen + .getByText('抓大鹅工作å°') + .closest('.unified-creation-page'); + expect(match3dWorkspace?.getAttribute('data-unified-chrome')).toBe('true'); + expect(match3dPage?.getAttribute('data-play-id')).toBe('match3d'); + match3dResult.unmount(); + + const jumpHopResult = render( + {}} + />, + ); + const jumpHopWorkspace = screen + .getByText('跳一跳工作å°') + .closest('[data-unified-chrome]'); + const jumpHopPage = screen + .getByText('跳一跳工作å°') + .closest('.unified-creation-page'); + expect(jumpHopWorkspace?.getAttribute('data-unified-chrome')).toBe('true'); + expect(jumpHopPage?.getAttribute('data-play-id')).toBe('jump-hop'); + jumpHopResult.unmount(); + + const woodenFishResult = render( + {}} + />, + ); + const woodenFishWorkspace = screen + .getByText('敲木鱼工作å°') + .closest('[data-unified-chrome]'); + const woodenFishPage = screen + .getByText('敲木鱼工作å°') + .closest('.unified-creation-page'); + expect(woodenFishWorkspace?.getAttribute('data-unified-chrome')).toBe( + 'true', + ); + expect(woodenFishPage?.getAttribute('data-play-id')).toBe('wooden-fish'); + woodenFishResult.unmount(); + }); + + test('统一页头返回按钮会é€ä¼ ç»™å½“å‰çŽ©æ³•å£³å±‚', async () => { + const onBack = vi.fn(); + + render( + {}} + />, + ); + + screen.getByRole('button', { name: '返回' }).click(); + expect(onBack).toHaveBeenCalledTimes(1); + expect(screen.queryAllByRole('button', { name: '返回' })).toHaveLength(1); + }); +}); diff --git a/src/components/unified-creation/UnifiedCreationWorkspace.tsx b/src/components/unified-creation/UnifiedCreationWorkspace.tsx new file mode 100644 index 00000000..2c282289 --- /dev/null +++ b/src/components/unified-creation/UnifiedCreationWorkspace.tsx @@ -0,0 +1,125 @@ +import type { ComponentProps } from 'react'; + +import { UnifiedCreationPage } from './UnifiedCreationPage'; +import type { UnifiedCreationSpec } from './unifiedCreationSpecs'; +import { Match3DCreationWorkspace } from './workspaces/Match3DCreationWorkspace'; +import { PuzzleCreationWorkspace } from './workspaces/PuzzleCreationWorkspace'; +import { JumpHopCreationWorkspace } from './workspaces/JumpHopCreationWorkspace'; +import { WoodenFishCreationWorkspace } from './workspaces/WoodenFishCreationWorkspace'; + +type PuzzleCreationWorkspaceProps = ComponentProps< + typeof PuzzleCreationWorkspace +>; +type Match3DCreationWorkspaceProps = ComponentProps< + typeof Match3DCreationWorkspace +>; +type JumpHopCreationWorkspaceProps = ComponentProps< + typeof JumpHopCreationWorkspace +>; +type WoodenFishCreationWorkspaceProps = ComponentProps< + typeof WoodenFishCreationWorkspace +>; + +type UnifiedCreationWorkspaceBaseProps = { + spec: UnifiedCreationSpec; +}; + +type PuzzleUnifiedCreationWorkspaceProps = + UnifiedCreationWorkspaceBaseProps & { + playId: 'puzzle'; + } & PuzzleCreationWorkspaceProps; + +type Match3DUnifiedCreationWorkspaceProps = + UnifiedCreationWorkspaceBaseProps & { + playId: 'match3d'; + } & Match3DCreationWorkspaceProps; + +type JumpHopUnifiedCreationWorkspaceProps = + UnifiedCreationWorkspaceBaseProps & { + playId: 'jump-hop'; + } & JumpHopCreationWorkspaceProps; + +type WoodenFishUnifiedCreationWorkspaceProps = + UnifiedCreationWorkspaceBaseProps & { + playId: 'wooden-fish'; + } & WoodenFishCreationWorkspaceProps; + +export type UnifiedCreationWorkspaceProps = + | PuzzleUnifiedCreationWorkspaceProps + | Match3DUnifiedCreationWorkspaceProps + | JumpHopUnifiedCreationWorkspaceProps + | WoodenFishUnifiedCreationWorkspaceProps; + +export function UnifiedCreationWorkspace(props: UnifiedCreationWorkspaceProps) { + switch (props.playId) { + case 'puzzle': + return ( + + + + ); + case 'match3d': + return ( + + + + ); + case 'jump-hop': + return ( + + + + ); + case 'wooden-fish': + return ( + + + + ); + default: { + const exhaustiveCheck: never = props; + return exhaustiveCheck; + } + } +} + +export default UnifiedCreationWorkspace; diff --git a/src/components/unified-creation/UnifiedGenerationPage.test.tsx b/src/components/unified-creation/UnifiedGenerationPage.test.tsx index 015199b2..f09d889e 100644 --- a/src/components/unified-creation/UnifiedGenerationPage.test.tsx +++ b/src/components/unified-creation/UnifiedGenerationPage.test.tsx @@ -50,4 +50,22 @@ describe('UnifiedGenerationPage', () => { expect(screen.getAllByText('ç”Ÿæˆæ‹¼å›¾é¦–图').length).toBeGreaterThan(0); expect(screen.getByText('当剿‹¼å›¾ä¿¡æ¯')).toBeTruthy(); }); + + test('jump-hop generation page uses unified copy', () => { + render( + {}} + onEditSetting={() => {}} + onRetry={() => {}} + />, + ); + + expect(document.body.textContent).toContain('跳一跳è‰ç¨¿ç”Ÿæˆè¿›åº¦'); + expect(screen.getByText('ç´ æç”Ÿæˆä¸­')).toBeTruthy(); + expect(screen.getByText('当å‰è·³ä¸€è·³ä¿¡æ¯')).toBeTruthy(); + }); }); diff --git a/src/components/puzzle-agent/PuzzleHistoryAssetPickerDialog.tsx b/src/components/unified-creation/shared/PuzzleHistoryAssetPickerDialog.tsx similarity index 95% rename from src/components/puzzle-agent/PuzzleHistoryAssetPickerDialog.tsx rename to src/components/unified-creation/shared/PuzzleHistoryAssetPickerDialog.tsx index ef47d349..d8de0319 100644 --- a/src/components/puzzle-agent/PuzzleHistoryAssetPickerDialog.tsx +++ b/src/components/unified-creation/shared/PuzzleHistoryAssetPickerDialog.tsx @@ -5,13 +5,13 @@ import { createPortal } from 'react-dom'; import { puzzleAssetClient, type PuzzleHistoryAsset, -} from '../../services/puzzle-works/puzzleAssetClient'; +} from '../../../services/puzzle-works/puzzleAssetClient'; import { formatPuzzleHistoryAssetCreatedAt, getPuzzleHistoryAssetDisplayName, -} from '../../services/puzzle-works/puzzleHistoryAsset'; -import { useAuthUi } from '../auth/AuthUiContext'; -import { ResolvedAssetImage } from '../ResolvedAssetImage'; +} from '../../../services/puzzle-works/puzzleHistoryAsset'; +import { useAuthUi } from '../../auth/AuthUiContext'; +import { ResolvedAssetImage } from '../../ResolvedAssetImage'; type PuzzleHistoryAssetPickerDialogProps = { isBusy: boolean; diff --git a/src/components/puzzle-agent/PuzzleImageModelPicker.tsx b/src/components/unified-creation/shared/PuzzleImageModelPicker.tsx similarity index 100% rename from src/components/puzzle-agent/PuzzleImageModelPicker.tsx rename to src/components/unified-creation/shared/PuzzleImageModelPicker.tsx diff --git a/src/components/puzzle-agent/puzzleImageModelOptions.ts b/src/components/unified-creation/shared/puzzleImageModelOptions.ts similarity index 100% rename from src/components/puzzle-agent/puzzleImageModelOptions.ts rename to src/components/unified-creation/shared/puzzleImageModelOptions.ts diff --git a/src/components/unified-creation/unifiedCreationSpecs.test.ts b/src/components/unified-creation/unifiedCreationSpecs.test.ts index 5d18ca54..cebb5657 100644 --- a/src/components/unified-creation/unifiedCreationSpecs.test.ts +++ b/src/components/unified-creation/unifiedCreationSpecs.test.ts @@ -6,9 +6,9 @@ import { } from './unifiedCreationSpecs'; describe('unified creation specs', () => { - test('ä¸€æœŸåªæŽ¥æ‹¼å›¾ã€æŠ“å¤§é¹…å’Œæ•²æœ¨é±¼', () => { + test('统一壳当å‰è¦†ç›–æ‹¼å›¾ã€æŠ“å¤§é¹…ã€è·³ä¸€è·³å’Œæ•²æœ¨é±¼', () => { expect(listUnifiedCreationSpecs().map((spec) => spec.playId).sort()).toEqual( - ['match3d', 'puzzle', 'wooden-fish'], + ['jump-hop', 'match3d', 'puzzle', 'wooden-fish'], ); }); @@ -22,7 +22,7 @@ describe('unified creation specs', () => { expect([...fieldKinds].sort()).toEqual(['audio', 'image', 'select', 'text']); }); - test('三æ¡é“¾è·¯éƒ½æ˜ å°„到统一创作ã€ç”Ÿæˆã€ç»“果阶段', () => { + test('å››æ¡é“¾è·¯éƒ½æ˜ å°„到统一创作ã€ç”Ÿæˆã€ç»“果阶段', () => { expect(getUnifiedCreationSpec('puzzle')).toMatchObject({ workspaceStage: 'puzzle-agent-workspace', generationStage: 'puzzle-generating', @@ -33,6 +33,11 @@ describe('unified creation specs', () => { generationStage: 'match3d-generating', resultStage: 'match3d-result', }); + expect(getUnifiedCreationSpec('jump-hop')).toMatchObject({ + workspaceStage: 'jump-hop-workspace', + generationStage: 'jump-hop-generating', + resultStage: 'jump-hop-result', + }); expect(getUnifiedCreationSpec('wooden-fish')).toMatchObject({ workspaceStage: 'wooden-fish-workspace', generationStage: 'wooden-fish-generating', diff --git a/src/components/unified-creation/unifiedCreationSpecs.ts b/src/components/unified-creation/unifiedCreationSpecs.ts index 7a849fc4..170ef6e2 100644 --- a/src/components/unified-creation/unifiedCreationSpecs.ts +++ b/src/components/unified-creation/unifiedCreationSpecs.ts @@ -58,6 +58,63 @@ const FALLBACK_UNIFIED_CREATION_SPECS: Record< }, ], }, + 'jump-hop': { + playId: 'jump-hop', + title: '想åšä¸ªä»€ä¹ˆçŽ©æ³•ï¼Ÿ', + workspaceStage: 'jump-hop-workspace', + generationStage: 'jump-hop-generating', + resultStage: 'jump-hop-result', + fields: [ + { + id: 'workTitle', + kind: 'text', + label: 'ä½œå“æ ‡é¢˜', + required: true, + }, + { + id: 'workDescription', + kind: 'text', + label: '作å“简介', + required: true, + }, + { + id: 'themeTags', + kind: 'text', + label: '主题标签', + required: true, + }, + { + id: 'difficulty', + kind: 'select', + label: '难度', + required: true, + }, + { + id: 'stylePreset', + kind: 'select', + label: '风格', + required: true, + }, + { + id: 'characterPrompt', + kind: 'text', + label: '角色æç¤ºè¯', + required: true, + }, + { + id: 'tilePrompt', + kind: 'text', + label: 'åœ°å—æç¤ºè¯', + required: true, + }, + { + id: 'endMoodPrompt', + kind: 'text', + label: '终点氛围', + required: false, + }, + ], + }, 'wooden-fish': { playId: 'wooden-fish', title: '想åšä¸ªä»€ä¹ˆçŽ©æ³•ï¼Ÿ', diff --git a/src/components/unified-creation/unifiedGenerationCopy.ts b/src/components/unified-creation/unifiedGenerationCopy.ts index 13572873..4b3bdd2d 100644 --- a/src/components/unified-creation/unifiedGenerationCopy.ts +++ b/src/components/unified-creation/unifiedGenerationCopy.ts @@ -13,6 +13,12 @@ const UNIFIED_GENERATION_COPY = { progressTitle: '抓大鹅è‰ç¨¿ç”Ÿæˆè¿›åº¦', activeBadgeLabel: 'ç´ æç”Ÿæˆä¸­', }, + 'jump-hop': { + retryLabel: '釿–°ç”Ÿæˆè‰ç¨¿', + settingTitle: '当å‰è·³ä¸€è·³ä¿¡æ¯', + progressTitle: '跳一跳è‰ç¨¿ç”Ÿæˆè¿›åº¦', + activeBadgeLabel: 'ç´ æç”Ÿæˆä¸­', + }, 'wooden-fish': { retryLabel: '釿–°ç”Ÿæˆè‰ç¨¿', settingTitle: '当剿•²æœ¨é±¼ä¿¡æ¯', diff --git a/src/components/jump-hop-creation/JumpHopWorkspace.test.tsx b/src/components/unified-creation/workspaces/JumpHopCreationWorkspace.test.tsx similarity index 71% rename from src/components/jump-hop-creation/JumpHopWorkspace.test.tsx rename to src/components/unified-creation/workspaces/JumpHopCreationWorkspace.test.tsx index 6953a94a..46ccb72d 100644 --- a/src/components/jump-hop-creation/JumpHopWorkspace.test.tsx +++ b/src/components/unified-creation/workspaces/JumpHopCreationWorkspace.test.tsx @@ -4,11 +4,11 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { beforeEach, expect, test, vi } from 'vitest'; -import type { JumpHopSessionResponse } from '../../../packages/shared/src/contracts/jumpHop'; -import { jumpHopClient } from '../../services/jump-hop/jumpHopClient'; -import { JumpHopWorkspace } from './JumpHopWorkspace'; +import type { JumpHopSessionResponse } from '../../../../packages/shared/src/contracts/jumpHop'; +import { jumpHopClient } from '../../../services/jump-hop/jumpHopClient'; +import { JumpHopCreationWorkspace } from './JumpHopCreationWorkspace'; -vi.mock('../../services/jump-hop/jumpHopClient', () => ({ +vi.mock('../../../services/jump-hop/jumpHopClient', () => ({ jumpHopClient: { createSession: vi.fn(), }, @@ -40,7 +40,7 @@ test('jump hop workspace submits structured payload after required fields are fi mockCreateSession.mockResolvedValue(sessionResponse); render( - {}} onSubmitted={onSubmitted} />, + {}} onSubmitted={onSubmitted} />, ); const submitButton = screen.getByRole('button', { name: '生æˆ' }); @@ -84,9 +84,26 @@ test('jump hop workspace calls back when return button is clicked', async () => const user = userEvent.setup(); const onBack = vi.fn(); - render( {}} />); + render( {}} />); await user.click(screen.getByRole('button', { name: '返回' })); expect(onBack).toHaveBeenCalledTimes(1); }); + +test('jump hop workspace can defer visible chrome to the unified creation page', () => { + const { container } = render( + {}} + onSubmitted={() => {}} + showBackButton={false} + unifiedChrome + />, + ); + + const workspace = container.querySelector('.jump-hop-workspace'); + expect(workspace?.getAttribute('data-unified-chrome')).toBe('true'); + expect(workspace?.className).toContain('max-w-none'); + expect(workspace?.className).not.toContain('platform-remap-surface'); + expect(screen.queryByRole('button', { name: '返回' })).toBeNull(); +}); diff --git a/src/components/jump-hop-creation/JumpHopWorkspace.tsx b/src/components/unified-creation/workspaces/JumpHopCreationWorkspace.tsx similarity index 89% rename from src/components/jump-hop-creation/JumpHopWorkspace.tsx rename to src/components/unified-creation/workspaces/JumpHopCreationWorkspace.tsx index d5b31e63..4b499896 100644 --- a/src/components/jump-hop-creation/JumpHopWorkspace.tsx +++ b/src/components/unified-creation/workspaces/JumpHopCreationWorkspace.tsx @@ -6,10 +6,10 @@ import type { JumpHopSessionResponse, JumpHopStylePreset, JumpHopWorkspaceCreateRequest, -} from '../../../packages/shared/src/contracts/jumpHop'; -import { jumpHopClient } from '../../services/jump-hop/jumpHopClient'; +} from '../../../../packages/shared/src/contracts/jumpHop'; +import { jumpHopClient } from '../../../services/jump-hop/jumpHopClient'; -type JumpHopWorkspaceProps = { +type JumpHopCreationWorkspaceProps = { isBusy?: boolean; error?: string | null; onBack: () => void; @@ -17,6 +17,8 @@ type JumpHopWorkspaceProps = { result: JumpHopSessionResponse, payload: JumpHopWorkspaceCreateRequest, ) => void; + showBackButton?: boolean; + unifiedChrome?: boolean; }; type JumpHopWorkspaceFormState = { @@ -41,12 +43,14 @@ const DEFAULT_FORM_STATE: JumpHopWorkspaceFormState = { endMoodPrompt: '', }; -export function JumpHopWorkspace({ +export function JumpHopCreationWorkspace({ isBusy = false, error = null, onBack, onSubmitted, -}: JumpHopWorkspaceProps) { + showBackButton = true, + unifiedChrome = false, +}: JumpHopCreationWorkspaceProps) { const [formState, setFormState] = useState(DEFAULT_FORM_STATE); const [localError, setLocalError] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); @@ -99,17 +103,26 @@ export function JumpHopWorkspace({ }; return ( -
-
- -
+
+ {showBackButton ? ( +
+ +
+ ) : null}