refactor: 收口拼图表单草稿判定

This commit is contained in:
2026-06-04 04:03:11 +08:00
parent bced46ad92
commit c31676a0e1
7 changed files with 59 additions and 27 deletions

View File

@@ -1406,8 +1406,8 @@
## 2026-06-04 Platform Mini Game Draft Payload Model 收口
- 背景:`PlatformEntryFlowShellImpl.tsx` 内联维护拼图 / 抓大鹅表单 payload、拼图编译 action、作品摘要回填 payload 和 pending 草稿 metadata壳层需要理解描述字段优先级、formDraft 回退、Match3D config / draft / anchorPack 优先级和数字解析。
- 决策:新增 `src/components/platform-entry/platformMiniGameDraftPayloadModel.ts`,收口 `buildPuzzleFormPayloadFromWork``buildPuzzleFormPayloadFromSession``buildPuzzleFormPayloadFromAction``buildPuzzleCompileActionFromFormPayload``buildPendingPuzzleDraftMetadata``buildMatch3DFormPayloadFromSession``buildMatch3DFormPayloadFromWork``buildPendingMatch3DDraftMetadata``parseOptionalFiniteNumber` 留在 Module 内部。
- 影响范围:拼图 action 完成 / 执行前 / 失败恢复、拼图表单直生草稿、拼图草稿架恢复、抓大鹅表单直生草稿与失败恢复。
- 决策:新增 `src/components/platform-entry/platformMiniGameDraftPayloadModel.ts`,收口 `buildPuzzleFormPayloadFromWork``buildPuzzleFormPayloadFromSession``buildPuzzleFormPayloadFromAction``buildPuzzleCompileActionFromFormPayload``buildPendingPuzzleDraftMetadata``isPuzzleFormOnlyDraft``isEmptyPuzzleFormOnlyDraft``buildMatch3DFormPayloadFromSession``buildMatch3DFormPayloadFromWork``buildPendingMatch3DDraftMetadata``parseOptionalFiniteNumber` 留在 Module 内部。
- 影响范围:拼图 action 完成 / 执行前 / 失败恢复、拼图表单直生草稿、拼图 form-only 草稿恢复 / 分流 / 结果页渲染、拼图草稿架恢复、抓大鹅表单直生草稿与失败恢复。
- 验证方式:`npm run test -- src/components/platform-entry/platformMiniGameDraftPayloadModel.test.ts`、针对新 Module 和 `PlatformEntryFlowShellImpl.tsx` 执行 ESLint、`npm run typecheck``npm run check:encoding`
- 关联文档:`docs/technical/【前端架构】PlatformMiniGameDraftPayloadModel收口计划-2026-06-04.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`

View File

@@ -59,7 +59,7 @@ AI 文字游戏模板接入以 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_
平台小游戏生成状态的恢复、失败 / 完成收尾、展示 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)。
平台小游戏草稿恢复和提交所需的拼图 / 抓大鹅表单 payload、拼图编译 actionpending metadata 收口到 `src/components/platform-entry/platformMiniGameDraftPayloadModel.ts`,壳层只保留 API、Action 执行、background task 与状态副作用,规则见 [【前端架构】PlatformMiniGameDraftPayloadModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformMiniGameDraftPayloadModel收口计划-2026-06-04.md)。
平台小游戏草稿恢复和提交所需的拼图 / 抓大鹅表单 payload、拼图编译 actionpending metadata 与拼图 form-only 草稿判定收口到 `src/components/platform-entry/platformMiniGameDraftPayloadModel.ts`,壳层只保留 API、Action 执行、background task 与状态副作用,规则见 [【前端架构】PlatformMiniGameDraftPayloadModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformMiniGameDraftPayloadModel收口计划-2026-06-04.md)。
平台拼图生成完成后刷新恢复的草稿归一化与可恢复完成态判定收口到 `src/components/platform-entry/platformPuzzleDraftRecoveryModel.ts`恢复链路只有在首图、关卡画面、UI spritesheet 与关卡背景资产包完整时才抬为 ready规则见 [【前端架构】PlatformPuzzleDraftRecoveryModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformPuzzleDraftRecoveryModel收口计划-2026-06-04.md)。

View File

