refactor: 收口作品架更新回填规则
This commit is contained in:
@@ -1354,9 +1354,9 @@
|
|||||||
|
|
||||||
## 2026-06-03 Draft Generation Shelf Model 收口
|
## 2026-06-03 Draft Generation Shelf Model 收口
|
||||||
|
|
||||||
- 背景:平台壳内散落创作生成 notice key、pending 作品架占位、失败文案覆盖、拼图稳定 ID、持久化 generating/failed 判断与草稿 Tab 未读点,新增或调整玩法时需要在多处理解 `workId` / `profileId` / `sourceSessionId` / `draftId` 形状。
|
- 背景:平台壳内散落创作生成 notice key、pending 作品架占位、作品详情更新回填、失败文案覆盖、拼图稳定 ID、持久化 generating/failed 判断与草稿 Tab 未读点,新增或调整玩法时需要在多处理解 `workId` / `profileId` / `sourceSessionId` / `draftId` 形状。
|
||||||
- 决策:新增 `src/components/platform-entry/platformDraftGenerationShelfModel.ts` 作为 Draft Generation Shelf Module,Interface 收口 `collectDraftNoticeKeys`、`getGenerationNoticeShelfKeys`、`createPendingDraftShelfState`、各玩法 `buildPending*Works`、`buildCreationWorkShelfRuntimeState`、`collectVisibleDraftNoticeKeys`、`hasUnreadDraftGenerationUpdates`、拼图稳定 ID 与持久化状态判断;`PlatformEntryFlowShellImpl.tsx` 仅作为 React state、网络刷新、路由和弹窗副作用 Adapter。
|
- 决策:新增 `src/components/platform-entry/platformDraftGenerationShelfModel.ts` 作为 Draft Generation Shelf Module,Interface 收口 `collectDraftNoticeKeys`、`getGenerationNoticeShelfKeys`、`createPendingDraftShelfState`、各玩法 `buildPending*Works`、`buildCreationWorkShelfRuntimeState`、`collectVisibleDraftNoticeKeys`、`hasUnreadDraftGenerationUpdates`、`mergePuzzleWorkSummary`、`mergeBigFishWorkSummary`、拼图稳定 ID 与持久化状态判断;`PlatformEntryFlowShellImpl.tsx` 仅作为 React state、网络刷新、路由和弹窗副作用 Adapter。
|
||||||
- 影响范围:创作中心草稿 Tab 未读点、作品架生成中遮罩、失败草稿摘要、pending 草稿占位、拼图 / 抓大鹅生成恢复和各玩法生成完成通知。
|
- 影响范围:创作中心草稿 Tab 未读点、作品架生成中遮罩、作品详情更新回填、失败草稿摘要、pending 草稿占位、拼图 / 抓大鹅生成恢复和各玩法生成完成通知。
|
||||||
- 验证方式:`npm run test -- src/components/platform-entry/platformDraftGenerationShelfModel.test.ts`、`npm run test -- src/components/custom-world-home/creationWorkShelf.test.ts -t "generation state|failure notice|failed puzzle"`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating puzzle draft|persisted generating match3d draft|completed baby object match draft"`、针对新 Module 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
|
- 验证方式:`npm run test -- src/components/platform-entry/platformDraftGenerationShelfModel.test.ts`、`npm run test -- src/components/custom-world-home/creationWorkShelf.test.ts -t "generation state|failure notice|failed puzzle"`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating puzzle draft|persisted generating match3d draft|completed baby object match draft"`、针对新 Module 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
|
||||||
- 关联文档:`docs/technical/【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md`。
|
- 关联文档:`docs/technical/【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md`。
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ Bark Battle 草稿三图完整性、生成状态归一、作品架摘要恢复
|
|||||||
|
|
||||||
RPG Agent 结果页发布门禁展示和预览来源 label 收口到 `src/components/platform-entry/platformRpgAgentResultPreviewModel.ts`,壳层只保留 session/profile 编排和结果页 props 传递,规则见 [【前端架构】PlatformRpgAgentResultPreviewModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformRpgAgentResultPreviewModel收口计划-2026-06-04.md)。
|
RPG Agent 结果页发布门禁展示和预览来源 label 收口到 `src/components/platform-entry/platformRpgAgentResultPreviewModel.ts`,壳层只保留 session/profile 编排和结果页 props 传递,规则见 [【前端架构】PlatformRpgAgentResultPreviewModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformRpgAgentResultPreviewModel收口计划-2026-06-04.md)。
|
||||||
|
|
||||||
平台入口创作生成通知、pending 作品架占位、失败覆盖、拼图稳定 ID 和草稿 Tab 未读点收口到 `src/components/platform-entry/platformDraftGenerationShelfModel.ts`,规则见 [【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91DraftGenerationShelfModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
平台入口创作生成通知、pending 作品架占位、作品详情更新回填、失败覆盖、拼图稳定 ID 和草稿 Tab 未读点收口到 `src/components/platform-entry/platformDraftGenerationShelfModel.ts`,规则见 [【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91DraftGenerationShelfModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||||
|
|
||||||
平台入口创作恢复 URL 私有 query、初始恢复判定、创作直达恢复目标解析、恢复目标身份匹配、跳一跳 / 敲木鱼恢复阶段落点、拼图 runtime query 与拼图稳定身份互推收口到 `src/components/platform-entry/platformCreationUrlStateModel.ts` 和 `src/components/platform-entry/platformPuzzleIdentityModel.ts`,规则见 [【前端架构】CreationUrlStateModel收口计划-2026-06-03.md](./technical/【前端架构】CreationUrlStateModel收口计划-2026-06-03.md)。
|
平台入口创作恢复 URL 私有 query、初始恢复判定、创作直达恢复目标解析、恢复目标身份匹配、跳一跳 / 敲木鱼恢复阶段落点、拼图 runtime query 与拼图稳定身份互推收口到 `src/components/platform-entry/platformCreationUrlStateModel.ts` 和 `src/components/platform-entry/platformPuzzleIdentityModel.ts`,规则见 [【前端架构】CreationUrlStateModel收口计划-2026-06-03.md](./technical/【前端架构】CreationUrlStateModel收口计划-2026-06-03.md)。
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## 背景
|
## 背景
|
||||||
|
|
||||||
`PlatformEntryFlowShellImpl.tsx` 同时承载创作生成状态、草稿 Tab 未读点、pending 作品架占位、失败文案覆盖和跨玩法 notice key。拼图、抓大鹅、方洞、跳一跳、敲木鱼、视觉小说、汪汪声浪、大鱼吃小鱼和宝贝识物各有不同的 `workId` / `profileId` / `sourceSessionId` / `draftId`,这些规则散在平台壳 **Implementation** 内,导致调用方必须理解每种玩法的草稿身份形状。
|
`PlatformEntryFlowShellImpl.tsx` 同时承载创作生成状态、草稿 Tab 未读点、pending 作品架占位、失败文案覆盖、作品详情更新回填和跨玩法 notice key。拼图、抓大鹅、方洞、跳一跳、敲木鱼、视觉小说、汪汪声浪、大鱼吃小鱼和宝贝识物各有不同的 `workId` / `profileId` / `sourceSessionId` / `draftId`,这些规则散在平台壳 **Implementation** 内,导致调用方必须理解每种玩法的草稿身份形状。
|
||||||
|
|
||||||
该 **Interface** 过浅:页面看似只关心“生成中 / 已完成未读 / 失败”,却要知道多 ID 去重、pending 草稿去重、失败摘要、拼图空标题兜底和持久化 generating 覆盖规则。
|
该 **Interface** 过浅:页面看似只关心“生成中 / 已完成未读 / 失败”,却要知道多 ID 去重、pending 草稿去重、失败摘要、拼图空标题兜底和持久化 generating 覆盖规则。
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
- `createPendingDraftShelfState(...)` 与 `buildPending*Works(...)`:统一把本地 pending 生成状态映射成作品架占位,并避免与后端已有草稿重复。
|
- `createPendingDraftShelfState(...)` 与 `buildPending*Works(...)`:统一把本地 pending 生成状态映射成作品架占位,并避免与后端已有草稿重复。
|
||||||
- `buildCreationWorkShelfRuntimeState({ item, notices, pendingShelfItems })`:统一输出 `CreationWorkShelfRuntimeState`,处理失败覆盖、拼图空标题 `拼图草稿` 兜底、summary 占位覆盖、生成中遮罩和 ready 未读点。
|
- `buildCreationWorkShelfRuntimeState({ item, notices, pendingShelfItems })`:统一输出 `CreationWorkShelfRuntimeState`,处理失败覆盖、拼图空标题 `拼图草稿` 兜底、summary 占位覆盖、生成中遮罩和 ready 未读点。
|
||||||
- `collectVisibleDraftNoticeKeys(...)` / `hasUnreadDraftGenerationUpdates(...)`:统一草稿 Tab 顶部未读点规则。
|
- `collectVisibleDraftNoticeKeys(...)` / `hasUnreadDraftGenerationUpdates(...)`:统一草稿 Tab 顶部未读点规则。
|
||||||
|
- `mergePuzzleWorkSummary(current, updated)` 与 `mergeBigFishWorkSummary(current, updated)`:统一作品详情更新后回填作品架和当前详情的身份匹配规则。
|
||||||
- `buildPuzzleResultWorkId(...)` / `buildPuzzleResultProfileId(...)`、`isPersistedDraftGenerating(...)` / `isPersistedDraftFailed(...)`:把拼图稳定 ID 与持久化状态判断收在同一 **Seam**。
|
- `buildPuzzleResultWorkId(...)` / `buildPuzzleResultProfileId(...)`、`isPersistedDraftGenerating(...)` / `isPersistedDraftFailed(...)`:把拼图稳定 ID 与持久化状态判断收在同一 **Seam**。
|
||||||
|
|
||||||
`PlatformEntryFlowShellImpl.tsx` 仍作为 React state 与副作用 **Adapter**:负责写入 `draftGenerationNotices` / `pendingDraftShelfItems`、启动生成、刷新后端列表、打开结果页和弹窗;它不再内联 pending shelf row shape、notice key 汇总和作品架 runtime state 规则。
|
`PlatformEntryFlowShellImpl.tsx` 仍作为 React state 与副作用 **Adapter**:负责写入 `draftGenerationNotices` / `pendingDraftShelfItems`、启动生成、刷新后端列表、打开结果页和弹窗;它不再内联 pending shelf row shape、notice key 汇总和作品架 runtime state 规则。
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
|
|
||||||
- 新玩法若需进入草稿生成通知,必须在此 **Module** 补 notice key、pending 占位和 visible key 映射,避免在平台壳里新增散落 switch。
|
- 新玩法若需进入草稿生成通知,必须在此 **Module** 补 notice key、pending 占位和 visible key 映射,避免在平台壳里新增散落 switch。
|
||||||
- pending 作品只用于本地生成任务尚未被后端作品架返回时的临时展示;一旦后端已有同一 `sourceSessionId` / `profileId` / `workId`,pending 占位必须让位。
|
- pending 作品只用于本地生成任务尚未被后端作品架返回时的临时展示;一旦后端已有同一 `sourceSessionId` / `profileId` / `workId`,pending 占位必须让位。
|
||||||
|
- 拼图作品详情更新只以 `profileId` 匹配回填;大鱼吃小鱼作品详情更新只以 `sourceSessionId` 匹配回填。
|
||||||
- 失败 notice 优先级高于持久化 generating,且可通过 pending metadata 提供更具体 summary;否则回退玩法默认失败摘要。
|
- 失败 notice 优先级高于持久化 generating,且可通过 pending metadata 提供更具体 summary;否则回退玩法默认失败摘要。
|
||||||
- 已有封面的拼图草稿即使局部关卡仍在后台生成,也不得被整卡遮罩为不可打开的生成中状态。
|
- 已有封面的拼图草稿即使局部关卡仍在后台生成,也不得被整卡遮罩为不可打开的生成中状态。
|
||||||
- 本 **Module** 不做网络请求、路由切换、弹窗副作用或 React state 写入,只保留纯 **Implementation**,以提高 **Depth**、**Leverage** 与 **Locality**。
|
- 本 **Module** 不做网络请求、路由切换、弹窗副作用或 React state 写入,只保留纯 **Implementation**,以提高 **Depth**、**Leverage** 与 **Locality**。
|
||||||
|
|||||||
@@ -458,6 +458,8 @@ import {
|
|||||||
hasUnreadReadyDraftGenerationNotice,
|
hasUnreadReadyDraftGenerationNotice,
|
||||||
isPersistedDraftFailed,
|
isPersistedDraftFailed,
|
||||||
isPersistedDraftGenerating,
|
isPersistedDraftGenerating,
|
||||||
|
mergeBigFishWorkSummary,
|
||||||
|
mergePuzzleWorkSummary,
|
||||||
normalizeDraftNoticeId,
|
normalizeDraftNoticeId,
|
||||||
type PendingDraftShelfKind,
|
type PendingDraftShelfKind,
|
||||||
type PendingDraftShelfMap,
|
type PendingDraftShelfMap,
|
||||||
@@ -740,13 +742,6 @@ const PUZZLE_DRAFT_GENERATION_POINT_COST = 2;
|
|||||||
const MATCH3D_DRAFT_GENERATION_POINT_COST = 10;
|
const MATCH3D_DRAFT_GENERATION_POINT_COST = 10;
|
||||||
const BARK_BATTLE_DRAFT_GENERATION_POINT_COST = 3;
|
const BARK_BATTLE_DRAFT_GENERATION_POINT_COST = 3;
|
||||||
|
|
||||||
function mergePuzzleWorkSummary(
|
|
||||||
current: PuzzleWorkSummary,
|
|
||||||
updated: PuzzleWorkSummary,
|
|
||||||
): PuzzleWorkSummary {
|
|
||||||
return current.profileId === updated.profileId ? updated : current;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PUZZLE_ONBOARDING_FIRST_VISIT_STORAGE_KEY =
|
const PUZZLE_ONBOARDING_FIRST_VISIT_STORAGE_KEY =
|
||||||
'genarrative.puzzle-onboarding.first-visit.v1';
|
'genarrative.puzzle-onboarding.first-visit.v1';
|
||||||
const PUZZLE_ONBOARDING_COPY = '待定待定待定';
|
const PUZZLE_ONBOARDING_COPY = '待定待定待定';
|
||||||
@@ -920,15 +915,6 @@ function markPuzzleOnboardingSeen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeBigFishWorkSummary(
|
|
||||||
current: BigFishWorkSummary,
|
|
||||||
updated: BigFishWorkSummary,
|
|
||||||
): BigFishWorkSummary {
|
|
||||||
return current.sourceSessionId === updated.sourceSessionId
|
|
||||||
? updated
|
|
||||||
: current;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resolvePublicWorkAuthorSummary(
|
async function resolvePublicWorkAuthorSummary(
|
||||||
entry: PlatformPublicGalleryCard,
|
entry: PlatformPublicGalleryCard,
|
||||||
): Promise<PublicUserSummary | null> {
|
): Promise<PublicUserSummary | null> {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { describe, expect, test } from 'vitest';
|
import { describe, expect, test } from 'vitest';
|
||||||
|
|
||||||
|
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
|
||||||
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
||||||
import { buildCreationWorkShelfItems } from '../custom-world-home/creationWorkShelf';
|
import { buildCreationWorkShelfItems } from '../custom-world-home/creationWorkShelf';
|
||||||
import {
|
import {
|
||||||
@@ -12,6 +13,8 @@ import {
|
|||||||
type DraftGenerationNoticeMap,
|
type DraftGenerationNoticeMap,
|
||||||
getGenerationNoticeShelfKeys,
|
getGenerationNoticeShelfKeys,
|
||||||
hasUnreadDraftGenerationUpdates,
|
hasUnreadDraftGenerationUpdates,
|
||||||
|
mergeBigFishWorkSummary,
|
||||||
|
mergePuzzleWorkSummary,
|
||||||
} from './platformDraftGenerationShelfModel';
|
} from './platformDraftGenerationShelfModel';
|
||||||
|
|
||||||
describe('platformDraftGenerationShelfModel', () => {
|
describe('platformDraftGenerationShelfModel', () => {
|
||||||
@@ -53,6 +56,42 @@ describe('platformDraftGenerationShelfModel', () => {
|
|||||||
expect(pending).toEqual([]);
|
expect(pending).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('mergePuzzleWorkSummary only replaces the matching profile', () => {
|
||||||
|
const current = buildPuzzleWork({
|
||||||
|
profileId: 'puzzle-profile-1',
|
||||||
|
workTitle: '旧拼图',
|
||||||
|
});
|
||||||
|
const updated = buildPuzzleWork({
|
||||||
|
profileId: 'puzzle-profile-1',
|
||||||
|
workTitle: '新拼图',
|
||||||
|
});
|
||||||
|
const other = buildPuzzleWork({
|
||||||
|
profileId: 'puzzle-profile-2',
|
||||||
|
workTitle: '别的拼图',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mergePuzzleWorkSummary(current, updated)).toBe(updated);
|
||||||
|
expect(mergePuzzleWorkSummary(current, other)).toBe(current);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mergeBigFishWorkSummary only replaces the matching source session', () => {
|
||||||
|
const current = buildBigFishWork({
|
||||||
|
sourceSessionId: 'big-fish-session-1',
|
||||||
|
title: '旧大鱼',
|
||||||
|
});
|
||||||
|
const updated = buildBigFishWork({
|
||||||
|
sourceSessionId: 'big-fish-session-1',
|
||||||
|
title: '新大鱼',
|
||||||
|
});
|
||||||
|
const other = buildBigFishWork({
|
||||||
|
sourceSessionId: 'big-fish-session-2',
|
||||||
|
title: '别的大鱼',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mergeBigFishWorkSummary(current, updated)).toBe(updated);
|
||||||
|
expect(mergeBigFishWorkSummary(current, other)).toBe(current);
|
||||||
|
});
|
||||||
|
|
||||||
test('buildCreationWorkShelfRuntimeState lets failure notice override persisted generating puzzle copy', () => {
|
test('buildCreationWorkShelfRuntimeState lets failure notice override persisted generating puzzle copy', () => {
|
||||||
const [item] = buildCreationWorkShelfItems({
|
const [item] = buildCreationWorkShelfItems({
|
||||||
rpgItems: [],
|
rpgItems: [],
|
||||||
@@ -187,3 +226,30 @@ function buildPuzzleWork(
|
|||||||
...overrides,
|
...overrides,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildBigFishWork(
|
||||||
|
overrides: Partial<BigFishWorkSummary> = {},
|
||||||
|
): BigFishWorkSummary {
|
||||||
|
return {
|
||||||
|
workId: 'big-fish-work-base',
|
||||||
|
sourceSessionId: 'big-fish-session-base',
|
||||||
|
ownerUserId: 'user-1',
|
||||||
|
authorDisplayName: '测试作者',
|
||||||
|
title: '潮雾大鱼',
|
||||||
|
subtitle: '潮雾港口',
|
||||||
|
summary: '潮雾港口大鱼吃小鱼。',
|
||||||
|
coverImageSrc: null,
|
||||||
|
status: 'draft',
|
||||||
|
updatedAt: '2026-06-03T08:00:00.000Z',
|
||||||
|
publishedAt: null,
|
||||||
|
playCount: 0,
|
||||||
|
remixCount: 0,
|
||||||
|
likeCount: 0,
|
||||||
|
publishReady: false,
|
||||||
|
levelCount: 1,
|
||||||
|
levelMainImageReadyCount: 0,
|
||||||
|
levelMotionReadyCount: 0,
|
||||||
|
backgroundReady: false,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -479,6 +479,22 @@ export function hasUnreadDraftGenerationUpdates(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mergeBigFishWorkSummary(
|
||||||
|
current: BigFishWorkSummary,
|
||||||
|
updated: BigFishWorkSummary,
|
||||||
|
): BigFishWorkSummary {
|
||||||
|
return current.sourceSessionId === updated.sourceSessionId
|
||||||
|
? updated
|
||||||
|
: current;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergePuzzleWorkSummary(
|
||||||
|
current: PuzzleWorkSummary,
|
||||||
|
updated: PuzzleWorkSummary,
|
||||||
|
): PuzzleWorkSummary {
|
||||||
|
return current.profileId === updated.profileId ? updated : current;
|
||||||
|
}
|
||||||
|
|
||||||
export function buildPendingBigFishWorks(
|
export function buildPendingBigFishWorks(
|
||||||
pending: Record<string, PendingDraftShelfState> | undefined,
|
pending: Record<string, PendingDraftShelfState> | undefined,
|
||||||
existingItems: readonly BigFishWorkSummary[],
|
existingItems: readonly BigFishWorkSummary[],
|
||||||
|
|||||||
Reference in New Issue
Block a user