diff --git a/.hermes/shared-memory/decision-log.md b/.hermes/shared-memory/decision-log.md index 427a7842..0f6852e8 100644 --- a/.hermes/shared-memory/decision-log.md +++ b/.hermes/shared-memory/decision-log.md @@ -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 收口 - 背景:`resolvePlatformPublicCodeSearchPlan(...)` 已收口公开搜索顺序,但 `PlatformEntryFlowShellImpl.tsx` 仍内联 RPG by-code DTO 构造,以及拼图、大鱼吃小鱼、跳一跳、敲木鱼、宝贝识物、抓大鹅、方洞挑战、视觉小说和汪汪声浪的 `isSame*PublicWorkCode` 匹配、公开可见性过滤与详情卡映射。 diff --git a/docs/README.md b/docs/README.md index b51c6193..00c2f843 100644 --- a/docs/README.md +++ b/docs/README.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)。 -平台入口创作生成通知、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)。 diff --git a/docs/technical/【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md b/docs/technical/【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md index 12270520..da131994 100644 --- a/docs/technical/【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md +++ b/docs/technical/【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md @@ -15,10 +15,10 @@ - `buildCreationWorkShelfRuntimeState({ item, notices, pendingShelfItems })`:统一输出 `CreationWorkShelfRuntimeState`,处理失败覆盖、拼图空标题 `拼图草稿` 兜底、summary 占位覆盖、生成中遮罩和 ready 未读点。 - `collectVisibleDraftNoticeKeys(...)` / `hasUnreadDraftGenerationUpdates(...)`:统一草稿 Tab 顶部未读点规则。 - `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**。 -`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` 匹配回填。 - 失败 notice 优先级高于持久化 generating,且可通过 pending metadata 提供更具体 summary;否则回退玩法默认失败摘要。 - 已有封面的拼图草稿即使局部关卡仍在后台生成,也不得被整卡遮罩为不可打开的生成中状态。 -- 拼图 / 抓大鹅草稿打开 intent 只返回纯计划与 notice keys,不创建失败生成态、不请求详情、不写 stage;这些仍由壳层 Adapter 执行。 +- 草稿打开 intent 只返回纯计划、notice keys 与必要稳定 ID,不创建失败生成态、不请求详情、不写 stage;这些仍由壳层 Adapter 执行。 - 本 **Module** 不做网络请求、路由切换、弹窗副作用或 React state 写入,只保留纯 **Implementation**,以提高 **Depth**、**Leverage** 与 **Locality**。 ## 验证 diff --git a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx index 21bb578c..a76165f2 100644 --- a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx +++ b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx @@ -447,8 +447,11 @@ import { type PendingDraftShelfKind, type PendingDraftShelfMap, type PendingDraftShelfMetadata, + resolveBigFishDraftOpenIntent, resolveMatch3DDraftOpenIntent, resolvePuzzleDraftOpenIntent, + resolveSquareHoleDraftOpenIntent, + resolveVisualNovelDraftOpenIntent, } from './platformDraftGenerationShelfModel'; import { canExposePublicWork, @@ -10685,42 +10688,42 @@ export function PlatformEntryFlowShellImpl({ item: SquareHoleWorkSummary, options: { forceDraft?: boolean } = {}, ) => { + const openIntent = resolveSquareHoleDraftOpenIntent({ + item, + forceDraft: options.forceDraft, + activeSessionId: squareHoleSession?.sessionId, + hasActiveGenerationRunning: isMiniGameDraftGenerating( + squareHoleGenerationState, + ), + isGenerationReady: isMiniGameDraftReady(squareHoleGenerationState), + }); setSquareHoleRun(null); setSquareHoleError(null); setSquareHoleProfile(null); - markDraftNoticeSeen( - collectDraftNoticeKeys('square-hole', [ - item.workId, - item.profileId, - item.sourceSessionId, - ]), - ); + markDraftNoticeSeen(openIntent.noticeKeys); - if (item.publicationStatus === 'published' && !options.forceDraft) { + if (openIntent.type === 'open-published-detail') { openPublicWorkDetail(mapSquareHoleWorkToPublicWorkDetail(item)); return; } - if (!item.sourceSessionId?.trim()) { - setSquareHoleError('这份方洞挑战草稿缺少会话信息,请重新开始创作。'); + if (openIntent.type === 'missing-session') { + setSquareHoleError(openIntent.errorMessage); return; } - if ( - item.sourceSessionId === squareHoleSession?.sessionId && - isMiniGameDraftGenerating(squareHoleGenerationState) - ) { + if (openIntent.type === 'active-generation') { enterCreateTab(); selectionStageRef.current = 'square-hole-generating'; setSelectionStage('square-hole-generating'); return; } - if (!isMiniGameDraftReady(squareHoleGenerationState)) { + if (openIntent.shouldClearGenerationState) { setSquareHoleGenerationState(null); } const restoredSession = await squareHoleFlow.restoreDraft( - item.sourceSessionId, + openIntent.sourceSessionId, ); if (!restoredSession) { await refreshSquareHoleShelf().catch(() => undefined); @@ -10756,21 +10759,23 @@ export function PlatformEntryFlowShellImpl({ const openBigFishDraft = useCallback( async (item: BigFishWorkSummary) => { + const openIntent = resolveBigFishDraftOpenIntent({ + item, + activeSessionId: bigFishSession?.sessionId, + hasActiveGenerationRunning: isMiniGameDraftGenerating( + bigFishGenerationState, + ), + }); setBigFishRun(null); - markDraftNoticeSeen( - collectDraftNoticeKeys('big-fish', [item.workId, item.sourceSessionId]), - ); - if ( - item.sourceSessionId === bigFishSession?.sessionId && - isMiniGameDraftGenerating(bigFishGenerationState) - ) { + markDraftNoticeSeen(openIntent.noticeKeys); + if (openIntent.type === 'active-generation') { enterCreateTab(); selectionStageRef.current = 'big-fish-generating'; setSelectionStage('big-fish-generating'); return; } const restoredSession = await bigFishFlow.restoreDraft( - item.sourceSessionId, + openIntent.sourceSessionId, ); if (!restoredSession) { await refreshBigFishShelf().catch(() => undefined); @@ -10823,27 +10828,27 @@ export function PlatformEntryFlowShellImpl({ item: VisualNovelWorkSummary, 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)); return; } - markDraftNoticeSeen( - collectDraftNoticeKeys('visual-novel', [item.profileId]), - ); - if ( - item.profileId === visualNovelSession?.sessionId && - visualNovelGenerationPhase === 'generating' - ) { + markDraftNoticeSeen(openIntent.noticeKeys); + if (openIntent.type === 'active-generation') { enterCreateTab(); selectionStageRef.current = 'visual-novel-generating'; setSelectionStage('visual-novel-generating'); return; } - if ( - item.profileId === visualNovelSession?.sessionId && - visualNovelSession.draft - ) { + if (openIntent.type === 'current-result') { enterCreateTab(); setSelectionStage('visual-novel-result'); return; @@ -10856,7 +10861,7 @@ export function PlatformEntryFlowShellImpl({ setIsVisualNovelBusy(true); try { - const { work } = await getVisualNovelWorkDetail(item.profileId); + const { work } = await getVisualNovelWorkDetail(openIntent.profileId); setVisualNovelWork(work); setVisualNovelSession(buildVisualNovelSessionFromWorkDetail(work)); enterCreateTab(); diff --git a/src/components/platform-entry/platformDraftGenerationShelfModel.test.ts b/src/components/platform-entry/platformDraftGenerationShelfModel.test.ts index f01e1195..156d2196 100644 --- a/src/components/platform-entry/platformDraftGenerationShelfModel.test.ts +++ b/src/components/platform-entry/platformDraftGenerationShelfModel.test.ts @@ -3,6 +3,8 @@ import { describe, expect, test } from 'vitest'; import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary'; import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks'; 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 { buildCreationWorkShelfRuntimeState, @@ -16,8 +18,11 @@ import { hasUnreadDraftGenerationUpdates, mergeBigFishWorkSummary, mergePuzzleWorkSummary, + resolveBigFishDraftOpenIntent, resolveMatch3DDraftOpenIntent, resolvePuzzleDraftOpenIntent, + resolveSquareHoleDraftOpenIntent, + resolveVisualNovelDraftOpenIntent, } from './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', () => { const pending = buildPendingPuzzleWorks( { @@ -425,3 +555,52 @@ function buildBigFishWork( ...overrides, }; } + +function buildSquareHoleWork( + overrides: Partial = {}, +): 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 { + 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, + }; +} diff --git a/src/components/platform-entry/platformDraftGenerationShelfModel.ts b/src/components/platform-entry/platformDraftGenerationShelfModel.ts index 62094b63..dc5b5220 100644 --- a/src/components/platform-entry/platformDraftGenerationShelfModel.ts +++ b/src/components/platform-entry/platformDraftGenerationShelfModel.ts @@ -148,6 +148,61 @@ export type Match3DDraftOpenIntent = 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( kind: CreationWorkShelfKind, 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: { item: PuzzleWorkSummary; notices: DraftGenerationNoticeMap; @@ -628,6 +706,117 @@ export function resolveMatch3DDraftOpenIntent(params: { 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: { item: CreationWorkShelfItem; notices: DraftGenerationNoticeMap;