refactor: 收口缺失创作状态回退
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
- 背景:平台入口在受保护数据失效后会清空当前用户私有作品、草稿、运行态和生成状态,但哪些 `SelectionStage` 可保留、哪些必须回首页曾以内联长否定串散在 `PlatformEntryFlowShellImpl.tsx`。
|
- 背景:平台入口在受保护数据失效后会清空当前用户私有作品、草稿、运行态和生成状态,但哪些 `SelectionStage` 可保留、哪些必须回首页曾以内联长否定串散在 `PlatformEntryFlowShellImpl.tsx`。
|
||||||
- 决策:新增 `src/components/platform-entry/platformSelectionStageModel.ts`,以 `resolveSelectionStageAfterProtectedDataLoss(stage)` 收口受保护数据失效后的 stage 去留判定。模型内部使用 `satisfies Record<SelectionStage, boolean>` 全量分类,新增 stage 时必须明确保留或回首页。壳层仍负责检测权限变化、清 state 和调用 `setSelectionStage`。
|
- 决策:新增 `src/components/platform-entry/platformSelectionStageModel.ts`,以 `resolveSelectionStageAfterProtectedDataLoss(stage)` 收口受保护数据失效后的 stage 去留判定。模型内部使用 `satisfies Record<SelectionStage, boolean>` 全量分类,新增 stage 时必须明确保留或回首页。壳层仍负责检测权限变化、清 state 和调用 `setSelectionStage`。
|
||||||
|
- 追加决策:缺失草稿 / 作品 / run 时的阶段回退也归入 `platformSelectionStageModel.ts`,由 `resolveSelectionStageAfterMissingCreationState(params)` 统一判断 big-fish、match3d、square-hole、visual-novel 和 baby-object-match 的 result / runtime / gallery-detail 是否还能被当前状态支撑。壳层只汇总布尔事实并按输出 stage 跳转;big-fish、match3d、square-hole 的草稿事实固定来自 `Boolean(session?.draft)`,visual-novel 的 session draft 与 work draft 可独立支撑结果页,baby-object-match runtime 缺 draft 时直接回首页。
|
||||||
- 影响范围:退出登录、鉴权上下文收回、平台入口公开页 / 工作台 / 结果页 / 生成页 / 运行态的阶段恢复规则,以及后续新增 `SelectionStage`。
|
- 影响范围:退出登录、鉴权上下文收回、平台入口公开页 / 工作台 / 结果页 / 生成页 / 运行态的阶段恢复规则,以及后续新增 `SelectionStage`。
|
||||||
- 验证方式:`npm run test -- src/components/platform-entry/platformSelectionStageModel.test.ts`、针对新 Module 与壳层执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
|
- 验证方式:`npm run test -- src/components/platform-entry/platformSelectionStageModel.test.ts`、针对新 Module 与壳层执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
|
||||||
- 关联文档:`docs/technical/【前端架构】PlatformSelectionStageModel收口计划-2026-06-04.md`。
|
- 关联文档:`docs/technical/【前端架构】PlatformSelectionStageModel收口计划-2026-06-04.md`。
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ AI 文字游戏模板接入以 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_
|
|||||||
|
|
||||||
平台入口错误 / 完成弹窗的文案归一、来源格式、候选择一、dismiss key 与任务完成文案收口到 `src/components/platform-entry/platformDialogStateModel.ts`,规则见 [【前端架构】PlatformDialogStateModel收口计划-2026-06-03.md](./technical/【前端架构】PlatformDialogStateModel收口计划-2026-06-03.md)。
|
平台入口错误 / 完成弹窗的文案归一、来源格式、候选择一、dismiss key 与任务完成文案收口到 `src/components/platform-entry/platformDialogStateModel.ts`,规则见 [【前端架构】PlatformDialogStateModel收口计划-2026-06-03.md](./technical/【前端架构】PlatformDialogStateModel收口计划-2026-06-03.md)。
|
||||||
|
|
||||||
平台入口受保护数据失效后的 stage 去留判定收口到 `src/components/platform-entry/platformSelectionStageModel.ts`,壳层只执行缓存清空和必要跳转,规则见 [【前端架构】PlatformSelectionStageModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformSelectionStageModel收口计划-2026-06-04.md)。
|
平台入口受保护数据失效后的 stage 去留判定,以及缺失草稿 / 作品 / run 时的阶段回退,收口到 `src/components/platform-entry/platformSelectionStageModel.ts`,壳层只执行缓存清空、布尔事实汇总和必要跳转,规则见 [【前端架构】PlatformSelectionStageModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformSelectionStageModel收口计划-2026-06-04.md)。
|
||||||
|
|
||||||
小游戏 runtime client 的路径编码、JSON 请求、runtime guest auth 与 retry 选项收口到 `src/services/runtimeRequest.ts`,Match3D、SquareHole、Big Fish、Bark Battle、Puzzle 公开 / 推荐运行态请求、Jump Hop / Wooden Fish 正式 run 请求和 Visual Novel 局部 JSON runtime 请求已先迁移,规则见 [【前端架构】RuntimeClientFamily收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91RuntimeClientFamily%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
小游戏 runtime client 的路径编码、JSON 请求、runtime guest auth 与 retry 选项收口到 `src/services/runtimeRequest.ts`,Match3D、SquareHole、Big Fish、Bark Battle、Puzzle 公开 / 推荐运行态请求、Jump Hop / Wooden Fish 正式 run 请求和 Visual Novel 局部 JSON runtime 请求已先迁移,规则见 [【前端架构】RuntimeClientFamily收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91RuntimeClientFamily%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||||
|
|
||||||
|
|||||||
@@ -4,20 +4,24 @@
|
|||||||
|
|
||||||
`PlatformEntryFlowShellImpl.tsx` 在受保护数据失效后会清空当前用户的私有作品、运行态、草稿 notice 和生成状态。清理完成后,壳层还要判断当前 `SelectionStage` 是否还能继续展示:公开首页、公开详情、工作台入口等阶段可保留;结果页、生成页、运行态、个人反馈等依赖私有数据或运行态快照的阶段必须回到首页。
|
`PlatformEntryFlowShellImpl.tsx` 在受保护数据失效后会清空当前用户的私有作品、运行态、草稿 notice 和生成状态。清理完成后,壳层还要判断当前 `SelectionStage` 是否还能继续展示:公开首页、公开详情、工作台入口等阶段可保留;结果页、生成页、运行态、个人反馈等依赖私有数据或运行态快照的阶段必须回到首页。
|
||||||
|
|
||||||
此前该规则以内联长否定串维护在壳层 **Implementation** 内。新增玩法 stage 或调整登录态行为时,维护者必须在巨型壳层中查找白名单,缺少独立测试面。
|
此外,平台壳还曾在多个 `useEffect` 中分别判断 big-fish、match3d、square-hole、visual-novel、baby-object-match 缺少草稿、作品或 run 时应回工作台、结果页还是首页。这类“当前 stage 已不能被现有状态支撑”的规则同样属于 stage 纯判定,不应散在壳层。
|
||||||
|
|
||||||
|
此前这些规则以内联长否定串或多段相似 effect 维护在壳层 **Implementation** 内。新增玩法 stage 或调整登录态行为时,维护者必须在巨型壳层中查找白名单和状态缺失回退,缺少独立测试面。
|
||||||
|
|
||||||
## 决策
|
## 决策
|
||||||
|
|
||||||
新增 `src/components/platform-entry/platformSelectionStageModel.ts` 作为 Platform Selection Stage **Module**。其公开 **Interface** 为:
|
新增 `src/components/platform-entry/platformSelectionStageModel.ts` 作为 Platform Selection Stage **Module**。其公开 **Interface** 为:
|
||||||
|
|
||||||
- `resolveSelectionStageAfterProtectedDataLoss(stage)`:输入当前 `SelectionStage`,输出受保护数据失效后应停留的 stage;可保留则原样返回,否则返回 `platform`。
|
- `resolveSelectionStageAfterProtectedDataLoss(stage)`:输入当前 `SelectionStage`,输出受保护数据失效后应停留的 stage;可保留则原样返回,否则返回 `platform`。
|
||||||
|
- `resolveSelectionStageAfterMissingCreationState(params)`:输入当前 `SelectionStage` 与各玩法“是否有 session / draft / run / work / formPayload”等可渲染事实,输出状态缺失后应停留的 stage;仍可展示则原样返回。
|
||||||
|
|
||||||
`PlatformEntryFlowShellImpl.tsx` 仍作为副作用 **Adapter**:负责检测受保护数据从可读变为不可读、清空各玩法缓存、重置生成和错误状态,并只在模型输出与当前 stage 不一致时调用 `setSelectionStage(nextStage)`。
|
`PlatformEntryFlowShellImpl.tsx` 仍作为副作用 **Adapter**:负责检测受保护数据从可读变为不可读、清空各玩法缓存、重置生成和错误状态,或把当前 React state 汇总为布尔事实,并只在模型输出与当前 stage 不一致时调用 `setSelectionStage(nextStage)`。
|
||||||
|
|
||||||
## 约定
|
## 约定
|
||||||
|
|
||||||
- 新增 `SelectionStage` 时,必须判断它在退出登录或鉴权上下文收回后是否仍可展示,并在本 **Module** 的全量 `Record<SelectionStage, boolean>` 与测试中列明。
|
- 新增 `SelectionStage` 时,必须判断它在退出登录或鉴权上下文收回后是否仍可展示,并在本 **Module** 的全量 `Record<SelectionStage, boolean>` 与测试中列明。
|
||||||
- 公开列表、公开详情和创作工作台入口可保留;依赖当前用户私有数据、生成 session、运行态 run 或个人资料的 stage 默认回 `platform`。
|
- 公开列表、公开详情和创作工作台入口可保留;依赖当前用户私有数据、生成 session、运行态 run 或个人资料的 stage 默认回 `platform`。
|
||||||
|
- 缺失状态回退只读取壳层传入的布尔事实,不直接读取玩法 session / work / run 对象。big-fish、match3d、square-hole 的草稿事实必须来自 `Boolean(session?.draft)`;visual-novel 的 session draft 与 work draft 可独立支撑结果页;baby-object-match runtime 缺 draft 时不看 formPayload,直接回 `platform`。
|
||||||
- 此 **Module** 不清理 state、不调用路由、不触发登录弹窗,只表达纯 stage 决策。
|
- 此 **Module** 不清理 state、不调用路由、不触发登录弹窗,只表达纯 stage 决策。
|
||||||
|
|
||||||
## 验收
|
## 验收
|
||||||
|
|||||||
@@ -540,7 +540,10 @@ import {
|
|||||||
buildPuzzleResultProfileId,
|
buildPuzzleResultProfileId,
|
||||||
buildPuzzleResultWorkId,
|
buildPuzzleResultWorkId,
|
||||||
} from './platformPuzzleIdentityModel';
|
} from './platformPuzzleIdentityModel';
|
||||||
import { resolveSelectionStageAfterProtectedDataLoss } from './platformSelectionStageModel';
|
import {
|
||||||
|
resolveSelectionStageAfterMissingCreationState,
|
||||||
|
resolveSelectionStageAfterProtectedDataLoss,
|
||||||
|
} from './platformSelectionStageModel';
|
||||||
import { PlatformTaskCompletionDialog } from './PlatformTaskCompletionDialog';
|
import { PlatformTaskCompletionDialog } from './PlatformTaskCompletionDialog';
|
||||||
import { PlatformWorkDetailView } from './PlatformWorkDetailView';
|
import { PlatformWorkDetailView } from './PlatformWorkDetailView';
|
||||||
import { usePlatformCreationAgentFlowController } from './usePlatformCreationAgentFlowController';
|
import { usePlatformCreationAgentFlowController } from './usePlatformCreationAgentFlowController';
|
||||||
@@ -8861,88 +8864,53 @@ export function PlatformEntryFlowShellImpl({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectionStage === 'big-fish-result' && !bigFishSession?.draft) {
|
const nextSelectionStage = resolveSelectionStageAfterMissingCreationState({
|
||||||
setSelectionStage(
|
stage: selectionStage,
|
||||||
bigFishSession ? 'big-fish-agent-workspace' : 'platform',
|
bigFish: {
|
||||||
);
|
hasSession: Boolean(bigFishSession),
|
||||||
}
|
hasSessionDraft: Boolean(bigFishSession?.draft),
|
||||||
if (selectionStage === 'big-fish-runtime' && !bigFishRun) {
|
hasRun: Boolean(bigFishRun),
|
||||||
setSelectionStage(bigFishSession?.draft ? 'big-fish-result' : 'platform');
|
},
|
||||||
}
|
match3d: {
|
||||||
}, [bigFishRun, bigFishSession, selectionStage, setSelectionStage]);
|
hasSession: Boolean(match3dSession),
|
||||||
|
hasSessionDraft: Boolean(match3dSession?.draft),
|
||||||
|
hasRun: Boolean(match3dRun),
|
||||||
|
},
|
||||||
|
squareHole: {
|
||||||
|
hasSession: Boolean(squareHoleSession),
|
||||||
|
hasSessionDraft: Boolean(squareHoleSession?.draft),
|
||||||
|
hasRun: Boolean(squareHoleRun),
|
||||||
|
},
|
||||||
|
visualNovel: {
|
||||||
|
hasSession: Boolean(visualNovelSession),
|
||||||
|
hasSessionDraft: Boolean(visualNovelSession?.draft),
|
||||||
|
hasWork: Boolean(visualNovelWork),
|
||||||
|
hasWorkDraft: Boolean(visualNovelWork?.draft),
|
||||||
|
hasRun: Boolean(visualNovelRun),
|
||||||
|
},
|
||||||
|
babyObjectMatch: {
|
||||||
|
hasDraft: Boolean(babyObjectMatchDraft),
|
||||||
|
hasFormPayload: Boolean(babyObjectMatchFormPayload),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
if (nextSelectionStage !== selectionStage) {
|
||||||
if (selectionStage === 'match3d-result' && !match3dSession?.draft) {
|
setSelectionStage(nextSelectionStage);
|
||||||
setSelectionStage(
|
|
||||||
match3dSession ? 'match3d-agent-workspace' : 'platform',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (selectionStage === 'match3d-runtime' && !match3dRun) {
|
|
||||||
setSelectionStage(match3dSession?.draft ? 'match3d-result' : 'platform');
|
|
||||||
}
|
|
||||||
}, [match3dRun, match3dSession, selectionStage, setSelectionStage]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectionStage === 'square-hole-result' && !squareHoleSession?.draft) {
|
|
||||||
setSelectionStage(
|
|
||||||
squareHoleSession ? 'square-hole-agent-workspace' : 'platform',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (selectionStage === 'square-hole-runtime' && !squareHoleRun) {
|
|
||||||
setSelectionStage(
|
|
||||||
squareHoleSession?.draft ? 'square-hole-result' : 'platform',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [selectionStage, setSelectionStage, squareHoleRun, squareHoleSession]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
selectionStage === 'visual-novel-result' &&
|
|
||||||
!visualNovelSession?.draft &&
|
|
||||||
!visualNovelWork?.draft
|
|
||||||
) {
|
|
||||||
setSelectionStage(
|
|
||||||
visualNovelSession ? 'visual-novel-agent-workspace' : 'platform',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (selectionStage === 'visual-novel-runtime' && !visualNovelRun) {
|
|
||||||
setSelectionStage(
|
|
||||||
visualNovelSession?.draft || visualNovelWork?.draft
|
|
||||||
? 'visual-novel-result'
|
|
||||||
: 'platform',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (selectionStage === 'visual-novel-gallery-detail' && !visualNovelWork) {
|
|
||||||
setSelectionStage('platform');
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
selectionStage,
|
|
||||||
setSelectionStage,
|
|
||||||
visualNovelRun,
|
|
||||||
visualNovelSession,
|
|
||||||
visualNovelWork,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
selectionStage === 'baby-object-match-result' &&
|
|
||||||
!babyObjectMatchDraft
|
|
||||||
) {
|
|
||||||
setSelectionStage(
|
|
||||||
babyObjectMatchFormPayload ? 'baby-object-match-workspace' : 'platform',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
selectionStage === 'baby-object-match-runtime' &&
|
|
||||||
!babyObjectMatchDraft
|
|
||||||
) {
|
|
||||||
setSelectionStage('platform');
|
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
babyObjectMatchDraft,
|
babyObjectMatchDraft,
|
||||||
babyObjectMatchFormPayload,
|
babyObjectMatchFormPayload,
|
||||||
|
bigFishRun,
|
||||||
|
bigFishSession,
|
||||||
|
match3dRun,
|
||||||
|
match3dSession,
|
||||||
selectionStage,
|
selectionStage,
|
||||||
setSelectionStage,
|
setSelectionStage,
|
||||||
|
squareHoleRun,
|
||||||
|
squareHoleSession,
|
||||||
|
visualNovelRun,
|
||||||
|
visualNovelSession,
|
||||||
|
visualNovelWork,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const startBigFishRun = useCallback(async () => {
|
const startBigFishRun = useCallback(async () => {
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { describe, expect, test } from 'vitest';
|
import { describe, expect, test } from 'vitest';
|
||||||
|
|
||||||
import type { SelectionStage } from './platformEntryTypes';
|
import type { SelectionStage } from './platformEntryTypes';
|
||||||
import { resolveSelectionStageAfterProtectedDataLoss } from './platformSelectionStageModel';
|
import {
|
||||||
|
type MissingCreationStateParams,
|
||||||
|
resolveSelectionStageAfterMissingCreationState,
|
||||||
|
resolveSelectionStageAfterProtectedDataLoss,
|
||||||
|
} from './platformSelectionStageModel';
|
||||||
|
|
||||||
describe('platformSelectionStageModel', () => {
|
describe('platformSelectionStageModel', () => {
|
||||||
test('keeps public and workspace stages after protected data loss', () => {
|
test('keeps public and workspace stages after protected data loss', () => {
|
||||||
@@ -72,4 +76,203 @@ describe('platformSelectionStageModel', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('resolves missing session draft result stages', () => {
|
||||||
|
expect(
|
||||||
|
resolveSelectionStageAfterMissingCreationState(
|
||||||
|
buildMissingCreationStateParams({
|
||||||
|
stage: 'big-fish-result',
|
||||||
|
bigFish: { hasSession: true, hasSessionDraft: false, hasRun: false },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('big-fish-agent-workspace');
|
||||||
|
expect(
|
||||||
|
resolveSelectionStageAfterMissingCreationState(
|
||||||
|
buildMissingCreationStateParams({
|
||||||
|
stage: 'big-fish-result',
|
||||||
|
bigFish: { hasSession: false, hasSessionDraft: false, hasRun: false },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('platform');
|
||||||
|
expect(
|
||||||
|
resolveSelectionStageAfterMissingCreationState(
|
||||||
|
buildMissingCreationStateParams({
|
||||||
|
stage: 'match3d-result',
|
||||||
|
match3d: { hasSession: true, hasSessionDraft: false, hasRun: false },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('match3d-agent-workspace');
|
||||||
|
expect(
|
||||||
|
resolveSelectionStageAfterMissingCreationState(
|
||||||
|
buildMissingCreationStateParams({
|
||||||
|
stage: 'square-hole-result',
|
||||||
|
squareHole: {
|
||||||
|
hasSession: true,
|
||||||
|
hasSessionDraft: false,
|
||||||
|
hasRun: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('square-hole-agent-workspace');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('resolves missing session run stages', () => {
|
||||||
|
expect(
|
||||||
|
resolveSelectionStageAfterMissingCreationState(
|
||||||
|
buildMissingCreationStateParams({
|
||||||
|
stage: 'big-fish-runtime',
|
||||||
|
bigFish: { hasSession: true, hasSessionDraft: true, hasRun: false },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('big-fish-result');
|
||||||
|
expect(
|
||||||
|
resolveSelectionStageAfterMissingCreationState(
|
||||||
|
buildMissingCreationStateParams({
|
||||||
|
stage: 'big-fish-runtime',
|
||||||
|
bigFish: { hasSession: true, hasSessionDraft: false, hasRun: false },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('platform');
|
||||||
|
expect(
|
||||||
|
resolveSelectionStageAfterMissingCreationState(
|
||||||
|
buildMissingCreationStateParams({
|
||||||
|
stage: 'match3d-runtime',
|
||||||
|
match3d: { hasSession: true, hasSessionDraft: true, hasRun: false },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('match3d-result');
|
||||||
|
expect(
|
||||||
|
resolveSelectionStageAfterMissingCreationState(
|
||||||
|
buildMissingCreationStateParams({
|
||||||
|
stage: 'square-hole-runtime',
|
||||||
|
squareHole: {
|
||||||
|
hasSession: true,
|
||||||
|
hasSessionDraft: true,
|
||||||
|
hasRun: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('square-hole-result');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('resolves visual novel and baby object missing state stages', () => {
|
||||||
|
expect(
|
||||||
|
resolveSelectionStageAfterMissingCreationState(
|
||||||
|
buildMissingCreationStateParams({
|
||||||
|
stage: 'visual-novel-result',
|
||||||
|
visualNovel: {
|
||||||
|
hasSession: true,
|
||||||
|
hasSessionDraft: false,
|
||||||
|
hasWork: false,
|
||||||
|
hasWorkDraft: false,
|
||||||
|
hasRun: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('visual-novel-agent-workspace');
|
||||||
|
expect(
|
||||||
|
resolveSelectionStageAfterMissingCreationState(
|
||||||
|
buildMissingCreationStateParams({
|
||||||
|
stage: 'visual-novel-runtime',
|
||||||
|
visualNovel: {
|
||||||
|
hasSession: true,
|
||||||
|
hasSessionDraft: false,
|
||||||
|
hasWork: true,
|
||||||
|
hasWorkDraft: true,
|
||||||
|
hasRun: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('visual-novel-result');
|
||||||
|
expect(
|
||||||
|
resolveSelectionStageAfterMissingCreationState(
|
||||||
|
buildMissingCreationStateParams({
|
||||||
|
stage: 'visual-novel-gallery-detail',
|
||||||
|
visualNovel: {
|
||||||
|
hasSession: false,
|
||||||
|
hasSessionDraft: false,
|
||||||
|
hasWork: false,
|
||||||
|
hasWorkDraft: false,
|
||||||
|
hasRun: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('platform');
|
||||||
|
expect(
|
||||||
|
resolveSelectionStageAfterMissingCreationState(
|
||||||
|
buildMissingCreationStateParams({
|
||||||
|
stage: 'baby-object-match-result',
|
||||||
|
babyObjectMatch: { hasDraft: false, hasFormPayload: true },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('baby-object-match-workspace');
|
||||||
|
expect(
|
||||||
|
resolveSelectionStageAfterMissingCreationState(
|
||||||
|
buildMissingCreationStateParams({
|
||||||
|
stage: 'baby-object-match-runtime',
|
||||||
|
babyObjectMatch: { hasDraft: false, hasFormPayload: true },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('platform');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('keeps stages when required creation state exists', () => {
|
||||||
|
expect(
|
||||||
|
resolveSelectionStageAfterMissingCreationState(
|
||||||
|
buildMissingCreationStateParams({
|
||||||
|
stage: 'big-fish-result',
|
||||||
|
bigFish: { hasSession: true, hasSessionDraft: true, hasRun: false },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('big-fish-result');
|
||||||
|
expect(
|
||||||
|
resolveSelectionStageAfterMissingCreationState(
|
||||||
|
buildMissingCreationStateParams({
|
||||||
|
stage: 'big-fish-runtime',
|
||||||
|
bigFish: { hasSession: true, hasSessionDraft: true, hasRun: true },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('big-fish-runtime');
|
||||||
|
expect(
|
||||||
|
resolveSelectionStageAfterMissingCreationState(
|
||||||
|
buildMissingCreationStateParams({
|
||||||
|
stage: 'visual-novel-gallery-detail',
|
||||||
|
visualNovel: {
|
||||||
|
hasSession: false,
|
||||||
|
hasSessionDraft: false,
|
||||||
|
hasWork: true,
|
||||||
|
hasWorkDraft: false,
|
||||||
|
hasRun: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('visual-novel-gallery-detail');
|
||||||
|
expect(
|
||||||
|
resolveSelectionStageAfterMissingCreationState(
|
||||||
|
buildMissingCreationStateParams({
|
||||||
|
stage: 'platform',
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe('platform');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function buildMissingCreationStateParams(
|
||||||
|
overrides: Partial<MissingCreationStateParams> = {},
|
||||||
|
): MissingCreationStateParams {
|
||||||
|
return {
|
||||||
|
stage: 'platform',
|
||||||
|
bigFish: { hasSession: false, hasSessionDraft: false, hasRun: false },
|
||||||
|
match3d: { hasSession: false, hasSessionDraft: false, hasRun: false },
|
||||||
|
squareHole: { hasSession: false, hasSessionDraft: false, hasRun: false },
|
||||||
|
visualNovel: {
|
||||||
|
hasSession: false,
|
||||||
|
hasSessionDraft: false,
|
||||||
|
hasWork: false,
|
||||||
|
hasWorkDraft: false,
|
||||||
|
hasRun: false,
|
||||||
|
},
|
||||||
|
babyObjectMatch: { hasDraft: false, hasFormPayload: false },
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,3 +57,97 @@ export function resolveSelectionStageAfterProtectedDataLoss(
|
|||||||
): SelectionStage {
|
): SelectionStage {
|
||||||
return PROTECTED_DATA_LOSS_STABLE_STAGE_BY_STAGE[stage] ? stage : 'platform';
|
return PROTECTED_DATA_LOSS_STABLE_STAGE_BY_STAGE[stage] ? stage : 'platform';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SessionDraftRunState = {
|
||||||
|
hasSession: boolean;
|
||||||
|
hasSessionDraft: boolean;
|
||||||
|
hasRun: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type VisualNovelCreationState = {
|
||||||
|
hasSession: boolean;
|
||||||
|
hasSessionDraft: boolean;
|
||||||
|
hasWork: boolean;
|
||||||
|
hasWorkDraft: boolean;
|
||||||
|
hasRun: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BabyObjectMatchCreationState = {
|
||||||
|
hasDraft: boolean;
|
||||||
|
hasFormPayload: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MissingCreationStateParams = {
|
||||||
|
stage: SelectionStage;
|
||||||
|
bigFish: SessionDraftRunState;
|
||||||
|
match3d: SessionDraftRunState;
|
||||||
|
squareHole: SessionDraftRunState;
|
||||||
|
visualNovel: VisualNovelCreationState;
|
||||||
|
babyObjectMatch: BabyObjectMatchCreationState;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function resolveSelectionStageAfterMissingCreationState(
|
||||||
|
params: MissingCreationStateParams,
|
||||||
|
): SelectionStage {
|
||||||
|
const { stage } = params;
|
||||||
|
|
||||||
|
if (stage === 'big-fish-result' && !params.bigFish.hasSessionDraft) {
|
||||||
|
return params.bigFish.hasSession ? 'big-fish-agent-workspace' : 'platform';
|
||||||
|
}
|
||||||
|
if (stage === 'big-fish-runtime' && !params.bigFish.hasRun) {
|
||||||
|
return params.bigFish.hasSessionDraft ? 'big-fish-result' : 'platform';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stage === 'match3d-result' && !params.match3d.hasSessionDraft) {
|
||||||
|
return params.match3d.hasSession ? 'match3d-agent-workspace' : 'platform';
|
||||||
|
}
|
||||||
|
if (stage === 'match3d-runtime' && !params.match3d.hasRun) {
|
||||||
|
return params.match3d.hasSessionDraft ? 'match3d-result' : 'platform';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stage === 'square-hole-result' && !params.squareHole.hasSessionDraft) {
|
||||||
|
return params.squareHole.hasSession
|
||||||
|
? 'square-hole-agent-workspace'
|
||||||
|
: 'platform';
|
||||||
|
}
|
||||||
|
if (stage === 'square-hole-runtime' && !params.squareHole.hasRun) {
|
||||||
|
return params.squareHole.hasSessionDraft
|
||||||
|
? 'square-hole-result'
|
||||||
|
: 'platform';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
stage === 'visual-novel-result' &&
|
||||||
|
!params.visualNovel.hasSessionDraft &&
|
||||||
|
!params.visualNovel.hasWorkDraft
|
||||||
|
) {
|
||||||
|
return params.visualNovel.hasSession
|
||||||
|
? 'visual-novel-agent-workspace'
|
||||||
|
: 'platform';
|
||||||
|
}
|
||||||
|
if (stage === 'visual-novel-runtime' && !params.visualNovel.hasRun) {
|
||||||
|
return params.visualNovel.hasSessionDraft || params.visualNovel.hasWorkDraft
|
||||||
|
? 'visual-novel-result'
|
||||||
|
: 'platform';
|
||||||
|
}
|
||||||
|
if (stage === 'visual-novel-gallery-detail' && !params.visualNovel.hasWork) {
|
||||||
|
return 'platform';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
stage === 'baby-object-match-result' &&
|
||||||
|
!params.babyObjectMatch.hasDraft
|
||||||
|
) {
|
||||||
|
return params.babyObjectMatch.hasFormPayload
|
||||||
|
? 'baby-object-match-workspace'
|
||||||
|
: 'platform';
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
stage === 'baby-object-match-runtime' &&
|
||||||
|
!params.babyObjectMatch.hasDraft
|
||||||
|
) {
|
||||||
|
return 'platform';
|
||||||
|
}
|
||||||
|
|
||||||
|
return stage;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user