@@ -2,7 +2,7 @@
## 背景
`PlatformEntryFlowShellImpl.tsx` 曾内联维护拼图和抓大鹅草稿恢复所需的表单 payload、拼图编译 action payload、作品摘要回填 payload 和 pending 草稿 metadata。壳层因此需要理解拼图描述字段优先级、formDraft 回退、Match3D config / draft / anchorPack 优先级,以及 pending 作品架标题摘要如何从 payload 派生。
`PlatformEntryFlowShellImpl.tsx` 曾内联维护拼图和抓大鹅草稿恢复所需的表单 payload、拼图编译 action payload、作品摘要回填 payload 和 pending 草稿 metadata。壳层因此需要理解拼图描述字段优先级、formDraft 回退、Match3D config / draft / anchorPack 优先级,以及 pending 作品架标题摘要如何从 payload 派生。后续还残留拼图 form-only 草稿判定,影响 action 分流、草稿恢复阶段和结果页渲染。
这些逻辑都是 DTO 变换;不读取 React state不请求网络也不写 URL。壳层只应决定何时恢复、何时提交 action、何时写入生成状态。
@@ -15,6 +15,7 @@
- `buildPuzzleFormPayloadFromAction(payload)`:从拼图 action 还原表单 payload仅接受 `compile_puzzle_draft``save_puzzle_form_draft`
- `buildPuzzleCompileActionFromFormPayload(payload)`:从表单 payload 构造拼图编译 action。
- `buildPendingPuzzleDraftMetadata(payload)`:从拼图 payload 派生 pending 作品架 metadata。
- `isPuzzleFormOnlyDraft(session)``isEmptyPuzzleFormOnlyDraft(session)`:判断拼图 session 是否仍只是表单草稿,以及表单草稿是否没有任何可提交内容。
- `buildMatch3DFormPayloadFromSession(session)``buildMatch3DFormPayloadFromWork(item)`:从抓大鹅 session / work 恢复表单 payload。
- `buildPendingMatch3DDraftMetadata(payload)`:从抓大鹅 payload 派生 pending metadata。
@@ -26,6 +27,8 @@
- 拼图 session payload 的 `pictureDescription` 优先级固定为 `formDraft.pictureDescription > first level pictureDescription > anchorPack.visualSubject.value > seedText > ''`
- 拼图编译 action 的 `promptText` 来自 `pictureDescription || seedText``workDescription` 缺省回退到图片描述;`candidateCount` 固定为 `1`
- 拼图 action 还原只接受 `compile_puzzle_draft``save_puzzle_form_draft`;其它 action 返回 `null`
- 拼图 form-only 草稿只在 `session.stage === 'collecting_anchors'` 且存在 `draft.formDraft` 时成立。
- 空 form-only 草稿必须同时缺少 `seedText``formDraft.workTitle``formDraft.workDescription``formDraft.pictureDescription`
- 抓大鹅 session payload 优先读取 `config`,其次 `draft`,最后 `anchorPack``anchorPack.clearCount``anchorPack.difficulty` 只接受有限数字字符串或数字。
- 抓大鹅 work payload 的 `themeText` 优先 `themeText`,缺失回退 `gameName`
- pending metadata 只收非空 trim 后标题和摘要;抓大鹅 metadata 用 `themeText || seedText` 同时作为 title 和 summary。

View File

@@ -16,7 +16,7 @@
平台小游戏生成状态的恢复、失败 / 完成收尾、展示 rebase、拼图后端进度合并和 ready / generating 判定统一由 `platformMiniGameDraftGenerationStateModel.ts` 处理。平台壳只决定何时调用并写入对应 React state不得在壳层重新维护 `MiniGameDraftGenerationState` 的 phase 阈值、`finishedAtMs` 清理或拼图进度 metadata 合并规则。
拼图 / 抓大鹅草稿恢复和提交所需的表单 payload、拼图编译 actionpending metadata 统一由 `platformMiniGameDraftPayloadModel.ts` 构造。平台壳不得重新手写拼图描述字段优先级、formDraft 回退、Match3D config / draft / anchorPack 优先级、数字解析或 pending 标题摘要派生规则。
拼图 / 抓大鹅草稿恢复和提交所需的表单 payload、拼图编译 actionpending metadata 与拼图 form-only 草稿判定统一由 `platformMiniGameDraftPayloadModel.ts` 构造。平台壳不得重新手写拼图描述字段优先级、formDraft 回退、form-only 空草稿判定、Match3D config / draft / anchorPack 优先级、数字解析或 pending 标题摘要派生规则。
拼图生成完成后刷新恢复的草稿归一化与可恢复完成态判定统一由 `platformPuzzleDraftRecoveryModel.ts` 处理。恢复链路只有在首图、关卡画面、UI spritesheet 与关卡背景资产包完整时才可把 draft 和首关状态抬为 `ready`;只有 cover 或候选图的半成品不得直接进入结果页完成态。

