Merge pull request 'fix: return draft results to shelf' (#37) from codex/fix-draft-result-back-target into master
Reviewed-on: #37
This commit was merged in pull request #37.
This commit is contained in:
@@ -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 秒一次的正常刷新。
|
||||
|
||||
@@ -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 上方完整滚动露出,不得被固定底部导航遮挡。
|
||||
|
||||
|
||||
@@ -462,6 +462,7 @@ type PendingDraftShelfMap = Partial<
|
||||
Record<string, PendingDraftShelfState>
|
||||
>
|
||||
>;
|
||||
type CreationFlowReturnTarget = 'create' | 'draft-shelf';
|
||||
type Match3DBackgroundCompileTask = {
|
||||
session: Match3DAgentSessionSnapshot;
|
||||
payload: CreateMatch3DSessionRequest;
|
||||
@@ -3313,6 +3314,8 @@ export function PlatformEntryFlowShellImpl({
|
||||
);
|
||||
const handledInitialPublicWorkCodeRef = useRef<string | null>(null);
|
||||
const selectionStageRef = useRef(selectionStage);
|
||||
const creationFlowReturnTargetRef =
|
||||
useRef<CreationFlowReturnTarget>('create');
|
||||
const activeMatch3DGenerationSessionIdRef = useRef<string | null>(null);
|
||||
const activePuzzleGenerationSessionIdRef = useRef<string | null>(null);
|
||||
const [draftGenerationNotices, setDraftGenerationNotices] =
|
||||
@@ -3565,11 +3568,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' &&
|
||||
@@ -4745,9 +4771,15 @@ export function PlatformEntryFlowShellImpl({
|
||||
handleStartNewGame();
|
||||
}
|
||||
|
||||
markCreationFlowReturnToCreate();
|
||||
sessionController.setCreationTypeError(null);
|
||||
return true;
|
||||
}, [handleStartNewGame, hasSavedGame, sessionController]);
|
||||
}, [
|
||||
handleStartNewGame,
|
||||
hasSavedGame,
|
||||
markCreationFlowReturnToCreate,
|
||||
sessionController,
|
||||
]);
|
||||
|
||||
const openCreationTypePicker = useCallback(() => {
|
||||
if (!prepareCreationLaunch()) {
|
||||
@@ -5948,11 +5980,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);
|
||||
@@ -5970,9 +6004,11 @@ export function PlatformEntryFlowShellImpl({
|
||||
setSquareHoleSession,
|
||||
setStreamingSquareHoleReplyText,
|
||||
squareHoleFlow,
|
||||
markCreationFlowReturnToCreate,
|
||||
]);
|
||||
|
||||
const openMatch3DWorkspace = useCallback(() => {
|
||||
markCreationFlowReturnToCreate();
|
||||
setMatch3DRun(null);
|
||||
setMatch3DProfile(null);
|
||||
setMatch3DRuntimeProfile(null);
|
||||
@@ -5989,9 +6025,11 @@ export function PlatformEntryFlowShellImpl({
|
||||
setMatch3DError,
|
||||
setSelectionStage,
|
||||
setStreamingMatch3DReplyText,
|
||||
markCreationFlowReturnToCreate,
|
||||
]);
|
||||
|
||||
const openJumpHopWorkspace = useCallback(() => {
|
||||
markCreationFlowReturnToCreate();
|
||||
setJumpHopError(null);
|
||||
setJumpHopSession(null);
|
||||
setJumpHopWork(null);
|
||||
@@ -6000,9 +6038,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);
|
||||
@@ -6011,9 +6050,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);
|
||||
@@ -6024,9 +6064,11 @@ export function PlatformEntryFlowShellImpl({
|
||||
setPuzzleCreationError,
|
||||
setPuzzleError,
|
||||
setSelectionStage,
|
||||
markCreationFlowReturnToCreate,
|
||||
]);
|
||||
|
||||
const openBarkBattleWorkspace = useCallback(() => {
|
||||
markCreationFlowReturnToCreate();
|
||||
setBarkBattleDraftConfig(null);
|
||||
setBarkBattlePublishedConfig(null);
|
||||
setBarkBattleRuntimeMode('draft');
|
||||
@@ -6038,15 +6080,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,
|
||||
]);
|
||||
@@ -6057,6 +6101,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
return;
|
||||
}
|
||||
|
||||
markCreationFlowReturnToCreate();
|
||||
enterCreateTab();
|
||||
setShowCreationTypeModal(false);
|
||||
setBabyObjectMatchError(null);
|
||||
@@ -6064,6 +6109,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
}, [
|
||||
enterCreateTab,
|
||||
isBabyObjectMatchVisible,
|
||||
markCreationFlowReturnToCreate,
|
||||
sessionController,
|
||||
setBabyObjectMatchError,
|
||||
setSelectionStage,
|
||||
@@ -6082,9 +6128,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) {
|
||||
@@ -6092,6 +6144,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
}
|
||||
|
||||
setPuzzleRun(null);
|
||||
markCreationFlowReturnToCreate();
|
||||
setPuzzleRuntimeAuthMode('default');
|
||||
setPuzzleOperation(null);
|
||||
setPuzzleGenerationState(null);
|
||||
@@ -6120,6 +6173,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
enterCreateTab,
|
||||
isCreativeAgentBusy,
|
||||
isCreativeAgentStreaming,
|
||||
markCreationFlowReturnToCreate,
|
||||
resolvePuzzleErrorMessage,
|
||||
setSelectionStage,
|
||||
]);
|
||||
@@ -6965,9 +7019,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
setBigFishRuntimeStartedAt(null);
|
||||
setBigFishRuntimeReturnStage('platform');
|
||||
setBigFishGenerationState(null);
|
||||
clearCreationUrlState();
|
||||
bigFishFlow.leaveFlow();
|
||||
}, [bigFishFlow]);
|
||||
returnToCreationFlowSource();
|
||||
}, [bigFishFlow, returnToCreationFlowSource]);
|
||||
|
||||
const leaveMatch3DFlow = useCallback(() => {
|
||||
setMatch3DRun(null);
|
||||
@@ -6975,17 +7029,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);
|
||||
@@ -6994,9 +7048,8 @@ export function PlatformEntryFlowShellImpl({
|
||||
setJumpHopGenerationState(null);
|
||||
setJumpHopSession(null);
|
||||
setJumpHopError(null);
|
||||
clearCreationUrlState();
|
||||
setSelectionStage('platform');
|
||||
}, [setSelectionStage]);
|
||||
returnToCreationFlowSource();
|
||||
}, [returnToCreationFlowSource]);
|
||||
|
||||
const leaveWoodenFishFlow = useCallback(() => {
|
||||
setWoodenFishRun(null);
|
||||
@@ -7005,9 +7058,8 @@ export function PlatformEntryFlowShellImpl({
|
||||
setWoodenFishGenerationState(null);
|
||||
setWoodenFishSession(null);
|
||||
setWoodenFishError(null);
|
||||
clearCreationUrlState();
|
||||
setSelectionStage('platform');
|
||||
}, [setSelectionStage]);
|
||||
returnToCreationFlowSource();
|
||||
}, [returnToCreationFlowSource]);
|
||||
|
||||
const createReadyJumpHopGenerationState = useCallback(
|
||||
(state: MiniGameDraftGenerationState) =>
|
||||
@@ -7035,10 +7087,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) => {
|
||||
@@ -7319,10 +7369,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
setActiveCreativeAgentSessionId(null);
|
||||
setCreativeDraftEditError(null);
|
||||
resetRecommendRuntimeSelection();
|
||||
clearCreationUrlState();
|
||||
clearPuzzleRuntimeUrlState();
|
||||
puzzleFlow.leaveFlow();
|
||||
}, [puzzleFlow, resetRecommendRuntimeSelection]);
|
||||
returnToCreationFlowSource();
|
||||
}, [puzzleFlow, resetRecommendRuntimeSelection, returnToCreationFlowSource]);
|
||||
|
||||
const leaveVisualNovelFlow = useCallback(() => {
|
||||
setVisualNovelWork(null);
|
||||
@@ -7331,9 +7381,9 @@ export function PlatformEntryFlowShellImpl({
|
||||
setVisualNovelFormDraftPayload(null);
|
||||
setVisualNovelGenerationStartedAtMs(null);
|
||||
setVisualNovelGenerationPhase('generating');
|
||||
clearCreationUrlState();
|
||||
visualNovelFlow.leaveFlow();
|
||||
}, [visualNovelFlow]);
|
||||
returnToCreationFlowSource();
|
||||
}, [returnToCreationFlowSource, visualNovelFlow]);
|
||||
|
||||
const leaveBabyObjectMatchFlow = useCallback(() => {
|
||||
setBabyObjectMatchDraft(null);
|
||||
@@ -7341,11 +7391,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) => {
|
||||
@@ -10172,7 +10219,6 @@ export function PlatformEntryFlowShellImpl({
|
||||
}, [activeRecommendRuntimeKind, setPuzzleError]);
|
||||
|
||||
const leaveAgentWorkspace = useCallback(() => {
|
||||
enterCreateTab();
|
||||
sessionController.resetSessionViewState();
|
||||
sessionController.setGeneratedCustomWorldProfile(null);
|
||||
autosaveCoordinator.resetAutoSaveTrackingToIdle();
|
||||
@@ -10180,12 +10226,11 @@ export function PlatformEntryFlowShellImpl({
|
||||
sessionController.activeAgentSessionId,
|
||||
null,
|
||||
);
|
||||
setSelectionStage('platform');
|
||||
returnToCreationFlowSource();
|
||||
}, [
|
||||
autosaveCoordinator,
|
||||
enterCreateTab,
|
||||
returnToCreationFlowSource,
|
||||
sessionController,
|
||||
setSelectionStage,
|
||||
]);
|
||||
|
||||
const leaveAgentDraftGeneration = useCallback(() => {
|
||||
@@ -10205,13 +10250,11 @@ export function PlatformEntryFlowShellImpl({
|
||||
autosaveCoordinator.resetAutoSaveTrackingToIdle();
|
||||
sessionController.setCustomWorldGenerationViewSource(null);
|
||||
sessionController.setCustomWorldResultViewSource(null);
|
||||
enterCreateTab();
|
||||
setSelectionStage('platform');
|
||||
returnToCreationFlowSource();
|
||||
}, [
|
||||
autosaveCoordinator,
|
||||
enterCreateTab,
|
||||
returnToCreationFlowSource,
|
||||
sessionController,
|
||||
setSelectionStage,
|
||||
]);
|
||||
|
||||
const leaveCustomWorldResult = useCallback(() => {
|
||||
@@ -10220,12 +10263,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(() => {
|
||||
@@ -14242,6 +14291,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
}}
|
||||
onOpenDraft={(item) => {
|
||||
runProtectedAction(() => {
|
||||
markCreationFlowReturnToDraftShelf();
|
||||
void detailNavigation.handleOpenCreationWork(item);
|
||||
});
|
||||
}}
|
||||
@@ -14253,6 +14303,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
if (!matchedWork) {
|
||||
return;
|
||||
}
|
||||
markCreationFlowReturnToDraftShelf();
|
||||
void detailNavigation.handleOpenCreationWork(matchedWork);
|
||||
});
|
||||
}}
|
||||
@@ -14267,6 +14318,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
isBigFishCreationVisible
|
||||
? (item) => {
|
||||
runProtectedAction(() => {
|
||||
markCreationFlowReturnToDraftShelf();
|
||||
void openBigFishDraft(item);
|
||||
});
|
||||
}
|
||||
@@ -14276,6 +14328,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
isJumpHopCreationVisible
|
||||
? (item) => {
|
||||
runProtectedAction(() => {
|
||||
markCreationFlowReturnToDraftShelf();
|
||||
void openJumpHopDraft(item);
|
||||
});
|
||||
}
|
||||
@@ -14292,6 +14345,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
match3dItems={match3dShelfItems}
|
||||
onOpenMatch3DDetail={(item) => {
|
||||
runProtectedAction(() => {
|
||||
markCreationFlowReturnToDraftShelf();
|
||||
void openMatch3DDraft(item);
|
||||
});
|
||||
}}
|
||||
@@ -14305,6 +14359,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
isSquareHoleCreationVisible
|
||||
? (item) => {
|
||||
runProtectedAction(() => {
|
||||
markCreationFlowReturnToDraftShelf();
|
||||
void openSquareHoleDraft(item);
|
||||
});
|
||||
}
|
||||
@@ -14320,6 +14375,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
puzzleItems={puzzleShelfItems}
|
||||
onOpenPuzzleDetail={(item) => {
|
||||
runProtectedAction(() => {
|
||||
markCreationFlowReturnToDraftShelf();
|
||||
void openPuzzleDraft(item);
|
||||
});
|
||||
}}
|
||||
@@ -14335,6 +14391,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
}
|
||||
onOpenBabyObjectMatchDetail={(item) => {
|
||||
runProtectedAction(() => {
|
||||
markCreationFlowReturnToDraftShelf();
|
||||
openBabyObjectMatchDraft(item);
|
||||
});
|
||||
}}
|
||||
@@ -14344,12 +14401,14 @@ export function PlatformEntryFlowShellImpl({
|
||||
barkBattleItems={barkBattleShelfItems}
|
||||
onOpenBarkBattleDetail={(item) => {
|
||||
runProtectedAction(() => {
|
||||
markCreationFlowReturnToDraftShelf();
|
||||
openBarkBattleDraft(item);
|
||||
});
|
||||
}}
|
||||
visualNovelItems={visualNovelShelfItems}
|
||||
onOpenVisualNovelDetail={(item) => {
|
||||
runProtectedAction(() => {
|
||||
markCreationFlowReturnToDraftShelf();
|
||||
void openVisualNovelDraft(item);
|
||||
});
|
||||
}}
|
||||
@@ -14799,6 +14858,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
isBusy={isBigFishBusy}
|
||||
error={bigFishError}
|
||||
onBack={() => {
|
||||
if (shouldReturnToDraftShelf()) {
|
||||
leaveBigFishFlow();
|
||||
return;
|
||||
}
|
||||
setSelectionStage('big-fish-agent-workspace');
|
||||
}}
|
||||
onDismissError={() => {
|
||||
@@ -14948,6 +15011,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
isBusy={isMatch3DBusy}
|
||||
error={match3dError}
|
||||
onBack={() => {
|
||||
if (shouldReturnToDraftShelf()) {
|
||||
leaveMatch3DFlow();
|
||||
return;
|
||||
}
|
||||
returnToCreationCenterFromGeneration();
|
||||
}}
|
||||
onSaved={(profile) => {
|
||||
@@ -15197,6 +15264,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
isBusy={isBabyObjectMatchBusy}
|
||||
error={babyObjectMatchError}
|
||||
onBack={() => {
|
||||
if (shouldReturnToDraftShelf()) {
|
||||
leaveBabyObjectMatchFlow();
|
||||
return;
|
||||
}
|
||||
setSelectionStage('baby-object-match-workspace');
|
||||
}}
|
||||
onSaveDraft={(draft) => {
|
||||
@@ -15382,6 +15453,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
isBusy={isSquareHoleBusy}
|
||||
error={squareHoleError}
|
||||
onBack={() => {
|
||||
if (shouldReturnToDraftShelf()) {
|
||||
leaveSquareHoleFlow();
|
||||
return;
|
||||
}
|
||||
setSelectionStage('square-hole-agent-workspace');
|
||||
}}
|
||||
onSaved={(profile) => {
|
||||
@@ -16019,6 +16094,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
isBusy={isVisualNovelBusy}
|
||||
error={visualNovelError}
|
||||
onBack={() => {
|
||||
if (shouldReturnToDraftShelf()) {
|
||||
leaveVisualNovelFlow();
|
||||
return;
|
||||
}
|
||||
setSelectionStage('visual-novel-agent-workspace');
|
||||
}}
|
||||
onSaveDraft={(draft) => {
|
||||
@@ -16297,6 +16376,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
: barkBattleError
|
||||
}
|
||||
onBack={() => {
|
||||
if (shouldReturnToDraftShelf()) {
|
||||
leaveBarkBattleFlow();
|
||||
return;
|
||||
}
|
||||
enterCreateTab();
|
||||
selectionStageRef.current = 'platform';
|
||||
setSelectionStage('platform');
|
||||
|
||||
@@ -336,6 +336,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: '新建作品',
|
||||
@@ -4688,7 +4695,7 @@ test('match3d result trial passes generated 2D image views into first runtime mo
|
||||
});
|
||||
});
|
||||
|
||||
test('match3d result back returns to platform creation page', async () => {
|
||||
test('match3d result back returns to draft hub when opened from shelf', async () => {
|
||||
const user = userEvent.setup();
|
||||
const match3dDraftWork: Match3DWorkSummary = {
|
||||
workId: 'match3d-work-back-1',
|
||||
@@ -4742,9 +4749,15 @@ test('match3d result back returns to platform creation page', async () => {
|
||||
expect(await screen.findByText('抓大鹅结果页')).toBeTruthy();
|
||||
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(within(draftPanel).getByText('自动试玩抓大鹅')).toBeTruthy();
|
||||
expect(getPlatformTabPanel('create').getAttribute('aria-hidden')).toBe('true');
|
||||
expect(screen.queryByText('抓大鹅结果页')).toBeNull();
|
||||
});
|
||||
|
||||
@@ -5153,8 +5166,10 @@ test('completed baby object match draft viewed immediately does not keep unread
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('宝贝识物结果页')).toBeNull();
|
||||
});
|
||||
await user.click(await screen.findByRole('button', { name: '返回' }));
|
||||
await openDraftHub(user);
|
||||
const reopenedDraftPanel = await findPlatformTabPanel('saves');
|
||||
await waitFor(() => {
|
||||
expect(reopenedDraftPanel.getAttribute('aria-hidden')).toBe('false');
|
||||
});
|
||||
expect(screen.queryByLabelText('新生成完成')).toBeNull();
|
||||
});
|
||||
|
||||
@@ -5205,8 +5220,10 @@ test('completed baby object match draft shows unread marker after leaving genera
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('宝贝识物结果页')).toBeNull();
|
||||
});
|
||||
await user.click(await screen.findByRole('button', { name: '返回' }));
|
||||
await openDraftHub(user);
|
||||
const draftPanel = await findPlatformTabPanel('saves');
|
||||
await waitFor(() => {
|
||||
expect(draftPanel.getAttribute('aria-hidden')).toBe('false');
|
||||
});
|
||||
expect(screen.queryByLabelText('新生成完成')).toBeNull();
|
||||
});
|
||||
|
||||
@@ -7301,7 +7318,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({
|
||||
@@ -7342,10 +7359,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 findCreationTypeButton('拼图')).toBeTruthy();
|
||||
expect(within(draftPanel).getByText('雨夜猫塔')).toBeTruthy();
|
||||
expect(getPlatformTabPanel('create').getAttribute('aria-hidden')).toBe('true');
|
||||
expect(screen.queryByText('拼图工作区:missing-session')).toBeNull();
|
||||
expect(
|
||||
screen.queryByText('雨夜里有一只会发光的猫站在遗迹台阶上。'),
|
||||
).toBeNull();
|
||||
@@ -7725,7 +7748,10 @@ test('formal puzzle runtime uses frontend move merge logic and backend leaderboa
|
||||
await user.click(within(dialog).getByRole('button', { name: '下一关' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(advancePuzzleNextLevel).toHaveBeenCalledWith(clearedFirstLevel.runId);
|
||||
expect(advancePuzzleNextLevel).toHaveBeenCalledWith(
|
||||
clearedFirstLevel.runId,
|
||||
{},
|
||||
);
|
||||
});
|
||||
expect(
|
||||
(
|
||||
@@ -9406,7 +9432,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 = {
|
||||
@@ -9563,22 +9589,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();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user