diff --git a/.hermes/shared-memory/pitfalls.md b/.hermes/shared-memory/pitfalls.md index eac362e4..55c3369a 100644 --- a/.hermes/shared-memory/pitfalls.md +++ b/.hermes/shared-memory/pitfalls.md @@ -31,6 +31,14 @@ - 验证:`npm run test -- src/services/creationUrlState.test.ts src/routing/appPageRoutes.test.ts src/components/platform-entry/usePlatformCreationAgentFlowController.test.tsx`;手测生成页 / 结果页刷新仍恢复同一草稿,打开公开作品详情 URL 不带私有恢复参数。 - 关联:`src/services/creationUrlState.ts`、`src/routing/appPageRoutes.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 +## 草稿作品架打开结果页返回必须回草稿 Tab + +- 现象:从草稿 Tab 作品架点击已有草稿进入结果页后,点结果页返回会跳回创作 Tab 模板入口,用户需要重新切回草稿页才能继续找原草稿。 +- 原因:平台壳层只按结果页类型硬编码返回创作入口,没有记录本次创作流是从草稿作品架打开;如果来源标记没有在新建入口时重置,还可能污染下一条创作链路。 +- 处理:从作品架打开任一玩法草稿时标记返回目标为 `draft-shelf`;从创作 Tab 新建、打开模板或退出非草稿来源工作区时重置为 `create`;结果页返回和工作区退出统一消费这个返回目标,并在消费后复位。 +- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "puzzle draft result back button returns to draft hub when opened from shelf|agent draft result back button returns to draft hub without syncing result profile"`。 +- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 + ## 拼图生成页轮询不要绑展示 phase 或不稳定 setter - 现象:拼图创作进入生成中页后,`/api/runtime/puzzle/agent/sessions/{sessionId}` 会在 0.3 到 0.5 秒内被反复 GET,看起来像轮询风暴,而不是 3 秒一次的正常刷新。 diff --git a/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md b/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md index 5044d3fe..e3b799cf 100644 --- a/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md +++ b/docs/【玩法创作】平台入口与玩法链路-2026-05-15.md @@ -42,7 +42,8 @@ 4. 生成中作品在整卡上加等待遮罩,但不移除作品基础信息。 5. 生成中状态不能只存在前端内存 notice。后端作品摘要必须下发可恢复的 `generationStatus`;前端刷新或退出产品后,作品架优先用摘要状态恢复等待遮罩,本轮内存 notice 只作为即时反馈。 6. 点击 `generationStatus=generating` 的草稿卡必须恢复对应玩法的生成进度页,不能进入空白结果页或普通工作区;恢复生成页的 `startedAtMs` 使用进入生成页的当前时间,作品摘要 `updatedAt` 只用于排序和摘要展示,不参与假进度起算。 -7. 私有 generated 图片必须通过 `ResolvedAssetImage` / `/api/assets/read-url` 换签读取。 +7. 从草稿 Tab 作品架打开草稿工作区、生成页或结果页时,返回按钮必须回到草稿 Tab 的同一作品架语境;从创作 Tab 新建或直接进入创作链路时才回到创作 Tab。平台壳层需要显式记录本次创作流的返回来源,不能让结果页返回动作固定跳到创作入口。 +8. 私有 generated 图片必须通过 `ResolvedAssetImage` / `/api/assets/read-url` 换签读取。 发现 Tab、创作 Tab 与草稿 Tab 的页面根内容区不再套 `platform-page-stage` 外层全局卡片壳,让列表、筛选和玩法卡获得更宽的横向空间;推荐页和我的页仍按各自页面设计保留原有全局卡片口径。移动端“我的”页仍按顶部头像 / 昵称 / 陶泥号、会员横幅、三张统计卡、每日任务、五项常用功能宫格、设置入口、次级入口带和法律信息组织,但字号必须维持平台普通 UI 档位,不能因为窄屏把卡片标题、功能 label 或法律信息撑成展示级字号;最后一屏内容必须能在底部 dock 上方完整滚动露出,不得被固定底部导航遮挡。 diff --git a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx index c6359f6b..ef7d73b8 100644 --- a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx +++ b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx @@ -453,6 +453,7 @@ type PendingDraftShelfMap = Partial< Record > >; +type CreationFlowReturnTarget = 'create' | 'draft-shelf'; type Match3DBackgroundCompileTask = { session: Match3DAgentSessionSnapshot; payload: CreateMatch3DSessionRequest; @@ -3300,6 +3301,8 @@ export function PlatformEntryFlowShellImpl({ ); const handledInitialPublicWorkCodeRef = useRef(null); const selectionStageRef = useRef(selectionStage); + const creationFlowReturnTargetRef = + useRef('create'); const activeMatch3DGenerationSessionIdRef = useRef(null); const activePuzzleGenerationSessionIdRef = useRef(null); const [draftGenerationNotices, setDraftGenerationNotices] = @@ -3545,11 +3548,34 @@ export function PlatformEntryFlowShellImpl({ const enterDraftTab = useCallback(() => { setPlatformTab('saves'); }, [setPlatformTab]); + const markCreationFlowReturnToCreate = useCallback(() => { + creationFlowReturnTargetRef.current = 'create'; + }, []); + const markCreationFlowReturnToDraftShelf = useCallback(() => { + creationFlowReturnTargetRef.current = 'draft-shelf'; + }, []); + const returnToCreationFlowSource = useCallback(() => { + const returnTarget = creationFlowReturnTargetRef.current; + creationFlowReturnTargetRef.current = 'create'; + clearCreationUrlState(); + if (returnTarget === 'draft-shelf') { + enterDraftTab(); + } else { + enterCreateTab(); + } + selectionStageRef.current = 'platform'; + setSelectionStage('platform'); + }, [enterCreateTab, enterDraftTab, setSelectionStage]); + const shouldReturnToDraftShelf = useCallback( + () => creationFlowReturnTargetRef.current === 'draft-shelf', + [], + ); const returnToCreationCenterFromGeneration = useCallback(() => { + markCreationFlowReturnToCreate(); enterCreateTab(); selectionStageRef.current = 'platform'; setSelectionStage('platform'); - }, [enterCreateTab, setSelectionStage]); + }, [enterCreateTab, markCreationFlowReturnToCreate, setSelectionStage]); const isViewingMatch3DGeneration = useCallback((sessionId: string) => { return ( selectionStageRef.current === 'match3d-generating' && @@ -4717,9 +4743,15 @@ export function PlatformEntryFlowShellImpl({ handleStartNewGame(); } + markCreationFlowReturnToCreate(); sessionController.setCreationTypeError(null); return true; - }, [handleStartNewGame, hasSavedGame, sessionController]); + }, [ + handleStartNewGame, + hasSavedGame, + markCreationFlowReturnToCreate, + sessionController, + ]); const openCreationTypePicker = useCallback(() => { if (!prepareCreationLaunch()) { @@ -5905,11 +5937,13 @@ export function PlatformEntryFlowShellImpl({ ]); const openBigFishAgentWorkspace = useCallback(async () => { + markCreationFlowReturnToCreate(); setBigFishRun(null); await bigFishFlow.openWorkspace(); - }, [bigFishFlow]); + }, [bigFishFlow, markCreationFlowReturnToCreate]); const openSquareHoleAgentWorkspace = useCallback(async () => { + markCreationFlowReturnToCreate(); setSquareHoleSession(null); setSquareHoleProfile(null); setSquareHoleRun(null); @@ -5927,9 +5961,11 @@ export function PlatformEntryFlowShellImpl({ setSquareHoleSession, setStreamingSquareHoleReplyText, squareHoleFlow, + markCreationFlowReturnToCreate, ]); const openMatch3DWorkspace = useCallback(() => { + markCreationFlowReturnToCreate(); setMatch3DRun(null); setMatch3DProfile(null); setMatch3DRuntimeProfile(null); @@ -5946,9 +5982,11 @@ export function PlatformEntryFlowShellImpl({ setMatch3DError, setSelectionStage, setStreamingMatch3DReplyText, + markCreationFlowReturnToCreate, ]); const openJumpHopWorkspace = useCallback(() => { + markCreationFlowReturnToCreate(); setJumpHopError(null); setJumpHopSession(null); setJumpHopWork(null); @@ -5957,9 +5995,10 @@ export function PlatformEntryFlowShellImpl({ enterCreateTab(); setShowCreationTypeModal(false); setSelectionStage('jump-hop-workspace'); - }, [enterCreateTab, setSelectionStage]); + }, [enterCreateTab, markCreationFlowReturnToCreate, setSelectionStage]); const openWoodenFishWorkspace = useCallback(() => { + markCreationFlowReturnToCreate(); setWoodenFishError(null); setWoodenFishSession(null); setWoodenFishWork(null); @@ -5968,9 +6007,10 @@ export function PlatformEntryFlowShellImpl({ enterCreateTab(); setShowCreationTypeModal(false); setSelectionStage('wooden-fish-workspace'); - }, [enterCreateTab, setSelectionStage]); + }, [enterCreateTab, markCreationFlowReturnToCreate, setSelectionStage]); const openPuzzleWorkspace = useCallback(() => { + markCreationFlowReturnToCreate(); enterCreateTab(); setShowCreationTypeModal(false); setPuzzleCreationError(null); @@ -5981,9 +6021,11 @@ export function PlatformEntryFlowShellImpl({ setPuzzleCreationError, setPuzzleError, setSelectionStage, + markCreationFlowReturnToCreate, ]); const openBarkBattleWorkspace = useCallback(() => { + markCreationFlowReturnToCreate(); setBarkBattleDraftConfig(null); setBarkBattlePublishedConfig(null); setBarkBattleRuntimeMode('draft'); @@ -5995,15 +6037,17 @@ export function PlatformEntryFlowShellImpl({ setShowCreationTypeModal(false); selectionStageRef.current = 'bark-battle-workspace'; setSelectionStage('bark-battle-workspace'); - }, [enterCreateTab, setSelectionStage]); + }, [enterCreateTab, markCreationFlowReturnToCreate, setSelectionStage]); const openVisualNovelWorkspace = useCallback(() => { + markCreationFlowReturnToCreate(); enterCreateTab(); setShowCreationTypeModal(false); setVisualNovelError(null); setSelectionStage('visual-novel-agent-workspace'); }, [ enterCreateTab, + markCreationFlowReturnToCreate, setSelectionStage, setVisualNovelError, ]); @@ -6014,6 +6058,7 @@ export function PlatformEntryFlowShellImpl({ return; } + markCreationFlowReturnToCreate(); enterCreateTab(); setShowCreationTypeModal(false); setBabyObjectMatchError(null); @@ -6021,6 +6066,7 @@ export function PlatformEntryFlowShellImpl({ }, [ enterCreateTab, isBabyObjectMatchVisible, + markCreationFlowReturnToCreate, sessionController, setBabyObjectMatchError, setSelectionStage, @@ -6039,9 +6085,15 @@ export function PlatformEntryFlowShellImpl({ setIsCreativeAgentStreaming(false); setCreativeDraftEditError(null); setIsCreativeDraftEditBusy(false); + markCreationFlowReturnToCreate(); enterCreateTab(); setSelectionStage('platform'); - }, [creativeAgentSession, enterCreateTab, setSelectionStage]); + }, [ + creativeAgentSession, + enterCreateTab, + markCreationFlowReturnToCreate, + setSelectionStage, + ]); const openCreativeAgentWorkspace = useCallback(async () => { if (isCreativeAgentBusy || isCreativeAgentStreaming) { @@ -6049,6 +6101,7 @@ export function PlatformEntryFlowShellImpl({ } setPuzzleRun(null); + markCreationFlowReturnToCreate(); setPuzzleRuntimeAuthMode('default'); setPuzzleOperation(null); setPuzzleGenerationState(null); @@ -6077,6 +6130,7 @@ export function PlatformEntryFlowShellImpl({ enterCreateTab, isCreativeAgentBusy, isCreativeAgentStreaming, + markCreationFlowReturnToCreate, resolvePuzzleErrorMessage, setSelectionStage, ]); @@ -6922,9 +6976,9 @@ export function PlatformEntryFlowShellImpl({ setBigFishRuntimeStartedAt(null); setBigFishRuntimeReturnStage('platform'); setBigFishGenerationState(null); - clearCreationUrlState(); bigFishFlow.leaveFlow(); - }, [bigFishFlow]); + returnToCreationFlowSource(); + }, [bigFishFlow, returnToCreationFlowSource]); const leaveMatch3DFlow = useCallback(() => { setMatch3DRun(null); @@ -6932,17 +6986,17 @@ export function PlatformEntryFlowShellImpl({ setMatch3DFormDraftPayload(null); setMatch3DGenerationState(null); setMatch3DRuntimeReturnStage('match3d-result'); - clearCreationUrlState(); match3dFlow.leaveFlow(); - }, [match3dFlow, setMatch3DFormDraftPayload]); + returnToCreationFlowSource(); + }, [match3dFlow, returnToCreationFlowSource, setMatch3DFormDraftPayload]); const leaveSquareHoleFlow = useCallback(() => { setSquareHoleRun(null); setSquareHoleRuntimeReturnStage('square-hole-result'); setSquareHoleGenerationState(null); - clearCreationUrlState(); squareHoleFlow.leaveFlow(); - }, [squareHoleFlow]); + returnToCreationFlowSource(); + }, [returnToCreationFlowSource, squareHoleFlow]); const leaveJumpHopFlow = useCallback(() => { setJumpHopRun(null); @@ -6951,9 +7005,8 @@ export function PlatformEntryFlowShellImpl({ setJumpHopGenerationState(null); setJumpHopSession(null); setJumpHopError(null); - clearCreationUrlState(); - setSelectionStage('platform'); - }, [setSelectionStage]); + returnToCreationFlowSource(); + }, [returnToCreationFlowSource]); const leaveWoodenFishFlow = useCallback(() => { setWoodenFishRun(null); @@ -6962,9 +7015,8 @@ export function PlatformEntryFlowShellImpl({ setWoodenFishGenerationState(null); setWoodenFishSession(null); setWoodenFishError(null); - clearCreationUrlState(); - setSelectionStage('platform'); - }, [setSelectionStage]); + returnToCreationFlowSource(); + }, [returnToCreationFlowSource]); const createReadyJumpHopGenerationState = useCallback( (state: MiniGameDraftGenerationState) => @@ -6992,10 +7044,8 @@ export function PlatformEntryFlowShellImpl({ setBarkBattleError(null); setBarkBattleGenerationPartialFailed(false); setIsBarkBattleBusy(false); - clearCreationUrlState(); - selectionStageRef.current = 'platform'; - setSelectionStage('platform'); - }, [setSelectionStage]); + returnToCreationFlowSource(); + }, [returnToCreationFlowSource]); const createBarkBattleGeneratingDraft = useCallback( async (payload: BarkBattleConfigEditorPayload) => { @@ -7276,10 +7326,10 @@ export function PlatformEntryFlowShellImpl({ setActiveCreativeAgentSessionId(null); setCreativeDraftEditError(null); resetRecommendRuntimeSelection(); - clearCreationUrlState(); clearPuzzleRuntimeUrlState(); puzzleFlow.leaveFlow(); - }, [puzzleFlow, resetRecommendRuntimeSelection]); + returnToCreationFlowSource(); + }, [puzzleFlow, resetRecommendRuntimeSelection, returnToCreationFlowSource]); const leaveVisualNovelFlow = useCallback(() => { setVisualNovelWork(null); @@ -7288,9 +7338,9 @@ export function PlatformEntryFlowShellImpl({ setVisualNovelFormDraftPayload(null); setVisualNovelGenerationStartedAtMs(null); setVisualNovelGenerationPhase('generating'); - clearCreationUrlState(); visualNovelFlow.leaveFlow(); - }, [visualNovelFlow]); + returnToCreationFlowSource(); + }, [returnToCreationFlowSource, visualNovelFlow]); const leaveBabyObjectMatchFlow = useCallback(() => { setBabyObjectMatchDraft(null); @@ -7298,11 +7348,8 @@ export function PlatformEntryFlowShellImpl({ setBabyObjectMatchGenerationState(null); setBabyObjectMatchGenerationPhase('generating'); setBabyObjectMatchError(null); - clearCreationUrlState(); - enterCreateTab(); - selectionStageRef.current = 'platform'; - setSelectionStage('platform'); - }, [enterCreateTab, setSelectionStage]); + returnToCreationFlowSource(); + }, [returnToCreationFlowSource]); const saveBabyObjectMatchResultDraft = useCallback( async (draft: BabyObjectMatchDraft) => { @@ -10079,7 +10126,6 @@ export function PlatformEntryFlowShellImpl({ }, [activeRecommendRuntimeKind, setPuzzleError]); const leaveAgentWorkspace = useCallback(() => { - enterCreateTab(); sessionController.resetSessionViewState(); sessionController.setGeneratedCustomWorldProfile(null); autosaveCoordinator.resetAutoSaveTrackingToIdle(); @@ -10087,12 +10133,11 @@ export function PlatformEntryFlowShellImpl({ sessionController.activeAgentSessionId, null, ); - setSelectionStage('platform'); + returnToCreationFlowSource(); }, [ autosaveCoordinator, - enterCreateTab, + returnToCreationFlowSource, sessionController, - setSelectionStage, ]); const leaveAgentDraftGeneration = useCallback(() => { @@ -10112,13 +10157,11 @@ export function PlatformEntryFlowShellImpl({ autosaveCoordinator.resetAutoSaveTrackingToIdle(); sessionController.setCustomWorldGenerationViewSource(null); sessionController.setCustomWorldResultViewSource(null); - enterCreateTab(); - setSelectionStage('platform'); + returnToCreationFlowSource(); }, [ autosaveCoordinator, - enterCreateTab, + returnToCreationFlowSource, sessionController, - setSelectionStage, ]); const leaveCustomWorldResult = useCallback(() => { @@ -10127,12 +10170,18 @@ export function PlatformEntryFlowShellImpl({ autosaveCoordinator.resetAutoSaveTrackingToIdle(); sessionController.setCustomWorldGenerationViewSource(null); sessionController.setCustomWorldResultViewSource(null); + if (shouldReturnToDraftShelf()) { + returnToCreationFlowSource(); + return; + } setSelectionStage(selectedDetailEntry ? 'detail' : 'platform'); }, [ autosaveCoordinator, + returnToCreationFlowSource, selectedDetailEntry, sessionController, setSelectionStage, + shouldReturnToDraftShelf, ]); const handleStartSelectedWorld = useCallback(() => { @@ -14135,6 +14184,7 @@ export function PlatformEntryFlowShellImpl({ }} onOpenDraft={(item) => { runProtectedAction(() => { + markCreationFlowReturnToDraftShelf(); void detailNavigation.handleOpenCreationWork(item); }); }} @@ -14146,6 +14196,7 @@ export function PlatformEntryFlowShellImpl({ if (!matchedWork) { return; } + markCreationFlowReturnToDraftShelf(); void detailNavigation.handleOpenCreationWork(matchedWork); }); }} @@ -14160,6 +14211,7 @@ export function PlatformEntryFlowShellImpl({ isBigFishCreationVisible ? (item) => { runProtectedAction(() => { + markCreationFlowReturnToDraftShelf(); void openBigFishDraft(item); }); } @@ -14169,6 +14221,7 @@ export function PlatformEntryFlowShellImpl({ isJumpHopCreationVisible ? (item) => { runProtectedAction(() => { + markCreationFlowReturnToDraftShelf(); void openJumpHopDraft(item); }); } @@ -14185,6 +14238,7 @@ export function PlatformEntryFlowShellImpl({ match3dItems={match3dShelfItems} onOpenMatch3DDetail={(item) => { runProtectedAction(() => { + markCreationFlowReturnToDraftShelf(); void openMatch3DDraft(item); }); }} @@ -14198,6 +14252,7 @@ export function PlatformEntryFlowShellImpl({ isSquareHoleCreationVisible ? (item) => { runProtectedAction(() => { + markCreationFlowReturnToDraftShelf(); void openSquareHoleDraft(item); }); } @@ -14213,6 +14268,7 @@ export function PlatformEntryFlowShellImpl({ puzzleItems={puzzleShelfItems} onOpenPuzzleDetail={(item) => { runProtectedAction(() => { + markCreationFlowReturnToDraftShelf(); void openPuzzleDraft(item); }); }} @@ -14228,6 +14284,7 @@ export function PlatformEntryFlowShellImpl({ } onOpenBabyObjectMatchDetail={(item) => { runProtectedAction(() => { + markCreationFlowReturnToDraftShelf(); openBabyObjectMatchDraft(item); }); }} @@ -14237,12 +14294,14 @@ export function PlatformEntryFlowShellImpl({ barkBattleItems={barkBattleShelfItems} onOpenBarkBattleDetail={(item) => { runProtectedAction(() => { + markCreationFlowReturnToDraftShelf(); openBarkBattleDraft(item); }); }} visualNovelItems={visualNovelShelfItems} onOpenVisualNovelDetail={(item) => { runProtectedAction(() => { + markCreationFlowReturnToDraftShelf(); void openVisualNovelDraft(item); }); }} @@ -14691,6 +14750,10 @@ export function PlatformEntryFlowShellImpl({ isBusy={isBigFishBusy} error={bigFishError} onBack={() => { + if (shouldReturnToDraftShelf()) { + leaveBigFishFlow(); + return; + } setSelectionStage('big-fish-agent-workspace'); }} onDismissError={() => { @@ -14840,6 +14903,10 @@ export function PlatformEntryFlowShellImpl({ isBusy={isMatch3DBusy} error={match3dError} onBack={() => { + if (shouldReturnToDraftShelf()) { + leaveMatch3DFlow(); + return; + } returnToCreationCenterFromGeneration(); }} onSaved={(profile) => { @@ -15081,6 +15148,10 @@ export function PlatformEntryFlowShellImpl({ isBusy={isBabyObjectMatchBusy} error={babyObjectMatchError} onBack={() => { + if (shouldReturnToDraftShelf()) { + leaveBabyObjectMatchFlow(); + return; + } setSelectionStage('baby-object-match-workspace'); }} onSaveDraft={(draft) => { @@ -15266,6 +15337,10 @@ export function PlatformEntryFlowShellImpl({ isBusy={isSquareHoleBusy} error={squareHoleError} onBack={() => { + if (shouldReturnToDraftShelf()) { + leaveSquareHoleFlow(); + return; + } setSelectionStage('square-hole-agent-workspace'); }} onSaved={(profile) => { @@ -15903,6 +15978,10 @@ export function PlatformEntryFlowShellImpl({ isBusy={isVisualNovelBusy} error={visualNovelError} onBack={() => { + if (shouldReturnToDraftShelf()) { + leaveVisualNovelFlow(); + return; + } setSelectionStage('visual-novel-agent-workspace'); }} onSaveDraft={(draft) => { @@ -16181,6 +16260,10 @@ export function PlatformEntryFlowShellImpl({ : barkBattleError } onBack={() => { + if (shouldReturnToDraftShelf()) { + leaveBarkBattleFlow(); + return; + } enterCreateTab(); selectionStageRef.current = 'platform'; setSelectionStage('platform'); diff --git a/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx b/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx index baf9807c..6ad68ccb 100644 --- a/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx +++ b/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx @@ -324,6 +324,13 @@ function getPlatformTabPanel(tab: string) { return panel; } +async function findPlatformTabPanel(tab: string) { + await waitFor(() => { + expect(document.getElementById(`platform-tab-panel-${tab}`)).toBeTruthy(); + }); + return getPlatformTabPanel(tab); +} + const testCreationEntryConfig = { startCard: { title: '新建作品', @@ -6938,7 +6945,7 @@ test('match3d creation tab stays usable even when public galleries fail', async expect(match3dCreationClient.createSession).not.toHaveBeenCalled(); }); -test('puzzle draft result back button returns to creation hub', async () => { +test('puzzle draft result back button returns to draft hub when opened from shelf', async () => { const user = userEvent.setup(); vi.mocked(listPuzzleWorks).mockResolvedValue({ @@ -6979,10 +6986,16 @@ test('puzzle draft result back button returns to creation hub', async () => { await user.click(screen.getByRole('button', { name: '返回' })); + const draftPanel = await findPlatformTabPanel('saves'); + await waitFor(() => { + expect(draftPanel.getAttribute('aria-hidden')).toBe('false'); + }); expect( - await screen.findByRole('tablist', { name: '玩法模板分类' }), + within(draftPanel).getByRole('tablist', { name: '作品筛选' }), ).toBeTruthy(); - expect(await screen.findByText('拼图工作区:missing-session')).toBeTruthy(); + expect(within(draftPanel).getByText('雨夜猫塔')).toBeTruthy(); + expect(getPlatformTabPanel('create').getAttribute('aria-hidden')).toBe('true'); + expect(screen.queryByText('拼图工作区:missing-session')).toBeNull(); expect( screen.queryByText('雨夜里有一只会发光的猫站在遗迹台阶上。'), ).toBeNull(); @@ -8996,7 +9009,7 @@ test('agent result view does not keep legacy publish blockers when preview uses expect((actionButton as HTMLButtonElement).disabled).toBe(false); }); -test('agent draft result back button returns to creation hub without syncing result profile', async () => { +test('agent draft result back button returns to draft hub without syncing result profile', async () => { const user = userEvent.setup(); const resultSession = { @@ -9153,22 +9166,32 @@ test('agent draft result back button returns to creation hub without syncing res }, { timeout: 2500 }, ); + const syncCallsBeforeBack = vi + .mocked(executeRpgCreationAction) + .mock.calls.filter( + ([sessionId, payload]) => + sessionId === 'custom-world-agent-session-1' && + payload?.action === 'sync_result_profile', + ).length; await user.click(screen.getByRole('button', { name: /返回创作/u })); + const draftPanel = await findPlatformTabPanel('saves'); await waitFor(() => { - expect(screen.getByRole('tablist', { name: '玩法模板分类' })).toBeTruthy(); + expect(draftPanel.getAttribute('aria-hidden')).toBe('false'); }); + expect(within(draftPanel).getByRole('tablist', { name: '作品筛选' })).toBeTruthy(); + expect(getPlatformTabPanel('create').getAttribute('aria-hidden')).toBe('true'); expect( vi .mocked(executeRpgCreationAction) - .mock.calls.some( + .mock.calls.filter( ([sessionId, payload]) => sessionId === 'custom-world-agent-session-1' && payload?.action === 'sync_result_profile', - ), - ).toBe(false); + ).length, + ).toBe(syncCallsBeforeBack); expect(screen.queryByText('世界档案')).toBeNull(); });