View File

@@ -531,6 +531,8 @@ import {
buildPuzzleFormPayloadFromAction,
buildPuzzleFormPayloadFromSession,
buildPuzzleFormPayloadFromWork,
isEmptyPuzzleFormOnlyDraft,
isPuzzleFormOnlyDraft,
} from './platformMiniGameDraftPayloadModel';
import {
buildJumpHopPendingSession,
@@ -1054,28 +1056,6 @@ function openPuzzleRuntimeStage(
writePuzzleRuntimeUrlState(state);
}
function isPuzzleFormOnlyDraft(session: PuzzleAgentSessionSnapshot | null) {
return Boolean(
session?.stage === 'collecting_anchors' && session.draft?.formDraft,
);
}
function isEmptyPuzzleFormOnlyDraft(
session: PuzzleAgentSessionSnapshot | null,
) {
if (!isPuzzleFormOnlyDraft(session)) {
return false;
}
const formDraft = session?.draft?.formDraft;
return !(
session?.seedText?.trim() ||
formDraft?.workTitle?.trim() ||
formDraft?.workDescription?.trim() ||
formDraft?.pictureDescription?.trim()
);
}
const CustomWorldGenerationView = lazy(async () => {
const module = await import('../CustomWorldGenerationView');
return {

View File

@@ -24,6 +24,8 @@ import {
buildPuzzleFormPayloadFromAction,
buildPuzzleFormPayloadFromSession,
buildPuzzleFormPayloadFromWork,
isEmptyPuzzleFormOnlyDraft,
isPuzzleFormOnlyDraft,
} from './platformMiniGameDraftPayloadModel';
function buildPuzzleAnchorPack(): PuzzleAnchorPack {
@@ -244,6 +246,29 @@ describe('platformMiniGameDraftPayloadModel', () => {
).toBe('关卡优先');
});
test('resolves puzzle form-only draft state for empty and filled forms', () => {
const baseDraft = buildPuzzleSession().draft!;
const emptySession = buildPuzzleSession({
seedText: ' ',
draft: {
...baseDraft,
formDraft: {
workTitle: ' ',
workDescription: ' ',
pictureDescription: ' ',
},
},
});
expect(isPuzzleFormOnlyDraft(emptySession)).toBe(true);
expect(isEmptyPuzzleFormOnlyDraft(emptySession)).toBe(true);
expect(isPuzzleFormOnlyDraft(buildPuzzleSession())).toBe(true);
expect(isEmptyPuzzleFormOnlyDraft(buildPuzzleSession())).toBe(false);
expect(
isPuzzleFormOnlyDraft(buildPuzzleSession({ stage: 'ready_to_publish' })),
).toBe(false);
});
test('builds puzzle compile action and restores form payload from action', () => {
const payload: CreatePuzzleAgentSessionRequest = {
seedText: '种子',

View File

@@ -151,6 +151,30 @@ export function buildPuzzleFormPayloadFromSession(
};
}
export function isPuzzleFormOnlyDraft(
session: PuzzleAgentSessionSnapshot | null,
) {
return Boolean(
session?.stage === 'collecting_anchors' && session.draft?.formDraft,
);
}
export function isEmptyPuzzleFormOnlyDraft(
session: PuzzleAgentSessionSnapshot | null,
) {
if (!isPuzzleFormOnlyDraft(session)) {
return false;
}
const formDraft = session?.draft?.formDraft;
return !(
session?.seedText?.trim() ||
formDraft?.workTitle?.trim() ||
formDraft?.workDescription?.trim() ||
formDraft?.pictureDescription?.trim()
);
}
export function buildPendingPuzzleDraftMetadata(
payload: CreatePuzzleAgentSessionRequest | null | undefined,
) {