refactor: 收口剩余草稿打开 intent
This commit is contained in:
@@ -16,6 +16,14 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-06-04 Draft Generation Shelf 剩余草稿打开 intent 收口
|
||||||
|
|
||||||
|
- 背景:拼图 / 抓大鹅草稿打开 intent 已归入 `platformDraftGenerationShelfModel.ts`,但方洞挑战、大鱼吃小鱼和视觉小说仍在平台壳层内联判断已发布详情、缺 session、active generating、当前结果页和普通草稿恢复。
|
||||||
|
- 决策:继续扩展 `src/components/platform-entry/platformDraftGenerationShelfModel.ts`,新增 `resolveSquareHoleDraftOpenIntent(...)`、`resolveBigFishDraftOpenIntent(...)` 与 `resolveVisualNovelDraftOpenIntent(...)`;平台壳只按 intent 执行 notice seen、详情打开、恢复 session、读取 work detail、清生成态和切 stage 副作用。
|
||||||
|
- 影响范围:创作中心作品架打开方洞挑战 / 大鱼吃小鱼 / 视觉小说草稿、创作 URL 恢复时强制打开草稿、生成中回到生成页和视觉小说结果页恢复。
|
||||||
|
- 验证方式:`npm run test -- src/components/platform-entry/platformDraftGenerationShelfModel.test.ts`、针对 Draft Shelf Module 与平台壳执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
|
||||||
|
- 关联文档:`docs/technical/【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md`。
|
||||||
|
|
||||||
## 2026-06-04 Platform Public Code Search matcher / DTO 收口
|
## 2026-06-04 Platform Public Code Search matcher / DTO 收口
|
||||||
|
|
||||||
- 背景:`resolvePlatformPublicCodeSearchPlan(...)` 已收口公开搜索顺序,但 `PlatformEntryFlowShellImpl.tsx` 仍内联 RPG by-code DTO 构造,以及拼图、大鱼吃小鱼、跳一跳、敲木鱼、宝贝识物、抓大鹅、方洞挑战、视觉小说和汪汪声浪的 `isSame*PublicWorkCode` 匹配、公开可见性过滤与详情卡映射。
|
- 背景:`resolvePlatformPublicCodeSearchPlan(...)` 已收口公开搜索顺序,但 `PlatformEntryFlowShellImpl.tsx` 仍内联 RPG by-code DTO 构造,以及拼图、大鱼吃小鱼、跳一跳、敲木鱼、宝贝识物、抓大鹅、方洞挑战、视觉小说和汪汪声浪的 `isSame*PublicWorkCode` 匹配、公开可见性过滤与详情卡映射。
|
||||||
|
|||||||
@@ -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)。
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,10 @@
|
|||||||
- `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)`:统一作品详情更新后回填作品架和当前详情的身份匹配规则。
|
- `mergePuzzleWorkSummary(current, updated)` 与 `mergeBigFishWorkSummary(current, updated)`:统一作品详情更新后回填作品架和当前详情的身份匹配规则。
|
||||||
- `resolvePuzzleDraftOpenIntent(...)` 与 `resolveMatch3DDraftOpenIntent(...)`:统一拼图 / 抓大鹅草稿打开时的已发布详情、缺 session、ready 未读试玩、失败生成页、active / background 生成页、持久化 generating 恢复和普通草稿恢复优先级。
|
- `resolvePuzzleDraftOpenIntent(...)`、`resolveMatch3DDraftOpenIntent(...)`、`resolveSquareHoleDraftOpenIntent(...)`、`resolveBigFishDraftOpenIntent(...)` 与 `resolveVisualNovelDraftOpenIntent(...)`:统一拼图、抓大鹅、方洞挑战、大鱼吃小鱼和视觉小说草稿打开时的已发布详情、缺 session、ready 未读试玩、失败 / active / background 生成页、当前结果页、持久化 generating 恢复和普通草稿恢复优先级。
|
||||||
- `buildPuzzleResultWorkId(...)` / `buildPuzzleResultProfileId(...)`、`isPersistedDraftGenerating(...)` / `isPersistedDraftFailed(...)`:把拼图稳定 ID 与持久化状态判断收在同一 **Seam**。
|
- `buildPuzzleResultWorkId(...)` / `buildPuzzleResultProfileId(...)`、`isPersistedDraftGenerating(...)` / `isPersistedDraftFailed(...)`:把拼图稳定 ID 与持久化状态判断收在同一 **Seam**。
|
||||||
|
|
||||||
`PlatformEntryFlowShellImpl.tsx` 仍作为 React state 与副作用 **Adapter**:负责写入 `draftGenerationNotices` / `pendingDraftShelfItems`、读取生成 session、启动 ready 草稿试玩、刷新后端列表、打开结果页和弹窗;它不再内联 pending shelf row shape、notice key 汇总、作品架 runtime state 和拼图 / 抓大鹅草稿打开优先级。
|
`PlatformEntryFlowShellImpl.tsx` 仍作为 React state 与副作用 **Adapter**:负责写入 `draftGenerationNotices` / `pendingDraftShelfItems`、读取生成 session、启动 ready 草稿试玩、刷新后端列表、打开结果页和弹窗;它不再内联 pending shelf row shape、notice key 汇总、作品架 runtime state 和上述玩法草稿打开优先级。
|
||||||
|
|
||||||
## 约定
|
## 约定
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
- 拼图作品详情更新只以 `profileId` 匹配回填;大鱼吃小鱼作品详情更新只以 `sourceSessionId` 匹配回填。
|
- 拼图作品详情更新只以 `profileId` 匹配回填;大鱼吃小鱼作品详情更新只以 `sourceSessionId` 匹配回填。
|
||||||
- 失败 notice 优先级高于持久化 generating,且可通过 pending metadata 提供更具体 summary;否则回退玩法默认失败摘要。
|
- 失败 notice 优先级高于持久化 generating,且可通过 pending metadata 提供更具体 summary;否则回退玩法默认失败摘要。
|
||||||
- 已有封面的拼图草稿即使局部关卡仍在后台生成,也不得被整卡遮罩为不可打开的生成中状态。
|
- 已有封面的拼图草稿即使局部关卡仍在后台生成,也不得被整卡遮罩为不可打开的生成中状态。
|
||||||
- 拼图 / 抓大鹅草稿打开 intent 只返回纯计划与 notice keys,不创建失败生成态、不请求详情、不写 stage;这些仍由壳层 Adapter 执行。
|
- 草稿打开 intent 只返回纯计划、notice keys 与必要稳定 ID,不创建失败生成态、不请求详情、不写 stage;这些仍由壳层 Adapter 执行。
|
||||||
- 本 **Module** 不做网络请求、路由切换、弹窗副作用或 React state 写入,只保留纯 **Implementation**,以提高 **Depth**、**Leverage** 与 **Locality**。
|
- 本 **Module** 不做网络请求、路由切换、弹窗副作用或 React state 写入,只保留纯 **Implementation**,以提高 **Depth**、**Leverage** 与 **Locality**。
|
||||||
|
|
||||||
## 验证
|
## 验证
|
||||||
|
|||||||
@@ -447,8 +447,11 @@ import {
|
|||||||
type PendingDraftShelfKind,
|
type PendingDraftShelfKind,
|
||||||
type PendingDraftShelfMap,
|
type PendingDraftShelfMap,
|
||||||
type PendingDraftShelfMetadata,
|
type PendingDraftShelfMetadata,
|
||||||
|
resolveBigFishDraftOpenIntent,
|
||||||
resolveMatch3DDraftOpenIntent,
|
resolveMatch3DDraftOpenIntent,
|
||||||
resolvePuzzleDraftOpenIntent,
|
resolvePuzzleDraftOpenIntent,
|
||||||
|
resolveSquareHoleDraftOpenIntent,
|
||||||
|
resolveVisualNovelDraftOpenIntent,
|
||||||
} from './platformDraftGenerationShelfModel';
|
} from './platformDraftGenerationShelfModel';
|
||||||
import {
|
import {
|
||||||
canExposePublicWork,
|
canExposePublicWork,
|
||||||
@@ -10685,42 +10688,42 @@ export function PlatformEntryFlowShellImpl({
|
|||||||
item: SquareHoleWorkSummary,
|
item: SquareHoleWorkSummary,
|
||||||
options: { forceDraft?: boolean } = {},
|
options: { forceDraft?: boolean } = {},
|
||||||
) => {
|
) => {
|
||||||
|
const openIntent = resolveSquareHoleDraftOpenIntent({
|
||||||
|
item,
|
||||||
|
forceDraft: options.forceDraft,
|
||||||
|
activeSessionId: squareHoleSession?.sessionId,
|
||||||
|
hasActiveGenerationRunning: isMiniGameDraftGenerating(
|
||||||
|
squareHoleGenerationState,
|
||||||
|
),
|
||||||
|
isGenerationReady: isMiniGameDraftReady(squareHoleGenerationState),
|
||||||
|
});
|
||||||
setSquareHoleRun(null);
|
setSquareHoleRun(null);
|
||||||
setSquareHoleError(null);
|
setSquareHoleError(null);
|
||||||
setSquareHoleProfile(null);
|
setSquareHoleProfile(null);
|
||||||
markDraftNoticeSeen(
|
markDraftNoticeSeen(openIntent.noticeKeys);
|
||||||
collectDraftNoticeKeys('square-hole', [
|
|
||||||
item.workId,
|
|
||||||
item.profileId,
|
|
||||||
item.sourceSessionId,
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (item.publicationStatus === 'published' && !options.forceDraft) {
|
if (openIntent.type === 'open-published-detail') {
|
||||||
openPublicWorkDetail(mapSquareHoleWorkToPublicWorkDetail(item));
|
openPublicWorkDetail(mapSquareHoleWorkToPublicWorkDetail(item));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!item.sourceSessionId?.trim()) {
|
if (openIntent.type === 'missing-session') {
|
||||||
setSquareHoleError('这份方洞挑战草稿缺少会话信息,请重新开始创作。');
|
setSquareHoleError(openIntent.errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (openIntent.type === 'active-generation') {
|
||||||
item.sourceSessionId === squareHoleSession?.sessionId &&
|
|
||||||
isMiniGameDraftGenerating(squareHoleGenerationState)
|
|
||||||
) {
|
|
||||||
enterCreateTab();
|
enterCreateTab();
|
||||||
selectionStageRef.current = 'square-hole-generating';
|
selectionStageRef.current = 'square-hole-generating';
|
||||||
setSelectionStage('square-hole-generating');
|
setSelectionStage('square-hole-generating');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isMiniGameDraftReady(squareHoleGenerationState)) {
|
if (openIntent.shouldClearGenerationState) {
|
||||||
setSquareHoleGenerationState(null);
|
setSquareHoleGenerationState(null);
|
||||||
}
|
}
|
||||||
const restoredSession = await squareHoleFlow.restoreDraft(
|
const restoredSession = await squareHoleFlow.restoreDraft(
|
||||||
item.sourceSessionId,
|
openIntent.sourceSessionId,
|
||||||
);
|
);
|
||||||
if (!restoredSession) {
|
if (!restoredSession) {
|
||||||
await refreshSquareHoleShelf().catch(() => undefined);
|
await refreshSquareHoleShelf().catch(() => undefined);
|
||||||
@@ -10756,21 +10759,23 @@ export function PlatformEntryFlowShellImpl({
|
|||||||
|
|
||||||
const openBigFishDraft = useCallback(
|
const openBigFishDraft = useCallback(
|
||||||
async (item: BigFishWorkSummary) => {
|
async (item: BigFishWorkSummary) => {
|
||||||
|
const openIntent = resolveBigFishDraftOpenIntent({
|
||||||
|
item,
|
||||||
|
activeSessionId: bigFishSession?.sessionId,
|
||||||
|
hasActiveGenerationRunning: isMiniGameDraftGenerating(
|
||||||
|
bigFishGenerationState,
|
||||||
|
),
|
||||||
|
});
|
||||||
setBigFishRun(null);
|
setBigFishRun(null);
|
||||||
markDraftNoticeSeen(
|
markDraftNoticeSeen(openIntent.noticeKeys);
|
||||||
collectDraftNoticeKeys('big-fish', [item.workId, item.sourceSessionId]),
|
if (openIntent.type === 'active-generation') {
|
||||||
);
|
|
||||||
if (
|
|
||||||
item.sourceSessionId === bigFishSession?.sessionId &&
|
|
||||||
isMiniGameDraftGenerating(bigFishGenerationState)
|
|
||||||
) {
|
|
||||||
enterCreateTab();
|
enterCreateTab();
|
||||||
selectionStageRef.current = 'big-fish-generating';
|
selectionStageRef.current = 'big-fish-generating';
|
||||||
setSelectionStage('big-fish-generating');
|
setSelectionStage('big-fish-generating');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const restoredSession = await bigFishFlow.restoreDraft(
|
const restoredSession = await bigFishFlow.restoreDraft(
|
||||||
item.sourceSessionId,
|
openIntent.sourceSessionId,
|
||||||
);
|
);
|
||||||
if (!restoredSession) {
|
if (!restoredSession) {
|
||||||
await refreshBigFishShelf().catch(() => undefined);
|
await refreshBigFishShelf().catch(() => undefined);
|
||||||
@@ -10823,27 +10828,27 @@ export function PlatformEntryFlowShellImpl({
|
|||||||
item: VisualNovelWorkSummary,
|
item: VisualNovelWorkSummary,
|
||||||
options: { forceDraft?: boolean } = {},
|
options: { forceDraft?: boolean } = {},
|
||||||
) => {
|
) => {
|
||||||
if (item.publishStatus === 'published' && !options.forceDraft) {
|
const openIntent = resolveVisualNovelDraftOpenIntent({
|
||||||
|
item,
|
||||||
|
forceDraft: options.forceDraft,
|
||||||
|
activeSessionId: visualNovelSession?.sessionId,
|
||||||
|
hasActiveGenerationRunning: visualNovelGenerationPhase === 'generating',
|
||||||
|
hasActiveSessionDraft: Boolean(visualNovelSession?.draft),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (openIntent.type === 'open-published-detail') {
|
||||||
openPublicWorkDetail(mapVisualNovelWorkToPublicWorkDetail(item));
|
openPublicWorkDetail(mapVisualNovelWorkToPublicWorkDetail(item));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
markDraftNoticeSeen(
|
markDraftNoticeSeen(openIntent.noticeKeys);
|
||||||
collectDraftNoticeKeys('visual-novel', [item.profileId]),
|
if (openIntent.type === 'active-generation') {
|
||||||
);
|
|
||||||
if (
|
|
||||||
item.profileId === visualNovelSession?.sessionId &&
|
|
||||||
visualNovelGenerationPhase === 'generating'
|
|
||||||
) {
|
|
||||||
enterCreateTab();
|
enterCreateTab();
|
||||||
selectionStageRef.current = 'visual-novel-generating';
|
selectionStageRef.current = 'visual-novel-generating';
|
||||||
setSelectionStage('visual-novel-generating');
|
setSelectionStage('visual-novel-generating');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (openIntent.type === 'current-result') {
|
||||||
item.profileId === visualNovelSession?.sessionId &&
|
|
||||||
visualNovelSession.draft
|
|
||||||
) {
|
|
||||||
enterCreateTab();
|
enterCreateTab();
|
||||||
setSelectionStage('visual-novel-result');
|
setSelectionStage('visual-novel-result');
|
||||||
return;
|
return;
|
||||||
@@ -10856,7 +10861,7 @@ export function PlatformEntryFlowShellImpl({
|
|||||||
setIsVisualNovelBusy(true);
|
setIsVisualNovelBusy(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { work } = await getVisualNovelWorkDetail(item.profileId);
|
const { work } = await getVisualNovelWorkDetail(openIntent.profileId);
|
||||||
setVisualNovelWork(work);
|
setVisualNovelWork(work);
|
||||||
setVisualNovelSession(buildVisualNovelSessionFromWorkDetail(work));
|
setVisualNovelSession(buildVisualNovelSessionFromWorkDetail(work));
|
||||||
enterCreateTab();
|
enterCreateTab();
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { describe, expect, test } from 'vitest';
|
|||||||
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
|
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
|
||||||
import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks';
|
import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks';
|
||||||
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
||||||
|
import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks';
|
||||||
|
import type { VisualNovelWorkSummary } from '../../../packages/shared/src/contracts/visualNovel';
|
||||||
import { buildCreationWorkShelfItems } from '../custom-world-home/creationWorkShelf';
|
import { buildCreationWorkShelfItems } from '../custom-world-home/creationWorkShelf';
|
||||||
import {
|
import {
|
||||||
buildCreationWorkShelfRuntimeState,
|
buildCreationWorkShelfRuntimeState,
|
||||||
@@ -16,8 +18,11 @@ import {
|
|||||||
hasUnreadDraftGenerationUpdates,
|
hasUnreadDraftGenerationUpdates,
|
||||||
mergeBigFishWorkSummary,
|
mergeBigFishWorkSummary,
|
||||||
mergePuzzleWorkSummary,
|
mergePuzzleWorkSummary,
|
||||||
|
resolveBigFishDraftOpenIntent,
|
||||||
resolveMatch3DDraftOpenIntent,
|
resolveMatch3DDraftOpenIntent,
|
||||||
resolvePuzzleDraftOpenIntent,
|
resolvePuzzleDraftOpenIntent,
|
||||||
|
resolveSquareHoleDraftOpenIntent,
|
||||||
|
resolveVisualNovelDraftOpenIntent,
|
||||||
} from './platformDraftGenerationShelfModel';
|
} from './platformDraftGenerationShelfModel';
|
||||||
|
|
||||||
describe('platformDraftGenerationShelfModel', () => {
|
describe('platformDraftGenerationShelfModel', () => {
|
||||||
@@ -150,6 +155,131 @@ describe('platformDraftGenerationShelfModel', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('resolveBigFishDraftOpenIntent reopens active generating session before restoring draft', () => {
|
||||||
|
expect(
|
||||||
|
resolveBigFishDraftOpenIntent({
|
||||||
|
item: buildBigFishWork(),
|
||||||
|
activeSessionId: 'big-fish-session-base',
|
||||||
|
hasActiveGenerationRunning: true,
|
||||||
|
}),
|
||||||
|
).toMatchObject({
|
||||||
|
type: 'active-generation',
|
||||||
|
sourceSessionId: 'big-fish-session-base',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveBigFishDraftOpenIntent({
|
||||||
|
item: buildBigFishWork(),
|
||||||
|
activeSessionId: 'other-session',
|
||||||
|
hasActiveGenerationRunning: true,
|
||||||
|
}),
|
||||||
|
).toMatchObject({
|
||||||
|
type: 'restore-draft',
|
||||||
|
sourceSessionId: 'big-fish-session-base',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('resolveSquareHoleDraftOpenIntent handles published, missing, active and restore states', () => {
|
||||||
|
expect(
|
||||||
|
resolveSquareHoleDraftOpenIntent({
|
||||||
|
item: buildSquareHoleWork({ publicationStatus: 'published' }),
|
||||||
|
activeSessionId: null,
|
||||||
|
hasActiveGenerationRunning: false,
|
||||||
|
isGenerationReady: false,
|
||||||
|
}),
|
||||||
|
).toMatchObject({
|
||||||
|
type: 'open-published-detail',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveSquareHoleDraftOpenIntent({
|
||||||
|
item: buildSquareHoleWork({ sourceSessionId: null }),
|
||||||
|
forceDraft: true,
|
||||||
|
activeSessionId: null,
|
||||||
|
hasActiveGenerationRunning: false,
|
||||||
|
isGenerationReady: false,
|
||||||
|
}),
|
||||||
|
).toMatchObject({
|
||||||
|
type: 'missing-session',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveSquareHoleDraftOpenIntent({
|
||||||
|
item: buildSquareHoleWork(),
|
||||||
|
activeSessionId: 'square-hole-session-base',
|
||||||
|
hasActiveGenerationRunning: true,
|
||||||
|
isGenerationReady: false,
|
||||||
|
}),
|
||||||
|
).toMatchObject({
|
||||||
|
type: 'active-generation',
|
||||||
|
sourceSessionId: 'square-hole-session-base',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveSquareHoleDraftOpenIntent({
|
||||||
|
item: buildSquareHoleWork(),
|
||||||
|
activeSessionId: 'other-session',
|
||||||
|
hasActiveGenerationRunning: false,
|
||||||
|
isGenerationReady: false,
|
||||||
|
}),
|
||||||
|
).toMatchObject({
|
||||||
|
type: 'restore-draft',
|
||||||
|
shouldClearGenerationState: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('resolveVisualNovelDraftOpenIntent handles published, active, current result and load detail states', () => {
|
||||||
|
expect(
|
||||||
|
resolveVisualNovelDraftOpenIntent({
|
||||||
|
item: buildVisualNovelWork({ publishStatus: 'published' }),
|
||||||
|
activeSessionId: null,
|
||||||
|
hasActiveGenerationRunning: false,
|
||||||
|
hasActiveSessionDraft: false,
|
||||||
|
}),
|
||||||
|
).toMatchObject({
|
||||||
|
type: 'open-published-detail',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveVisualNovelDraftOpenIntent({
|
||||||
|
item: buildVisualNovelWork(),
|
||||||
|
forceDraft: true,
|
||||||
|
activeSessionId: 'visual-novel-profile-base',
|
||||||
|
hasActiveGenerationRunning: true,
|
||||||
|
hasActiveSessionDraft: false,
|
||||||
|
}),
|
||||||
|
).toMatchObject({
|
||||||
|
type: 'active-generation',
|
||||||
|
profileId: 'visual-novel-profile-base',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveVisualNovelDraftOpenIntent({
|
||||||
|
item: buildVisualNovelWork(),
|
||||||
|
forceDraft: true,
|
||||||
|
activeSessionId: 'visual-novel-profile-base',
|
||||||
|
hasActiveGenerationRunning: false,
|
||||||
|
hasActiveSessionDraft: true,
|
||||||
|
}),
|
||||||
|
).toMatchObject({
|
||||||
|
type: 'current-result',
|
||||||
|
profileId: 'visual-novel-profile-base',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveVisualNovelDraftOpenIntent({
|
||||||
|
item: buildVisualNovelWork(),
|
||||||
|
forceDraft: true,
|
||||||
|
activeSessionId: 'other-profile',
|
||||||
|
hasActiveGenerationRunning: false,
|
||||||
|
hasActiveSessionDraft: false,
|
||||||
|
}),
|
||||||
|
).toMatchObject({
|
||||||
|
type: 'load-detail',
|
||||||
|
profileId: 'visual-novel-profile-base',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('buildPendingPuzzleWorks creates failed puzzle placeholder with stable ids and fallback title', () => {
|
test('buildPendingPuzzleWorks creates failed puzzle placeholder with stable ids and fallback title', () => {
|
||||||
const pending = buildPendingPuzzleWorks(
|
const pending = buildPendingPuzzleWorks(
|
||||||
{
|
{
|
||||||
@@ -425,3 +555,52 @@ function buildBigFishWork(
|
|||||||
...overrides,
|
...overrides,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildSquareHoleWork(
|
||||||
|
overrides: Partial<SquareHoleWorkSummary> = {},
|
||||||
|
): SquareHoleWorkSummary {
|
||||||
|
return {
|
||||||
|
workId: 'square-hole-work-base',
|
||||||
|
profileId: 'square-hole-profile-base',
|
||||||
|
ownerUserId: 'user-1',
|
||||||
|
sourceSessionId: 'square-hole-session-base',
|
||||||
|
gameName: '潮雾方洞',
|
||||||
|
themeText: '潮雾港口',
|
||||||
|
twistRule: '避开雾门',
|
||||||
|
summary: '潮雾港口方洞挑战。',
|
||||||
|
tags: [],
|
||||||
|
coverImageSrc: null,
|
||||||
|
backgroundPrompt: '潮雾港口',
|
||||||
|
backgroundImageSrc: null,
|
||||||
|
shapeOptions: [],
|
||||||
|
holeOptions: [],
|
||||||
|
shapeCount: 1,
|
||||||
|
difficulty: 1,
|
||||||
|
publicationStatus: 'draft',
|
||||||
|
playCount: 0,
|
||||||
|
updatedAt: '2026-06-03T08:00:00.000Z',
|
||||||
|
publishedAt: null,
|
||||||
|
publishReady: false,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildVisualNovelWork(
|
||||||
|
overrides: Partial<VisualNovelWorkSummary> = {},
|
||||||
|
): VisualNovelWorkSummary {
|
||||||
|
return {
|
||||||
|
runtimeKind: 'visual-novel',
|
||||||
|
profileId: 'visual-novel-profile-base',
|
||||||
|
ownerUserId: 'user-1',
|
||||||
|
title: '潮雾视觉小说',
|
||||||
|
description: '潮雾港口视觉小说。',
|
||||||
|
coverImageSrc: null,
|
||||||
|
tags: [],
|
||||||
|
publishStatus: 'draft',
|
||||||
|
publishReady: false,
|
||||||
|
playCount: 0,
|
||||||
|
updatedAt: '2026-06-03T08:00:00.000Z',
|
||||||
|
publishedAt: null,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -148,6 +148,61 @@ export type Match3DDraftOpenIntent =
|
|||||||
noticeKeys: string[];
|
noticeKeys: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type BigFishDraftOpenIntent =
|
||||||
|
| {
|
||||||
|
type: 'active-generation';
|
||||||
|
noticeKeys: string[];
|
||||||
|
sourceSessionId: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'restore-draft';
|
||||||
|
noticeKeys: string[];
|
||||||
|
sourceSessionId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SquareHoleDraftOpenIntent =
|
||||||
|
| {
|
||||||
|
type: 'open-published-detail';
|
||||||
|
noticeKeys: string[];
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'missing-session';
|
||||||
|
noticeKeys: string[];
|
||||||
|
errorMessage: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'active-generation';
|
||||||
|
noticeKeys: string[];
|
||||||
|
sourceSessionId: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'restore-draft';
|
||||||
|
noticeKeys: string[];
|
||||||
|
sourceSessionId: string;
|
||||||
|
shouldClearGenerationState: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type VisualNovelDraftOpenIntent =
|
||||||
|
| {
|
||||||
|
type: 'open-published-detail';
|
||||||
|
noticeKeys: string[];
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'active-generation';
|
||||||
|
noticeKeys: string[];
|
||||||
|
profileId: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'current-result';
|
||||||
|
noticeKeys: string[];
|
||||||
|
profileId: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'load-detail';
|
||||||
|
noticeKeys: string[];
|
||||||
|
profileId: string;
|
||||||
|
};
|
||||||
|
|
||||||
export function buildDraftNoticeKey(
|
export function buildDraftNoticeKey(
|
||||||
kind: CreationWorkShelfKind,
|
kind: CreationWorkShelfKind,
|
||||||
id: string,
|
id: string,
|
||||||
@@ -433,6 +488,29 @@ export function buildMatch3DDraftOpenNoticeKeys(item: Match3DWorkSummary) {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildBigFishDraftOpenNoticeKeys(item: BigFishWorkSummary) {
|
||||||
|
return collectDraftNoticeKeys('big-fish', [
|
||||||
|
item.workId,
|
||||||
|
item.sourceSessionId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildSquareHoleDraftOpenNoticeKeys(
|
||||||
|
item: SquareHoleWorkSummary,
|
||||||
|
) {
|
||||||
|
return collectDraftNoticeKeys('square-hole', [
|
||||||
|
item.workId,
|
||||||
|
item.profileId,
|
||||||
|
item.sourceSessionId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildVisualNovelDraftOpenNoticeKeys(
|
||||||
|
item: VisualNovelWorkSummary,
|
||||||
|
) {
|
||||||
|
return collectDraftNoticeKeys('visual-novel', [item.profileId]);
|
||||||
|
}
|
||||||
|
|
||||||
export function resolvePuzzleDraftOpenIntent(params: {
|
export function resolvePuzzleDraftOpenIntent(params: {
|
||||||
item: PuzzleWorkSummary;
|
item: PuzzleWorkSummary;
|
||||||
notices: DraftGenerationNoticeMap;
|
notices: DraftGenerationNoticeMap;
|
||||||
@@ -628,6 +706,117 @@ export function resolveMatch3DDraftOpenIntent(params: {
|
|||||||
return { type: 'restore-draft', noticeKeys };
|
return { type: 'restore-draft', noticeKeys };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveBigFishDraftOpenIntent(params: {
|
||||||
|
item: BigFishWorkSummary;
|
||||||
|
activeSessionId?: string | null;
|
||||||
|
hasActiveGenerationRunning: boolean;
|
||||||
|
}): BigFishDraftOpenIntent {
|
||||||
|
const { item, activeSessionId, hasActiveGenerationRunning } = params;
|
||||||
|
const noticeKeys = buildBigFishDraftOpenNoticeKeys(item);
|
||||||
|
if (item.sourceSessionId === activeSessionId && hasActiveGenerationRunning) {
|
||||||
|
return {
|
||||||
|
type: 'active-generation',
|
||||||
|
noticeKeys,
|
||||||
|
sourceSessionId: item.sourceSessionId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'restore-draft',
|
||||||
|
noticeKeys,
|
||||||
|
sourceSessionId: item.sourceSessionId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveSquareHoleDraftOpenIntent(params: {
|
||||||
|
item: SquareHoleWorkSummary;
|
||||||
|
forceDraft?: boolean;
|
||||||
|
activeSessionId?: string | null;
|
||||||
|
hasActiveGenerationRunning: boolean;
|
||||||
|
isGenerationReady: boolean;
|
||||||
|
}): SquareHoleDraftOpenIntent {
|
||||||
|
const {
|
||||||
|
item,
|
||||||
|
forceDraft = false,
|
||||||
|
activeSessionId,
|
||||||
|
hasActiveGenerationRunning,
|
||||||
|
isGenerationReady,
|
||||||
|
} = params;
|
||||||
|
const noticeKeys = buildSquareHoleDraftOpenNoticeKeys(item);
|
||||||
|
|
||||||
|
if (item.publicationStatus === 'published' && !forceDraft) {
|
||||||
|
return { type: 'open-published-detail', noticeKeys };
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceSessionId = normalizeDraftNoticeId(item.sourceSessionId);
|
||||||
|
if (!sourceSessionId) {
|
||||||
|
return {
|
||||||
|
type: 'missing-session',
|
||||||
|
noticeKeys,
|
||||||
|
errorMessage: '这份方洞挑战草稿缺少会话信息,请重新开始创作。',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceSessionId === activeSessionId && hasActiveGenerationRunning) {
|
||||||
|
return {
|
||||||
|
type: 'active-generation',
|
||||||
|
noticeKeys,
|
||||||
|
sourceSessionId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'restore-draft',
|
||||||
|
noticeKeys,
|
||||||
|
sourceSessionId,
|
||||||
|
shouldClearGenerationState: !isGenerationReady,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveVisualNovelDraftOpenIntent(params: {
|
||||||
|
item: VisualNovelWorkSummary;
|
||||||
|
forceDraft?: boolean;
|
||||||
|
activeSessionId?: string | null;
|
||||||
|
hasActiveGenerationRunning: boolean;
|
||||||
|
hasActiveSessionDraft: boolean;
|
||||||
|
}): VisualNovelDraftOpenIntent {
|
||||||
|
const {
|
||||||
|
item,
|
||||||
|
forceDraft = false,
|
||||||
|
activeSessionId,
|
||||||
|
hasActiveGenerationRunning,
|
||||||
|
hasActiveSessionDraft,
|
||||||
|
} = params;
|
||||||
|
const noticeKeys = buildVisualNovelDraftOpenNoticeKeys(item);
|
||||||
|
|
||||||
|
if (item.publishStatus === 'published' && !forceDraft) {
|
||||||
|
return { type: 'open-published-detail', noticeKeys };
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCurrentSession = item.profileId === activeSessionId;
|
||||||
|
if (isCurrentSession && hasActiveGenerationRunning) {
|
||||||
|
return {
|
||||||
|
type: 'active-generation',
|
||||||
|
noticeKeys,
|
||||||
|
profileId: item.profileId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCurrentSession && hasActiveSessionDraft) {
|
||||||
|
return {
|
||||||
|
type: 'current-result',
|
||||||
|
noticeKeys,
|
||||||
|
profileId: item.profileId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'load-detail',
|
||||||
|
noticeKeys,
|
||||||
|
profileId: item.profileId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function buildCreationWorkShelfRuntimeState(params: {
|
export function buildCreationWorkShelfRuntimeState(params: {
|
||||||
item: CreationWorkShelfItem;
|
item: CreationWorkShelfItem;
|
||||||
notices: DraftGenerationNoticeMap;
|
notices: DraftGenerationNoticeMap;
|
||||||
|
|||||||
Reference in New Issue
Block a user