refactor: 收口平台阶段失权判定
This commit is contained in:
@@ -16,6 +16,14 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-06-04 Platform Selection Stage Model 收口
|
||||||
|
|
||||||
|
- 背景:平台入口在受保护数据失效后会清空当前用户私有作品、草稿、运行态和生成状态,但哪些 `SelectionStage` 可保留、哪些必须回首页曾以内联长否定串散在 `PlatformEntryFlowShellImpl.tsx`。
|
||||||
|
- 决策:新增 `src/components/platform-entry/platformSelectionStageModel.ts`,以 `resolveSelectionStageAfterProtectedDataLoss(stage)` 收口受保护数据失效后的 stage 去留判定。模型内部使用 `satisfies Record<SelectionStage, boolean>` 全量分类,新增 stage 时必须明确保留或回首页。壳层仍负责检测权限变化、清 state 和调用 `setSelectionStage`。
|
||||||
|
- 影响范围:退出登录、鉴权上下文收回、平台入口公开页 / 工作台 / 结果页 / 生成页 / 运行态的阶段恢复规则,以及后续新增 `SelectionStage`。
|
||||||
|
- 验证方式:`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`。
|
||||||
|
|
||||||
## 2026-06-04 Creation Work Delete Flow 收口
|
## 2026-06-04 Creation Work Delete Flow 收口
|
||||||
|
|
||||||
- 背景:平台入口作品架删除入口在 RPG、拼图、抓大鹅、方洞挑战、大鱼吃小鱼、视觉小说和宝贝识物 handler 内重复计算确认标题、删除说明、草稿 notice key 与拼图派生稳定 ID,导致删除确认规则散在巨型壳层。
|
- 背景:平台入口作品架删除入口在 RPG、拼图、抓大鹅、方洞挑战、大鱼吃小鱼、视觉小说和宝贝识物 handler 内重复计算确认标题、删除说明、草稿 notice key 与拼图派生稳定 ID,导致删除确认规则散在巨型壳层。
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ 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)。
|
||||||
|
|
||||||
小游戏 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)。
|
||||||
|
|
||||||
抓大鹅 runtime profile 的公开详情转 work、session draft 转 profile、生成背景资产提升和 run/profile/public detail 素材优先级收口到 `src/components/platform-entry/platformMatch3DRuntimeProfile.ts`,规则见 [【前端架构】Match3DRuntimeProfile收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91Match3DRuntimeProfile%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
抓大鹅 runtime profile 的公开详情转 work、session draft 转 profile、生成背景资产提升和 run/profile/public detail 素材优先级收口到 `src/components/platform-entry/platformMatch3DRuntimeProfile.ts`,规则见 [【前端架构】Match3DRuntimeProfile收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91Match3DRuntimeProfile%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# 【前端架构】Platform Selection Stage Model 收口计划
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
`PlatformEntryFlowShellImpl.tsx` 在受保护数据失效后会清空当前用户的私有作品、运行态、草稿 notice 和生成状态。清理完成后,壳层还要判断当前 `SelectionStage` 是否还能继续展示:公开首页、公开详情、工作台入口等阶段可保留;结果页、生成页、运行态、个人反馈等依赖私有数据或运行态快照的阶段必须回到首页。
|
||||||
|
|
||||||
|
此前该规则以内联长否定串维护在壳层 **Implementation** 内。新增玩法 stage 或调整登录态行为时,维护者必须在巨型壳层中查找白名单,缺少独立测试面。
|
||||||
|
|
||||||
|
## 决策
|
||||||
|
|
||||||
|
新增 `src/components/platform-entry/platformSelectionStageModel.ts` 作为 Platform Selection Stage **Module**。其公开 **Interface** 为:
|
||||||
|
|
||||||
|
- `resolveSelectionStageAfterProtectedDataLoss(stage)`:输入当前 `SelectionStage`,输出受保护数据失效后应停留的 stage;可保留则原样返回,否则返回 `platform`。
|
||||||
|
|
||||||
|
`PlatformEntryFlowShellImpl.tsx` 仍作为副作用 **Adapter**:负责检测受保护数据从可读变为不可读、清空各玩法缓存、重置生成和错误状态,并只在模型输出与当前 stage 不一致时调用 `setSelectionStage(nextStage)`。
|
||||||
|
|
||||||
|
## 约定
|
||||||
|
|
||||||
|
- 新增 `SelectionStage` 时,必须判断它在退出登录或鉴权上下文收回后是否仍可展示,并在本 **Module** 的全量 `Record<SelectionStage, boolean>` 与测试中列明。
|
||||||
|
- 公开列表、公开详情和创作工作台入口可保留;依赖当前用户私有数据、生成 session、运行态 run 或个人资料的 stage 默认回 `platform`。
|
||||||
|
- 此 **Module** 不清理 state、不调用路由、不触发登录弹窗,只表达纯 stage 决策。
|
||||||
|
|
||||||
|
## 验收
|
||||||
|
|
||||||
|
- `npm run test -- src/components/platform-entry/platformSelectionStageModel.test.ts`
|
||||||
|
- `npx eslint src/components/platform-entry/platformSelectionStageModel.ts src/components/platform-entry/platformSelectionStageModel.test.ts src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
|
||||||
|
- `npm run typecheck`
|
||||||
|
- `npm run check:encoding`
|
||||||
@@ -532,6 +532,7 @@ import {
|
|||||||
buildPuzzleResultProfileId,
|
buildPuzzleResultProfileId,
|
||||||
buildPuzzleResultWorkId,
|
buildPuzzleResultWorkId,
|
||||||
} from './platformPuzzleIdentityModel';
|
} from './platformPuzzleIdentityModel';
|
||||||
|
import { 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';
|
||||||
@@ -6657,24 +6658,10 @@ export function PlatformEntryFlowShellImpl({
|
|||||||
persistRpgAgentUiState(null, null);
|
persistRpgAgentUiState(null, null);
|
||||||
resetAutoSaveTrackingToIdle();
|
resetAutoSaveTrackingToIdle();
|
||||||
|
|
||||||
if (
|
const nextSelectionStage =
|
||||||
selectionStage !== 'platform' &&
|
resolveSelectionStageAfterProtectedDataLoss(selectionStage);
|
||||||
selectionStage !== 'work-detail' &&
|
if (nextSelectionStage !== selectionStage) {
|
||||||
selectionStage !== 'detail' &&
|
setSelectionStage(nextSelectionStage);
|
||||||
selectionStage !== 'agent-workspace' &&
|
|
||||||
selectionStage !== 'big-fish-agent-workspace' &&
|
|
||||||
selectionStage !== 'match3d-agent-workspace' &&
|
|
||||||
selectionStage !== 'square-hole-agent-workspace' &&
|
|
||||||
selectionStage !== 'jump-hop-workspace' &&
|
|
||||||
selectionStage !== 'wooden-fish-workspace' &&
|
|
||||||
selectionStage !== 'puzzle-agent-workspace' &&
|
|
||||||
selectionStage !== 'bark-battle-workspace' &&
|
|
||||||
selectionStage !== 'visual-novel-agent-workspace' &&
|
|
||||||
selectionStage !== 'baby-object-match-workspace' &&
|
|
||||||
selectionStage !== 'creative-agent-workspace' &&
|
|
||||||
selectionStage !== 'puzzle-gallery-detail'
|
|
||||||
) {
|
|
||||||
setSelectionStage('platform');
|
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
authUi?.user,
|
authUi?.user,
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
|
||||||
|
import type { SelectionStage } from './platformEntryTypes';
|
||||||
|
import { resolveSelectionStageAfterProtectedDataLoss } from './platformSelectionStageModel';
|
||||||
|
|
||||||
|
describe('platformSelectionStageModel', () => {
|
||||||
|
test('keeps public and workspace stages after protected data loss', () => {
|
||||||
|
const stableStages: SelectionStage[] = [
|
||||||
|
'platform',
|
||||||
|
'work-detail',
|
||||||
|
'detail',
|
||||||
|
'agent-workspace',
|
||||||
|
'big-fish-agent-workspace',
|
||||||
|
'match3d-agent-workspace',
|
||||||
|
'square-hole-agent-workspace',
|
||||||
|
'jump-hop-workspace',
|
||||||
|
'wooden-fish-workspace',
|
||||||
|
'puzzle-agent-workspace',
|
||||||
|
'bark-battle-workspace',
|
||||||
|
'visual-novel-agent-workspace',
|
||||||
|
'baby-object-match-workspace',
|
||||||
|
'creative-agent-workspace',
|
||||||
|
'puzzle-gallery-detail',
|
||||||
|
];
|
||||||
|
|
||||||
|
stableStages.forEach((stage) => {
|
||||||
|
expect(resolveSelectionStageAfterProtectedDataLoss(stage)).toBe(stage);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('resets private result, generating, runtime and profile stages to platform', () => {
|
||||||
|
const resetStages: SelectionStage[] = [
|
||||||
|
'profile-feedback',
|
||||||
|
'big-fish-generating',
|
||||||
|
'big-fish-result',
|
||||||
|
'big-fish-runtime',
|
||||||
|
'match3d-generating',
|
||||||
|
'match3d-result',
|
||||||
|
'match3d-runtime',
|
||||||
|
'square-hole-generating',
|
||||||
|
'square-hole-result',
|
||||||
|
'square-hole-runtime',
|
||||||
|
'jump-hop-generating',
|
||||||
|
'jump-hop-result',
|
||||||
|
'jump-hop-runtime',
|
||||||
|
'jump-hop-gallery-detail',
|
||||||
|
'wooden-fish-generating',
|
||||||
|
'wooden-fish-result',
|
||||||
|
'wooden-fish-runtime',
|
||||||
|
'visual-novel-generating',
|
||||||
|
'visual-novel-result',
|
||||||
|
'visual-novel-gallery-detail',
|
||||||
|
'visual-novel-runtime',
|
||||||
|
'baby-object-match-generating',
|
||||||
|
'baby-object-match-result',
|
||||||
|
'baby-object-match-runtime',
|
||||||
|
'baby-love-drawing-runtime',
|
||||||
|
'puzzle-generating',
|
||||||
|
'puzzle-onboarding',
|
||||||
|
'puzzle-result',
|
||||||
|
'puzzle-runtime',
|
||||||
|
'custom-world-generating',
|
||||||
|
'custom-world-result',
|
||||||
|
'bark-battle-generating',
|
||||||
|
'bark-battle-result',
|
||||||
|
'bark-battle-runtime',
|
||||||
|
];
|
||||||
|
|
||||||
|
resetStages.forEach((stage) => {
|
||||||
|
expect(resolveSelectionStageAfterProtectedDataLoss(stage)).toBe(
|
||||||
|
'platform',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
59
src/components/platform-entry/platformSelectionStageModel.ts
Normal file
59
src/components/platform-entry/platformSelectionStageModel.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import type { SelectionStage } from './platformEntryTypes';
|
||||||
|
|
||||||
|
const PROTECTED_DATA_LOSS_STABLE_STAGE_BY_STAGE = {
|
||||||
|
platform: true,
|
||||||
|
'profile-feedback': false,
|
||||||
|
'work-detail': true,
|
||||||
|
detail: true,
|
||||||
|
'agent-workspace': true,
|
||||||
|
'big-fish-agent-workspace': true,
|
||||||
|
'big-fish-generating': false,
|
||||||
|
'big-fish-result': false,
|
||||||
|
'big-fish-runtime': false,
|
||||||
|
'match3d-agent-workspace': true,
|
||||||
|
'match3d-generating': false,
|
||||||
|
'match3d-result': false,
|
||||||
|
'match3d-runtime': false,
|
||||||
|
'square-hole-agent-workspace': true,
|
||||||
|
'square-hole-generating': false,
|
||||||
|
'square-hole-result': false,
|
||||||
|
'square-hole-runtime': false,
|
||||||
|
'jump-hop-workspace': true,
|
||||||
|
'jump-hop-generating': false,
|
||||||
|
'jump-hop-result': false,
|
||||||
|
'jump-hop-runtime': false,
|
||||||
|
'jump-hop-gallery-detail': false,
|
||||||
|
'bark-battle-workspace': true,
|
||||||
|
'bark-battle-generating': false,
|
||||||
|
'bark-battle-result': false,
|
||||||
|
'bark-battle-runtime': false,
|
||||||
|
'wooden-fish-workspace': true,
|
||||||
|
'wooden-fish-generating': false,
|
||||||
|
'wooden-fish-result': false,
|
||||||
|
'wooden-fish-runtime': false,
|
||||||
|
'creative-agent-workspace': true,
|
||||||
|
'visual-novel-agent-workspace': true,
|
||||||
|
'visual-novel-generating': false,
|
||||||
|
'visual-novel-result': false,
|
||||||
|
'visual-novel-gallery-detail': false,
|
||||||
|
'visual-novel-runtime': false,
|
||||||
|
'baby-object-match-workspace': true,
|
||||||
|
'baby-object-match-generating': false,
|
||||||
|
'baby-object-match-result': false,
|
||||||
|
'baby-object-match-runtime': false,
|
||||||
|
'baby-love-drawing-runtime': false,
|
||||||
|
'puzzle-agent-workspace': true,
|
||||||
|
'puzzle-generating': false,
|
||||||
|
'puzzle-onboarding': false,
|
||||||
|
'puzzle-result': false,
|
||||||
|
'puzzle-gallery-detail': true,
|
||||||
|
'puzzle-runtime': false,
|
||||||
|
'custom-world-generating': false,
|
||||||
|
'custom-world-result': false,
|
||||||
|
} as const satisfies Record<SelectionStage, boolean>;
|
||||||
|
|
||||||
|
export function resolveSelectionStageAfterProtectedDataLoss(
|
||||||
|
stage: SelectionStage,
|
||||||
|
): SelectionStage {
|
||||||
|
return PROTECTED_DATA_LOSS_STABLE_STAGE_BY_STAGE[stage] ? stage : 'platform';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user