From 18908609fcd0e6ee4829a3b02d1d0fb260bf2099 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=94=E9=A6=99=E4=B8=B8=E5=AD=90?= <15518898337@163.com>
Date: Sat, 6 Jun 2026 22:49:38 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BB=9F=E4=B8=80=E5=88=9B=E4=BD=9C?=
=?UTF-8?q?=E9=A1=B5=E8=A1=A8=E5=A4=B4=E8=B7=9F=E9=9A=8F=E5=90=8E=E5=8F=B0?=
=?UTF-8?q?=E5=85=A5=E5=8F=A3=E9=85=8D=E7=BD=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.hermes/shared-memory/decision-log.md | 8 ++
.../AdminCreationEntrySwitchPage.test.tsx | 5 +-
.../pages/AdminCreationEntrySwitchPage.tsx | 4 +
...玩法创作】平å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md | 2 +-
.../crates/module-runtime/src/application.rs | 21 ++--
server-rs/crates/module-runtime/src/lib.rs | 74 ++++++++++-
.../src/creation_entry_config.rs | 117 +++++++++++++++---
.../src/runtime/creation_entry_config.rs | 56 ++++++++-
.../unifiedCreationSpecs.test.ts | 8 ++
.../unified-creation/unifiedCreationSpecs.ts | 22 ++--
...ch3DCreationWorkspace.interaction.test.tsx | 4 +-
.../workspaces/Match3DCreationWorkspace.tsx | 2 +-
...zzleCreationWorkspace.interaction.test.tsx | 4 +-
.../workspaces/PuzzleCreationWorkspace.tsx | 2 +-
14 files changed, 276 insertions(+), 53 deletions(-)
diff --git a/.hermes/shared-memory/decision-log.md b/.hermes/shared-memory/decision-log.md
index 0ca4927b..fcc727ec 100644
--- a/.hermes/shared-memory/decision-log.md
+++ b/.hermes/shared-memory/decision-log.md
@@ -1305,3 +1305,11 @@
- å½±å“èŒƒå›´ï¼šæ‹¼æ¶ˆæ¶ˆå·¥ä½œå° payloadã€`shared-contracts` / `packages/shared` 契约ã€api-server 生æˆç¼–排ã€SpacetimeDB session/work snapshotã€æ–‡æ¡£ä¸Žç”Ÿæˆè¿›åº¦å±•示。
- éªŒè¯æ–¹å¼ï¼š`npm run spacetime:generate`ã€`npm run check:encoding`ã€`npm run check:server-rs-ddd`ã€`cargo test -p module-puzzle-clear`ã€`cargo test -p spacetime-client puzzle_clear -- --nocapture`ã€`npm run test -- src/components/puzzle-clear-creation/PuzzleClearWorkspace.test.tsx src/services/miniGameDraftGenerationProgress.test.ts src/routing/appPageRoutes.test.ts src/services/publicWorkCode.test.ts`。
- å…³è”æ–‡æ¡£ï¼š`docs/prd/ã€çŽ©æ³•åˆ›ä½œã€‘æ‹¼æ¶ˆæ¶ˆçŽ©æ³•æ¨¡æ¿PRD-2026-05-30.md`ã€`docs/technical/ã€çŽ©æ³•åˆ›ä½œã€‘æ‹¼æ¶ˆæ¶ˆçŽ©æ³•æ¨¡æ¿æŠ€æœ¯æ–¹æ¡ˆ-2026-05-30.md`ã€`docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md`。
+
+## 2026-06-06 统一创作页表头跟éšåŽå°çŽ©æ³•å…¥å£é…ç½®
+
+- 背景:统一创作页长期使用固定表头 `想åšä¸ªä»€ä¹ˆçŽ©æ³•ï¼Ÿ`,导致跳一跳ç‰çŽ©æ³•å¸Œæœ›æŒ‰è‡ªèº«è¯ä¹‰å±•ç¤ºæ ‡é¢˜æ—¶åªèƒ½æ”¹å‰ç«¯æˆ–默认契约。
+- 决ç–:`creationTypes[].unifiedCreationSpec.title` ç»§ç»ä½œä¸ºç»Ÿä¸€åˆ›ä½œé¡µè¡¨å¤´ä¼ è¾“å—æ®µï¼Œä½†é»˜è®¤å€¼æ”¹ä¸ºçŽ©æ³•å…¥å£ä¸æ–‡å称;读å–å’Œä¿å˜æ—¶å¦‚æžœå‘现旧公共表头或代ç é»˜è®¤ä¸æ–‡å,则归一为当å‰å…¥å£ `title`,åŽå°æ‰‹åЍé…ç½®æˆå…¶å®ƒæ ‡é¢˜æ—¶ä¿ç•™è‡ªå®šä¹‰å€¼ã€‚
+- å½±å“范围:`shared-contracts` 默认 specã€`module-runtime` å…¥å£é…ç½®å“应ã€`spacetime-module` åŽå°ä¿å˜å½’一化ã€åŽå°å…¥å£å¼€å…³é¡µæ‘˜è¦å’Œå‰ç«¯ fallback spec。
+- éªŒè¯æ–¹å¼ï¼š`GET /api/creation-entry/config` ä¸å„玩法 `unifiedCreationSpec.title` 默认ç‰äºŽå…¥å£ä¸æ–‡åï¼›åŽå°ä¿®æ”¹å…¥å£åç§°åŽï¼Œæœªè‡ªå®šä¹‰è¡¨å¤´çš„统一创作页跟éšå˜åŒ–。
+- å…³è”æ–‡æ¡£ï¼š`docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md`。
diff --git a/apps/admin-web/src/pages/AdminCreationEntrySwitchPage.test.tsx b/apps/admin-web/src/pages/AdminCreationEntrySwitchPage.test.tsx
index da9362d7..adad5145 100644
--- a/apps/admin-web/src/pages/AdminCreationEntrySwitchPage.test.tsx
+++ b/apps/admin-web/src/pages/AdminCreationEntrySwitchPage.test.tsx
@@ -27,7 +27,7 @@ vi.mock('../api/adminApiClient', () => ({
const puzzleSpec: UnifiedCreationSpecPayload = {
playId: 'puzzle',
- title: '想åšä¸ªä»€ä¹ˆçŽ©æ³•ï¼Ÿ',
+ title: '拼图',
workspaceStage: 'puzzle-agent-workspace',
generationStage: 'puzzle-generating',
resultStage: 'puzzle-result',
@@ -88,6 +88,9 @@ test('创作入å£åŽå°å±•示并ä¿å˜ç»Ÿä¸€åˆ›ä½œå¥‘约', async () => {
await screen.findByText('pictureDescription');
expect(container.querySelector('.admin-subsection .admin-info-list')).not.toBeNull();
+ expect(
+ container.querySelector('.admin-subsection .admin-info-list')?.textContent,
+ ).toContain('拼图');
expect(container.querySelector('.admin-panel .admin-panel')).toBeNull();
expect(container.querySelector('.admin-muted')).toBeNull();
diff --git a/apps/admin-web/src/pages/AdminCreationEntrySwitchPage.tsx b/apps/admin-web/src/pages/AdminCreationEntrySwitchPage.tsx
index 9de00709..9390f0f1 100644
--- a/apps/admin-web/src/pages/AdminCreationEntrySwitchPage.tsx
+++ b/apps/admin-web/src/pages/AdminCreationEntrySwitchPage.tsx
@@ -707,6 +707,10 @@ function UnifiedCreationSpecSummary({specJson}: {specJson: string}) {
玩法
{parsed.spec.playId}
+
+
表头
+ {parsed.spec.title}
+
阶段
diff --git a/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md b/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md
index 9f950b43..4ed742ed 100644
--- a/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md
+++ b/docs/ã€çŽ©æ³•åˆ›ä½œã€‘å¹³å°å…¥å£ä¸ŽçŽ©æ³•é“¾è·¯-2026-05-15.md
@@ -14,7 +14,7 @@
创作æ¢å¤å‚æ•°åªä¿ç•™ `sessionId`ã€`profileId`ã€`draftId`ã€`workId` è¿™å››ä¸ªç§æœ‰ query。它们åªå…许在åŒä¸€æ¡åˆ›ä½œé“¾è·¯çš„结果页ã€ç”Ÿæˆé¡µã€å·¥ä½œå°ä¹‹é—´ä¿ç•™ï¼›åˆ‡åˆ°é¦–页ã€å…¬å¼€ä½œå“详情ã€runtime 或å¦ä¸€æ¡çŽ©æ³•é“¾è·¯æ—¶å¿…é¡»æ¸…æŽ‰ã€‚ç”Ÿæˆé¡µç‰å¾…时间统一以生æˆçжæ€é‡Œçš„ `startedAtMs` ä¸ºå‡†ï¼›åˆ›å»ºè¯¥çŠ¶æ€æ—¶ä¼˜å…ˆä½¿ç”¨åŽç«¯ session 下å‘çš„æ—¶é—´æˆ³ï¼Œä½œå“æ‘˜è¦é‡Œçš„ `updatedAt` ä»åªç”¨äºŽæŽ’åºä¸Žæ‘˜è¦å±•示,ä¸ä½œä¸ºå‰ç«¯è‡ªè¡ŒæŽ¨å¯¼ä¸šåŠ¡çŠ¶æ€çš„真相。
-统一创作入å£è¦†ç›–当å‰å¯è¿›å…¥åˆ›ä½œé“¾è·¯çš„已有模æ¿ï¼š`rpg`ã€`big-fish`ã€`puzzle`ã€`match3d`ã€`jump-hop`ã€`wooden-fish`ã€`square-hole`ã€`bark-battle`ã€`visual-novel`ã€`baby-object-match` å’Œ `creative-agent`ï¼›`airp` 仿˜¯æœªå¼€æ”¾å ä½ï¼Œä¸ä½œä¸ºå½“å‰ç»Ÿä¸€åˆ›ä½œé“¾è·¯ç›®æ ‡ã€‚æ‹¼å›¾ã€æŠ“å¤§é¹…ã€è·³ä¸€è·³å’Œæ•²æœ¨é±¼åœ¨å‰ç«¯ç»§ç»ç»è¿‡ `UnifiedCreationWorkspace` å’Œ `UnifiedGenerationPage`:`UnifiedCreationWorkspace` 作为平å°å£³ä¾èµ–的统一创作编排层,å†å†…部调用 `src/components/unified-creation/workspaces/` 下的 `PuzzleCreationWorkspace`ã€`Match3DCreationWorkspace`ã€`JumpHopCreationWorkspace` å’Œ `WoodenFishCreationWorkspace`。其它已有模æ¿ç”±å¹³å°å£³ç”¨ `UnifiedCreationPage` åŒ…ä½æ—¢æœ‰å·¥ä½œå°ï¼Œå¤ç”¨ç»Ÿä¸€æ ‡é¢˜æ ã€è¿”回入å£ã€é¡µé¢çº§çºµå‘滚动和éšè—å—æ®µå¥‘çº¦ï¼ŒåŒæ—¶ä¿ç•™å„玩法自己的表å•ã€è‰ç¨¿æ¢å¤å’ŒåŽç»ç¼–æŽ’ã€‚åˆ›ä½œé¡µå—æ®µæ¸…å•ç”±åŽç«¯åœ¨ `GET /api/creation-entry/config` çš„ `creationTypes[].unifiedCreationSpec` 下å‘,å‰ç«¯ä»…在该扩展ä½ç¼ºå¤±æ—¶å›žé€€åˆ°æœ¬åœ°é»˜è®¤ specï¼›å—æ®µç±»åž‹åªä¿ç•™ `text`ã€`select`ã€`image`ã€`audio`。`UnifiedCreationPage` ä¸åœ¨ UI ä¸é¢å¤–å±•ç¤ºå—æ®µè¯´æ˜Ž chip,也ä¸åœ¨å³ä¸Šè§’显示内部 `playId`ã€æ¨¡æ¿ ID 或工作å°é˜¶æ®µå;竖å±ç§»åŠ¨ç«¯å¿…é¡»èƒ½ä»Žæ ‡é¢˜ã€è¡¨å•一路滑到æäº¤æŒ‰é’®ã€‚å„玩法工作å°è´Ÿè´£æ¸²æŸ“真实输入控件ã€ä¸Šä¼ ã€åކå²ç´ æã€æ ¡éªŒå’Œæäº¤ï¼Œä½†è¿”回按钮åªä¿ç•™åœ¨ç»Ÿä¸€é¡µå¤´ï¼Œå·¥ä½œå°å†…部ä¸å†é‡å¤æ¸²æŸ“。暗色创作进度å¡ç‰‡ä½äºŽ `platform-remap-surface` 内时,必须用组件专属 class 覆盖浅色主题 remap,确ä¿ç™½å—ã€æµ…色边框和进度æ¡åº•色ä¸ä¼šè¢«å…¨å±€è§„åˆ™æ”¹æˆæ·±è‰²ï¼›ä¸è¦åªä¾èµ–通用 `text-white*` ç±»ã€‚æ•²æœ¨é±¼çš„éŸ³æ•ˆå’ŒåŠŸå¾·è¯æ¡é¢æ¿ä¸å¾—放进独立内部滚动容器,移动端应跟éšé¡µé¢è‡ªç„¶æ»šåŠ¨å±•å¼€ã€‚ç”Ÿæˆé¡µç»Ÿä¸€å±•示阶段ã€å½“剿¥éª¤ã€æ€»è¿›åº¦ã€é”™è¯¯å’Œé‡è¯•动作。
+统一创作入å£è¦†ç›–当å‰å¯è¿›å…¥åˆ›ä½œé“¾è·¯çš„已有模æ¿ï¼š`rpg`ã€`big-fish`ã€`puzzle`ã€`match3d`ã€`jump-hop`ã€`wooden-fish`ã€`square-hole`ã€`bark-battle`ã€`visual-novel`ã€`baby-object-match` å’Œ `creative-agent`ï¼›`airp` 仿˜¯æœªå¼€æ”¾å ä½ï¼Œä¸ä½œä¸ºå½“å‰ç»Ÿä¸€åˆ›ä½œé“¾è·¯ç›®æ ‡ã€‚æ‹¼å›¾ã€æŠ“å¤§é¹…ã€è·³ä¸€è·³å’Œæ•²æœ¨é±¼åœ¨å‰ç«¯ç»§ç»ç»è¿‡ `UnifiedCreationWorkspace` å’Œ `UnifiedGenerationPage`:`UnifiedCreationWorkspace` 作为平å°å£³ä¾èµ–的统一创作编排层,å†å†…部调用 `src/components/unified-creation/workspaces/` 下的 `PuzzleCreationWorkspace`ã€`Match3DCreationWorkspace`ã€`JumpHopCreationWorkspace` å’Œ `WoodenFishCreationWorkspace`。其它已有模æ¿ç”±å¹³å°å£³ç”¨ `UnifiedCreationPage` åŒ…ä½æ—¢æœ‰å·¥ä½œå°ï¼Œå¤ç”¨ç»Ÿä¸€æ ‡é¢˜æ ã€è¿”回入å£ã€é¡µé¢çº§çºµå‘滚动和éšè—å—æ®µå¥‘çº¦ï¼ŒåŒæ—¶ä¿ç•™å„玩法自己的表å•ã€è‰ç¨¿æ¢å¤å’ŒåŽç»ç¼–æŽ’ã€‚åˆ›ä½œé¡µå—æ®µæ¸…å•和表头由åŽç«¯åœ¨ `GET /api/creation-entry/config` çš„ `creationTypes[].unifiedCreationSpec` 下å‘,å‰ç«¯ä»…在该扩展ä½ç¼ºå¤±æ—¶å›žé€€åˆ°æœ¬åœ°é»˜è®¤ specï¼›å—æ®µç±»åž‹åªä¿ç•™ `text`ã€`select`ã€`image`ã€`audio`。统一创作页表头属于åŽå°çŽ©æ³•å…¥å£é…置,默认使用åŒä¸€å…¥å£çš„䏿–‡åç§°ï¼›æ—§åº“é‡Œä»æŒä¹…化为 `想åšä¸ªä»€ä¹ˆçŽ©æ³•ï¼Ÿ` 或代ç é»˜è®¤ä¸æ–‡åçš„ spec title,会在读å–å’Œä¿å˜æ—¶å½’一到当å‰å…¥å£å称,åŽå°å•独写æˆå…¶å®ƒæ ‡é¢˜æ—¶ä¿ç•™è¯¥è‡ªå®šä¹‰å€¼ã€‚`UnifiedCreationPage` ä¸åœ¨ UI ä¸é¢å¤–å±•ç¤ºå—æ®µè¯´æ˜Ž chip,也ä¸åœ¨å³ä¸Šè§’显示内部 `playId`ã€æ¨¡æ¿ ID 或工作å°é˜¶æ®µå;竖å±ç§»åŠ¨ç«¯å¿…é¡»èƒ½ä»Žæ ‡é¢˜ã€è¡¨å•一路滑到æäº¤æŒ‰é’®ã€‚å„玩法工作å°è´Ÿè´£æ¸²æŸ“真实输入控件ã€ä¸Šä¼ ã€åކå²ç´ æã€æ ¡éªŒå’Œæäº¤ï¼Œä½†è¿”回按钮åªä¿ç•™åœ¨ç»Ÿä¸€é¡µå¤´ï¼Œå·¥ä½œå°å†…部ä¸å†é‡å¤æ¸²æŸ“。暗色创作进度å¡ç‰‡ä½äºŽ `platform-remap-surface` 内时,必须用组件专属 class 覆盖浅色主题 remap,确ä¿ç™½å—ã€æµ…色边框和进度æ¡åº•色ä¸ä¼šè¢«å…¨å±€è§„åˆ™æ”¹æˆæ·±è‰²ï¼›ä¸è¦åªä¾èµ–通用 `text-white*` ç±»ã€‚æ•²æœ¨é±¼çš„éŸ³æ•ˆå’ŒåŠŸå¾·è¯æ¡é¢æ¿ä¸å¾—放进独立内部滚动容器,移动端应跟éšé¡µé¢è‡ªç„¶æ»šåŠ¨å±•å¼€ã€‚ç”Ÿæˆé¡µç»Ÿä¸€å±•示阶段ã€å½“剿¥éª¤ã€æ€»è¿›åº¦ã€é”™è¯¯å’Œé‡è¯•动作。
åˆ›ä½œè¡¨å•æäº¤å‰çš„æ³¥ç‚¹ä½™é¢å‰ç½®æ ¡éªŒåªå…许用独立弹窗æç¤ºå¤±è´¥åŽŸå› ï¼Œä¸å¾—æŠŠç”¨æˆ·é€€å›žåˆ›ä½œå…¥å£æˆ–玩法模æ¿åˆ—表,也ä¸å¾—清空当å‰è¡¨å•状æ€ã€‚当å‰é€‚ç”¨æ‹¼å›¾ã€æŠ“å¤§é¹…å’Œæ±ªæ±ªå£°æµªç‰ä¼šåœ¨å‰ç«¯æäº¤å‰æ ¡éªŒæ³¥ç‚¹çš„生æˆå…¥å£ï¼›ä½™é¢ä¸è¶³ã€ä½™é¢è¯»å–失败都应åœç•™åœ¨å½“å‰å·¥ä½œå°ï¼Œç”±ç”¨æˆ·å…³é—æç¤ºåŽç»§ç»ç¼–辑或自行补足泥点。
diff --git a/server-rs/crates/module-runtime/src/application.rs b/server-rs/crates/module-runtime/src/application.rs
index 2c578dd9..cae5c119 100644
--- a/server-rs/crates/module-runtime/src/application.rs
+++ b/server-rs/crates/module-runtime/src/application.rs
@@ -12,7 +12,7 @@ use crate::format_utc_micros;
use shared_contracts::creation_entry_config::{
CreationEntryConfigResponse, CreationEntryEventBannerResponse, CreationEntryStartCardResponse,
CreationEntryTypeModalResponse, CreationEntryTypeResponse,
- encode_unified_creation_spec_response, resolve_unified_creation_spec_response,
+ encode_unified_creation_spec_response, resolve_unified_creation_spec_response_with_entry_title,
};
/// 将创作入å£é¢†åŸŸå¿«ç…§è½¬æ¢ä¸ºå‰åŽå°å…±äº«çš„ HTTP 契约å“应。
@@ -45,8 +45,9 @@ pub fn build_creation_entry_config_response(
.creation_types
.into_iter()
.map(|item| {
- let unified_creation_spec = resolve_unified_creation_spec_response(
+ let unified_creation_spec = resolve_unified_creation_spec_response_with_entry_title(
item.id.as_str(),
+ item.title.as_str(),
item.unified_creation_spec_json.as_deref(),
);
CreationEntryTypeResponse {
@@ -161,10 +162,9 @@ fn normalize_creation_entry_announcement_banner_value(
);
}
- let banner = serde_json::from_value::(Value::Object(
- object.clone(),
- ))
- .map_err(|error| format!("第 {} æ¡å…¬å‘Šå¯¹è±¡éžæ³•:{error}", index + 1))?;
+ let banner =
+ serde_json::from_value::(Value::Object(object.clone()))
+ .map_err(|error| format!("第 {} æ¡å…¬å‘Šå¯¹è±¡éžæ³•:{error}", index + 1))?;
normalize_creation_entry_event_banner_response(index, banner)
}
@@ -243,8 +243,8 @@ pub fn resolve_creation_entry_event_banner_responses(
banners
}
.into_iter()
- .map(build_creation_entry_event_banner_response)
- .collect()
+ .map(build_creation_entry_event_banner_response)
+ .collect()
}
/// 把领域公告快照转æ¢ä¸º HTTP å“åº”å—æ®µã€‚
@@ -332,10 +332,7 @@ fn normalize_banner_html_code(
}
let lower_html_code = html_code.to_ascii_lowercase();
if lower_html_code.contains("