refactor: 收口方洞 session profile 映射
This commit is contained in:
@@ -1405,9 +1405,9 @@
|
||||
|
||||
## 2026-06-04 Platform Mini Game Session Mapping Model 收口
|
||||
|
||||
- 背景:`PlatformEntryFlowShellImpl.tsx` 顶部仍保留拼图 runtime 恢复、跳一跳 pending session、敲木鱼 detail 恢复和敲木鱼 pending session 四段纯 DTO 映射,壳层需要理解 sessionId 优先级、拼图稳定 ID 和 pending draft 默认值。
|
||||
- 决策:新增 `src/components/platform-entry/platformMiniGameSessionMappingModel.ts`,收口 `buildPuzzleRuntimeWorkFromSession`、`buildJumpHopPendingSession`、`buildWoodenFishSessionFromWorkDetail` 与 `buildWoodenFishPendingSession`。Module 复用 `normalizeCreationUrlValue` 与 `platformPuzzleIdentityModel`;壳层只保留网络读取、React state、URL 写入和 stage 切换副作用。
|
||||
- 影响范围:拼图 runtime URL 恢复、跳一跳生成中作品架打开、敲木鱼生成中作品架打开和敲木鱼草稿 detail 恢复。
|
||||
- 背景:`PlatformEntryFlowShellImpl.tsx` 顶部仍保留拼图 runtime 恢复、方洞 session draft 转 profile、跳一跳 pending session、敲木鱼 detail 恢复和敲木鱼 pending session 等纯 DTO 映射,壳层需要理解 sessionId 优先级、拼图稳定 ID、方洞草稿 profile 默认值和 pending draft 默认值。
|
||||
- 决策:新增 `src/components/platform-entry/platformMiniGameSessionMappingModel.ts`,收口 `buildPuzzleRuntimeWorkFromSession`、`buildSquareHoleProfileFromSession`、`buildJumpHopPendingSession`、`buildWoodenFishSessionFromWorkDetail` 与 `buildWoodenFishPendingSession`。Module 复用 `normalizeCreationUrlValue` 与 `platformPuzzleIdentityModel`;壳层只保留网络读取、React state、URL 写入和 stage 切换副作用。
|
||||
- 影响范围:拼图 runtime URL 恢复、方洞挑战草稿 profile 构造、跳一跳生成中作品架打开、敲木鱼生成中作品架打开和敲木鱼草稿 detail 恢复。
|
||||
- 验证方式:`npm run test -- src/components/platform-entry/platformMiniGameSessionMappingModel.test.ts`、针对新 Module 和 `PlatformEntryFlowShellImpl.tsx` 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
|
||||
- 关联文档:`docs/technical/【前端架构】PlatformMiniGameSessionMappingModel收口计划-2026-06-04.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ AI 文字游戏模板接入以 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_
|
||||
|
||||
平台入口生成页进度 tick 的 stage 到生成状态映射、终态判定和视觉小说轻量生成特例收口到 `src/components/platform-entry/platformGenerationProgressTickModel.ts`,壳层只保留 `Date.now()`、`setInterval` 和 cleanup 副作用,规则见 [【前端架构】PlatformGenerationProgressTickModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformGenerationProgressTickModel收口计划-2026-06-04.md)。
|
||||
|
||||
平台壳的拼图 runtime 恢复 work、跳一跳 pending session、敲木鱼 detail 恢复 session 和敲木鱼 pending session DTO 映射收口到 `src/components/platform-entry/platformMiniGameSessionMappingModel.ts`,壳层只保留网络、状态、URL 与 stage 副作用,规则见 [【前端架构】PlatformMiniGameSessionMappingModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformMiniGameSessionMappingModel收口计划-2026-06-04.md)。
|
||||
平台壳的拼图 runtime 恢复 work、方洞 session draft 转 profile、跳一跳 pending session、敲木鱼 detail 恢复 session 和敲木鱼 pending session DTO 映射收口到 `src/components/platform-entry/platformMiniGameSessionMappingModel.ts`,壳层只保留网络、状态、URL 与 stage 副作用,规则见 [【前端架构】PlatformMiniGameSessionMappingModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformMiniGameSessionMappingModel收口计划-2026-06-04.md)。
|
||||
|
||||
平台小游戏生成状态的恢复、失败 / 完成收尾、展示 rebase、拼图后端进度合并、抓大鹅生成资产旁路进度合并和 ready / generating 判定收口到 `src/components/platform-entry/platformMiniGameDraftGenerationStateModel.ts`,壳层只保留 API、后台任务、React state、URL 与 stage 副作用,规则见 [【前端架构】PlatformMiniGameDraftGenerationStateModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformMiniGameDraftGenerationStateModel收口计划-2026-06-04.md)。
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 顶部曾保留拼图 runtime 恢复、跳一跳 pending session、敲木鱼 work detail 恢复和敲木鱼 pending session 四段纯 DTO 映射。它们没有 React state、网络请求、路由、弹窗或计时副作用,却住在大型平台壳内;新增或修正生成中草稿恢复时,需要在壳层里理解 sessionId 优先级、拼图稳定 ID、pending draft 默认值和木鱼 fallback 规则。
|
||||
`PlatformEntryFlowShellImpl.tsx` 顶部曾保留拼图 runtime 恢复、方洞挑战 session draft 转 profile、跳一跳 pending session、敲木鱼 work detail 恢复和敲木鱼 pending session 多段纯 DTO 映射。它们没有 React state、网络请求、路由、弹窗或计时副作用,却住在大型平台壳内;新增或修正生成中草稿恢复时,需要在壳层里理解 sessionId 优先级、拼图稳定 ID、方洞 profile 默认值、pending draft 默认值和木鱼 fallback 规则。
|
||||
|
||||
这些规则属于平台壳 session / work 恢复映射,应成为可测试的 **Module**。壳层只负责调用网络、写 React state、写 URL 和切换 stage。
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
新增 `src/components/platform-entry/platformMiniGameSessionMappingModel.ts` 作为 Platform Mini Game Session Mapping **Module**。其公开 **Interface** 为:
|
||||
|
||||
- `buildPuzzleRuntimeWorkFromSession(session, owner)`:从拼图 Agent session 构造可进入 runtime 的 draft `PuzzleWorkSummary`,缺草稿、缺 profile 或缺封面时返回 `null`。
|
||||
- `buildSquareHoleProfileFromSession(session)`:从方洞挑战 Agent session draft 构造草稿 `SquareHoleWorkProfile`,缺 session、缺 draft 或缺 profileId 时返回 `null`。
|
||||
- `buildJumpHopPendingSession(item)`:从跳一跳作品架 summary 构造生成中 pending session。
|
||||
- `buildWoodenFishSessionFromWorkDetail(work, fallbackItem?)`:从敲木鱼 work detail 恢复 session,并按 summary / fallback / profileId 决定 sessionId。
|
||||
- `buildWoodenFishPendingSession(item)`:从敲木鱼作品架 summary 构造生成中 pending session。
|
||||
@@ -22,15 +23,17 @@
|
||||
- 拼图 runtime work 必须保留 `draft.coverImageSrc` 非空门槛,避免启动缺封面的草稿运行态。
|
||||
- 拼图 profileId 优先 `publishedProfileId`,否则用 `buildPuzzleResultProfileId(sessionId)`;workId 使用 `buildPuzzleResultWorkId(sessionId)`,缺失时回退 profileId。
|
||||
- 拼图 owner 缺省为 `current-user` / `玩家`;`publishReady` 来自 `session.resultPreview?.publishReady`。
|
||||
- 方洞 profile 的 `workId` 与 `profileId` 都来自 draft `profileId`;owner 固定为 `current-user`,`sourceSessionId` 来自 sessionId。
|
||||
- 方洞 profile 的 `updatedAt` 优先 session `updatedAt`,缺失时使用当前时间;`publicationStatus='draft'`、`playCount=0`、`publishedAt=null`,`publishReady` 来自 draft。
|
||||
- 跳一跳 pending sessionId 优先 `sourceSessionId`,缺失时用 `profileId`;素材、路径和 prompt 维持空值兜底。
|
||||
- 敲木鱼 detail sessionId 优先级固定为 `work.summary.sourceSessionId > fallbackItem.sourceSessionId > profileId`。
|
||||
- 敲木鱼 pending session 保持 `floatingWords=['功德 +1']`、素材 / 音效 / 背景为空的旧默认。
|
||||
|
||||
## Depth / Leverage / Locality
|
||||
|
||||
- **Depth**:壳层以四个函数取得恢复用 DTO;ID 优先级和默认 draft 字段藏入 Module Implementation。
|
||||
- **Depth**:壳层以少量函数取得恢复用 DTO;ID 优先级、方洞 profile 默认值和 pending draft 字段藏入 Module Implementation。
|
||||
- **Leverage**:后续新增生成中作品恢复或修改 sessionId 规则时,先改 Module 与单测,再保持壳层 Adapter 副作用不变。
|
||||
- **Locality**:拼图、跳一跳和敲木鱼的恢复映射集中在一个纯测试面,避免在大型壳层顶部继续堆积 DTO 构造。
|
||||
- **Locality**:拼图、方洞、跳一跳和敲木鱼的恢复映射集中在一个纯测试面,避免在大型壳层顶部继续堆积 DTO 构造。
|
||||
|
||||
## 验收
|
||||
|
||||
|
||||
@@ -536,6 +536,7 @@ import {
|
||||
import {
|
||||
buildJumpHopPendingSession,
|
||||
buildPuzzleRuntimeWorkFromSession,
|
||||
buildSquareHoleProfileFromSession,
|
||||
buildWoodenFishPendingSession,
|
||||
buildWoodenFishSessionFromWorkDetail,
|
||||
} from './platformMiniGameSessionMappingModel';
|
||||
@@ -751,40 +752,6 @@ function mapVisualNovelWorkDetailToSession(
|
||||
};
|
||||
}
|
||||
|
||||
function buildSquareHoleProfileFromSession(
|
||||
session: SquareHoleSessionSnapshot | null,
|
||||
): SquareHoleWorkProfile | null {
|
||||
const draft = session?.draft;
|
||||
if (!session || !draft?.profileId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const now = session.updatedAt || new Date().toISOString();
|
||||
return {
|
||||
workId: draft.profileId,
|
||||
profileId: draft.profileId,
|
||||
ownerUserId: 'current-user',
|
||||
sourceSessionId: session.sessionId,
|
||||
gameName: draft.gameName,
|
||||
themeText: draft.themeText,
|
||||
twistRule: draft.twistRule,
|
||||
summary: draft.summary,
|
||||
tags: draft.tags,
|
||||
coverImageSrc: draft.coverImageSrc ?? null,
|
||||
backgroundPrompt: draft.backgroundPrompt,
|
||||
backgroundImageSrc: draft.backgroundImageSrc ?? null,
|
||||
shapeOptions: draft.shapeOptions,
|
||||
holeOptions: draft.holeOptions,
|
||||
shapeCount: draft.shapeCount,
|
||||
difficulty: draft.difficulty,
|
||||
publicationStatus: 'draft',
|
||||
playCount: 0,
|
||||
updatedAt: now,
|
||||
publishedAt: null,
|
||||
publishReady: Boolean(draft.publishReady),
|
||||
};
|
||||
}
|
||||
|
||||
function mergePuzzleWorkSummary(
|
||||
current: PuzzleWorkSummary,
|
||||
updated: PuzzleWorkSummary,
|
||||
|
||||
@@ -8,6 +8,10 @@ import type {
|
||||
PuzzleResultDraft,
|
||||
} from '../../../packages/shared/src/contracts/puzzleAgentDraft';
|
||||
import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/contracts/puzzleAgentSession';
|
||||
import type {
|
||||
SquareHoleResultDraft,
|
||||
SquareHoleSessionSnapshot,
|
||||
} from '../../../packages/shared/src/contracts/squareHoleAgent';
|
||||
import type {
|
||||
WoodenFishAudioAsset,
|
||||
WoodenFishImageAsset,
|
||||
@@ -17,6 +21,7 @@ import type {
|
||||
import {
|
||||
buildJumpHopPendingSession,
|
||||
buildPuzzleRuntimeWorkFromSession,
|
||||
buildSquareHoleProfileFromSession,
|
||||
buildWoodenFishPendingSession,
|
||||
buildWoodenFishSessionFromWorkDetail,
|
||||
} from './platformMiniGameSessionMappingModel';
|
||||
@@ -123,6 +128,100 @@ function buildJumpHopSummary(
|
||||
};
|
||||
}
|
||||
|
||||
function buildSquareHoleDraft(
|
||||
overrides: Partial<SquareHoleResultDraft> = {},
|
||||
): SquareHoleResultDraft {
|
||||
return {
|
||||
profileId: 'square-hole-profile-1',
|
||||
gameName: '星桥方洞',
|
||||
themeText: '星桥机关',
|
||||
twistRule: '只允许相同颜色形状入洞',
|
||||
summary: '把星桥机关里的形状送入正确孔洞。',
|
||||
tags: ['星桥', '机关'],
|
||||
coverImageSrc: '/square-hole-cover.png',
|
||||
backgroundPrompt: '星桥机关背景',
|
||||
backgroundImageSrc: '/square-hole-background.png',
|
||||
shapeOptions: [
|
||||
{
|
||||
optionId: 'shape-1',
|
||||
shapeKind: 'star',
|
||||
label: '星形',
|
||||
targetHoleId: 'hole-1',
|
||||
imagePrompt: '星形积木',
|
||||
imageSrc: '/shape-star.png',
|
||||
},
|
||||
],
|
||||
holeOptions: [
|
||||
{
|
||||
holeId: 'hole-1',
|
||||
holeKind: 'star-hole',
|
||||
label: '星形洞',
|
||||
imagePrompt: '星形洞口',
|
||||
imageSrc: '/hole-star.png',
|
||||
},
|
||||
],
|
||||
shapeCount: 6,
|
||||
difficulty: 3,
|
||||
publishReady: true,
|
||||
blockers: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function buildSquareHoleSession(
|
||||
overrides: Partial<SquareHoleSessionSnapshot> = {},
|
||||
): SquareHoleSessionSnapshot {
|
||||
return {
|
||||
sessionId: 'square-hole-session-1',
|
||||
currentTurn: 2,
|
||||
progressPercent: 100,
|
||||
stage: 'draft_ready',
|
||||
anchorPack: {
|
||||
theme: {
|
||||
key: 'theme',
|
||||
label: '主题',
|
||||
value: '星桥机关',
|
||||
status: 'confirmed',
|
||||
},
|
||||
twistRule: {
|
||||
key: 'twistRule',
|
||||
label: '扭转规则',
|
||||
value: '只允许相同颜色形状入洞',
|
||||
status: 'confirmed',
|
||||
},
|
||||
shapeCount: {
|
||||
key: 'shapeCount',
|
||||
label: '形状数量',
|
||||
value: '6',
|
||||
status: 'confirmed',
|
||||
},
|
||||
difficulty: {
|
||||
key: 'difficulty',
|
||||
label: '难度',
|
||||
value: '3',
|
||||
status: 'confirmed',
|
||||
},
|
||||
},
|
||||
config: {
|
||||
themeText: '星桥机关',
|
||||
twistRule: '只允许相同颜色形状入洞',
|
||||
shapeCount: 6,
|
||||
difficulty: 3,
|
||||
shapeOptions: [],
|
||||
holeOptions: [],
|
||||
backgroundPrompt: '星桥机关背景',
|
||||
coverImageSrc: null,
|
||||
backgroundImageSrc: null,
|
||||
},
|
||||
draft: buildSquareHoleDraft(),
|
||||
messages: [],
|
||||
lastAssistantReply: null,
|
||||
publishedProfileId: null,
|
||||
updatedAt: '2026-06-01T12:30:00.000Z',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
const woodenFishImageAsset: WoodenFishImageAsset = {
|
||||
assetId: 'asset-hit',
|
||||
imageSrc: '/hit.png',
|
||||
@@ -284,6 +383,50 @@ describe('platformMiniGameSessionMappingModel', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('builds square hole draft profile from session', () => {
|
||||
expect(buildSquareHoleProfileFromSession(buildSquareHoleSession())).toEqual({
|
||||
workId: 'square-hole-profile-1',
|
||||
profileId: 'square-hole-profile-1',
|
||||
ownerUserId: 'current-user',
|
||||
sourceSessionId: 'square-hole-session-1',
|
||||
gameName: '星桥方洞',
|
||||
themeText: '星桥机关',
|
||||
twistRule: '只允许相同颜色形状入洞',
|
||||
summary: '把星桥机关里的形状送入正确孔洞。',
|
||||
tags: ['星桥', '机关'],
|
||||
coverImageSrc: '/square-hole-cover.png',
|
||||
backgroundPrompt: '星桥机关背景',
|
||||
backgroundImageSrc: '/square-hole-background.png',
|
||||
shapeOptions: buildSquareHoleDraft().shapeOptions,
|
||||
holeOptions: buildSquareHoleDraft().holeOptions,
|
||||
shapeCount: 6,
|
||||
difficulty: 3,
|
||||
publicationStatus: 'draft',
|
||||
playCount: 0,
|
||||
updatedAt: '2026-06-01T12:30:00.000Z',
|
||||
publishedAt: null,
|
||||
publishReady: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('returns null for square hole profile without session draft or profile id', () => {
|
||||
expect(buildSquareHoleProfileFromSession(null)).toBeNull();
|
||||
expect(
|
||||
buildSquareHoleProfileFromSession(
|
||||
buildSquareHoleSession({
|
||||
draft: null,
|
||||
}),
|
||||
),
|
||||
).toBeNull();
|
||||
expect(
|
||||
buildSquareHoleProfileFromSession(
|
||||
buildSquareHoleSession({
|
||||
draft: buildSquareHoleDraft({ profileId: '' }),
|
||||
}),
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
test('builds wooden fish pending session from work summary', () => {
|
||||
expect(buildWoodenFishPendingSession(buildWoodenFishSummary())).toEqual({
|
||||
sessionId: 'wooden-fish-session-1',
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { JumpHopSessionSnapshotResponse, JumpHopWorkSummaryResponse } from '../../../packages/shared/src/contracts/jumpHop';
|
||||
import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/contracts/puzzleAgentSession';
|
||||
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
||||
import type { SquareHoleSessionSnapshot } from '../../../packages/shared/src/contracts/squareHoleAgent';
|
||||
import type { SquareHoleWorkProfile } from '../../../packages/shared/src/contracts/squareHoleWorks';
|
||||
import type {
|
||||
WoodenFishSessionSnapshotResponse,
|
||||
WoodenFishWorkProfileResponse,
|
||||
@@ -52,6 +54,40 @@ export function buildPuzzleRuntimeWorkFromSession(
|
||||
};
|
||||
}
|
||||
|
||||
export function buildSquareHoleProfileFromSession(
|
||||
session: SquareHoleSessionSnapshot | null,
|
||||
): SquareHoleWorkProfile | null {
|
||||
const draft = session?.draft;
|
||||
if (!session || !draft?.profileId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const now = session.updatedAt || new Date().toISOString();
|
||||
return {
|
||||
workId: draft.profileId,
|
||||
profileId: draft.profileId,
|
||||
ownerUserId: 'current-user',
|
||||
sourceSessionId: session.sessionId,
|
||||
gameName: draft.gameName,
|
||||
themeText: draft.themeText,
|
||||
twistRule: draft.twistRule,
|
||||
summary: draft.summary,
|
||||
tags: draft.tags,
|
||||
coverImageSrc: draft.coverImageSrc ?? null,
|
||||
backgroundPrompt: draft.backgroundPrompt,
|
||||
backgroundImageSrc: draft.backgroundImageSrc ?? null,
|
||||
shapeOptions: draft.shapeOptions,
|
||||
holeOptions: draft.holeOptions,
|
||||
shapeCount: draft.shapeCount,
|
||||
difficulty: draft.difficulty,
|
||||
publicationStatus: 'draft',
|
||||
playCount: 0,
|
||||
updatedAt: now,
|
||||
publishedAt: null,
|
||||
publishReady: Boolean(draft.publishReady),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildJumpHopPendingSession(
|
||||
item: JumpHopWorkSummaryResponse,
|
||||
): JumpHopSessionSnapshotResponse {
|
||||
|
||||
Reference in New Issue
Block a user