refactor: 收口创作直达恢复判定
This commit is contained in:
@@ -1331,6 +1331,7 @@
|
|||||||
|
|
||||||
- 背景:平台壳内散落各玩法创作恢复 URL 的 `sessionId` / `profileId` / `draftId` / `workId` 组装、空值归一化、拼图 runtime query key 与拼图稳定身份互推,导致刷新恢复规则缺少稳定测试面。
|
- 背景:平台壳内散落各玩法创作恢复 URL 的 `sessionId` / `profileId` / `draftId` / `workId` 组装、空值归一化、拼图 runtime query key 与拼图稳定身份互推,导致刷新恢复规则缺少稳定测试面。
|
||||||
- 决策:新增 `src/components/platform-entry/platformCreationUrlStateModel.ts` 作为 Creation URL State Module,Interface 收口各玩法 `build*CreationUrlState`、拼图 `buildPuzzle*RuntimeUrlState`、URL state 非空判断和 runtime state key;新增 `src/components/platform-entry/platformPuzzleIdentityModel.ts` 作为拼图稳定身份 Module,`platformDraftGenerationShelfModel.ts` 仅 re-export 旧入口以保持兼容。`PlatformEntryFlowShellImpl.tsx` 只保留路由、URL 写入和网络副作用 Adapter。
|
- 决策:新增 `src/components/platform-entry/platformCreationUrlStateModel.ts` 作为 Creation URL State Module,Interface 收口各玩法 `build*CreationUrlState`、拼图 `buildPuzzle*RuntimeUrlState`、URL state 非空判断和 runtime state key;新增 `src/components/platform-entry/platformPuzzleIdentityModel.ts` 作为拼图稳定身份 Module,`platformDraftGenerationShelfModel.ts` 仅 re-export 旧入口以保持兼容。`PlatformEntryFlowShellImpl.tsx` 只保留路由、URL 写入和网络副作用 Adapter。
|
||||||
|
- 追加决策:初始创作 URL 恢复的已处理、非创作路径、无私有 query、平台配置加载中、受保护数据暂不可读与可恢复判定也收口到 `resolveInitialCreationUrlRestoreDecision`;壳层只按 `skip`、`mark-handled`、`wait`、`restore` 执行 ref 标记或进入原恢复副作用。
|
||||||
- 影响范围:创作流程刷新恢复、拼图草稿 / 发布 runtime 深链、作品架打开试玩、跳一跳 / 敲木鱼 work-backed 恢复、Bark Battle / 宝贝识物本地草稿恢复。
|
- 影响范围:创作流程刷新恢复、拼图草稿 / 发布 runtime 深链、作品架打开试玩、跳一跳 / 敲木鱼 work-backed 恢复、Bark Battle / 宝贝识物本地草稿恢复。
|
||||||
- 验证方式:`npm run test -- src/components/platform-entry/platformCreationUrlStateModel.test.ts src/components/platform-entry/platformPuzzleIdentityModel.test.ts`、`npm run test -- src/services/creationUrlState.test.ts`、`npm run test -- src/components/platform-entry/platformDraftGenerationShelfModel.test.ts`、针对新 Module 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
|
- 验证方式:`npm run test -- src/components/platform-entry/platformCreationUrlStateModel.test.ts src/components/platform-entry/platformPuzzleIdentityModel.test.ts`、`npm run test -- src/services/creationUrlState.test.ts`、`npm run test -- src/components/platform-entry/platformDraftGenerationShelfModel.test.ts`、针对新 Module 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
|
||||||
- 关联文档:`docs/technical/【前端架构】CreationUrlStateModel收口计划-2026-06-03.md`。
|
- 关联文档:`docs/technical/【前端架构】CreationUrlStateModel收口计划-2026-06-03.md`。
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ AI 文字游戏模板接入以 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_
|
|||||||
|
|
||||||
平台入口创作生成通知、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)。
|
平台入口创作恢复 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)。
|
||||||
|
|
||||||
平台入口错误 / 完成弹窗的文案归一、来源格式、候选择一、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)。
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
## 决策
|
## 决策
|
||||||
|
|
||||||
- 新增 `src/components/platform-entry/platformCreationUrlStateModel.ts` 作为 Creation URL State Module。
|
- 新增 `src/components/platform-entry/platformCreationUrlStateModel.ts` 作为 Creation URL State Module。
|
||||||
- 该 Module 的 Interface 收口为各玩法 `build*CreationUrlState`、拼图 `buildPuzzle*RuntimeUrlState`、`normalizeCreationUrlValue`、`hasCreationUrlStateValue`、`hasPuzzleRuntimeUrlStateValue` 与 `buildPuzzleRuntimeUrlStateKey`。
|
- 该 Module 的 Interface 收口为各玩法 `build*CreationUrlState`、拼图 `buildPuzzle*RuntimeUrlState`、`normalizeCreationUrlValue`、`hasCreationUrlStateValue`、`hasPuzzleRuntimeUrlStateValue`、`buildPuzzleRuntimeUrlStateKey` 与初始创作 URL 恢复判定 `resolveInitialCreationUrlRestoreDecision`。
|
||||||
- 新增 `src/components/platform-entry/platformPuzzleIdentityModel.ts` 作为拼图稳定身份 Module,统一 `puzzle-session-*`、`puzzle-profile-*`、`puzzle-work-*` 的互推规则。
|
- 新增 `src/components/platform-entry/platformPuzzleIdentityModel.ts` 作为拼图稳定身份 Module,统一 `puzzle-session-*`、`puzzle-profile-*`、`puzzle-work-*` 的互推规则。
|
||||||
- `PlatformEntryFlowShellImpl.tsx` 保留 React state、路由、登录门禁、网络请求和 URL 写入副作用 Adapter;不再在壳层内定义各玩法 URL 状态构造函数。
|
- `PlatformEntryFlowShellImpl.tsx` 保留 React state、路由、登录门禁、网络请求和 URL 写入副作用 Adapter;不再在壳层内定义各玩法 URL 状态构造函数,也不直接内联初始恢复的已处理 / 等待 / 可恢复判定。
|
||||||
|
|
||||||
## Interface 约束
|
## Interface 约束
|
||||||
|
|
||||||
@@ -18,11 +18,12 @@
|
|||||||
- work-backed 玩法优先使用后端 work summary 的公开 `workId` / `profileId`;仅缺失时才回退 session draft。
|
- work-backed 玩法优先使用后端 work summary 的公开 `workId` / `profileId`;仅缺失时才回退 session draft。
|
||||||
- 拼图 runtime query 独立使用 `mode`、`runtimeSessionId`、`runtimeProfileId`、`runtimeLevelId`、`publicWorkCode`,不与创作恢复 query 混写。
|
- 拼图 runtime query 独立使用 `mode`、`runtimeSessionId`、`runtimeProfileId`、`runtimeLevelId`、`publicWorkCode`,不与创作恢复 query 混写。
|
||||||
- 拼图 draft runtime 若没有 `sourceSessionId`,只允许从 `puzzle-profile-*` 反推出 `puzzle-session-*`。
|
- 拼图 draft runtime 若没有 `sourceSessionId`,只允许从 `puzzle-profile-*` 反推出 `puzzle-session-*`。
|
||||||
|
- 初始创作 URL 恢复只在未处理、当前路径属于创作恢复路径、私有 query 有值、平台配置加载完成且受保护数据可读时执行;非创作路径或无私有 query 时标记已处理,加载中或暂不可读时等待。
|
||||||
|
|
||||||
## Depth / Leverage / Locality
|
## Depth / Leverage / Locality
|
||||||
|
|
||||||
- **Depth**:调用方只传玩法快照或作品摘要,即可得到规范化 URL state;各玩法字段优先级藏在 Module Implementation 内。
|
- **Depth**:调用方只传玩法快照或作品摘要,即可得到规范化 URL state;各玩法字段优先级藏在 Module Implementation 内。
|
||||||
- **Leverage**:新增或调整玩法恢复规则时,优先补 Module Interface 测试,再接壳层 Adapter。
|
- **Leverage**:新增或调整玩法恢复规则、恢复等待条件时,优先补 Module Interface 测试,再接壳层 Adapter。
|
||||||
- **Locality**:恢复 query、拼图 runtime query 和拼图稳定身份规则集中在两个小 Module,避免散落在页面壳、作品架和 runtime 打开逻辑中。
|
- **Locality**:恢复 query、拼图 runtime query 和拼图稳定身份规则集中在两个小 Module,避免散落在页面壳、作品架和 runtime 打开逻辑中。
|
||||||
|
|
||||||
## 验收
|
## 验收
|
||||||
|
|||||||
@@ -158,7 +158,6 @@ import {
|
|||||||
} from '../../services/creationEntryConfigService';
|
} from '../../services/creationEntryConfigService';
|
||||||
import {
|
import {
|
||||||
clearCreationUrlState,
|
clearCreationUrlState,
|
||||||
isCreationRestorePath,
|
|
||||||
readCreationUrlState,
|
readCreationUrlState,
|
||||||
writeCreationUrlState,
|
writeCreationUrlState,
|
||||||
} from '../../services/creationUrlState';
|
} from '../../services/creationUrlState';
|
||||||
@@ -408,9 +407,9 @@ import {
|
|||||||
buildSquareHoleCreationUrlState,
|
buildSquareHoleCreationUrlState,
|
||||||
buildVisualNovelCreationUrlState,
|
buildVisualNovelCreationUrlState,
|
||||||
buildWoodenFishCreationUrlState,
|
buildWoodenFishCreationUrlState,
|
||||||
hasCreationUrlStateValue,
|
|
||||||
hasPuzzleRuntimeUrlStateValue,
|
hasPuzzleRuntimeUrlStateValue,
|
||||||
normalizeCreationUrlValue,
|
normalizeCreationUrlValue,
|
||||||
|
resolveInitialCreationUrlRestoreDecision,
|
||||||
} from './platformCreationUrlStateModel';
|
} from './platformCreationUrlStateModel';
|
||||||
import { resolvePlatformCreationWorkDeleteConfirmationModel } from './platformCreationWorkDeleteFlow';
|
import { resolvePlatformCreationWorkDeleteConfirmationModel } from './platformCreationWorkDeleteFlow';
|
||||||
import {
|
import {
|
||||||
@@ -12136,23 +12135,22 @@ export function PlatformEntryFlowShellImpl({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (handledInitialCreationUrlStateRef.current) {
|
const restoreDecision = resolveInitialCreationUrlRestoreDecision({
|
||||||
|
handled: handledInitialCreationUrlStateRef.current,
|
||||||
|
pathname: window.location.pathname,
|
||||||
|
state: initialCreationUrlState,
|
||||||
|
isLoadingPlatform: platformBootstrap.isLoadingPlatform,
|
||||||
|
canReadProtectedData: platformBootstrap.canReadProtectedData,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (restoreDecision.type === 'skip' || restoreDecision.type === 'wait') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!isCreationRestorePath(window.location.pathname)) {
|
|
||||||
|
if (restoreDecision.type === 'mark-handled') {
|
||||||
handledInitialCreationUrlStateRef.current = true;
|
handledInitialCreationUrlStateRef.current = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!hasCreationUrlStateValue(initialCreationUrlState)) {
|
|
||||||
handledInitialCreationUrlStateRef.current = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (platformBootstrap.isLoadingPlatform) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!platformBootstrap.canReadProtectedData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handledInitialCreationUrlStateRef.current = true;
|
handledInitialCreationUrlStateRef.current = true;
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
hasCreationUrlStateValue,
|
hasCreationUrlStateValue,
|
||||||
hasPuzzleRuntimeUrlStateValue,
|
hasPuzzleRuntimeUrlStateValue,
|
||||||
normalizeCreationUrlValue,
|
normalizeCreationUrlValue,
|
||||||
|
resolveInitialCreationUrlRestoreDecision,
|
||||||
} from './platformCreationUrlStateModel';
|
} from './platformCreationUrlStateModel';
|
||||||
|
|
||||||
describe('platformCreationUrlStateModel', () => {
|
describe('platformCreationUrlStateModel', () => {
|
||||||
@@ -49,6 +50,50 @@ describe('platformCreationUrlStateModel', () => {
|
|||||||
expect(hasCreationUrlStateValue({})).toBe(false);
|
expect(hasCreationUrlStateValue({})).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('resolves initial creation url restore readiness', () => {
|
||||||
|
const readyParams = {
|
||||||
|
handled: false,
|
||||||
|
pathname: '/creation/puzzle/result',
|
||||||
|
state: { sessionId: 'puzzle-session-1' },
|
||||||
|
isLoadingPlatform: false,
|
||||||
|
canReadProtectedData: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveInitialCreationUrlRestoreDecision({
|
||||||
|
...readyParams,
|
||||||
|
handled: true,
|
||||||
|
}),
|
||||||
|
).toEqual({ type: 'skip' });
|
||||||
|
expect(
|
||||||
|
resolveInitialCreationUrlRestoreDecision({
|
||||||
|
...readyParams,
|
||||||
|
pathname: '/works/detail',
|
||||||
|
}),
|
||||||
|
).toEqual({ type: 'mark-handled' });
|
||||||
|
expect(
|
||||||
|
resolveInitialCreationUrlRestoreDecision({
|
||||||
|
...readyParams,
|
||||||
|
state: {},
|
||||||
|
}),
|
||||||
|
).toEqual({ type: 'mark-handled' });
|
||||||
|
expect(
|
||||||
|
resolveInitialCreationUrlRestoreDecision({
|
||||||
|
...readyParams,
|
||||||
|
isLoadingPlatform: true,
|
||||||
|
}),
|
||||||
|
).toEqual({ type: 'wait' });
|
||||||
|
expect(
|
||||||
|
resolveInitialCreationUrlRestoreDecision({
|
||||||
|
...readyParams,
|
||||||
|
canReadProtectedData: false,
|
||||||
|
}),
|
||||||
|
).toEqual({ type: 'wait' });
|
||||||
|
expect(resolveInitialCreationUrlRestoreDecision(readyParams)).toEqual({
|
||||||
|
type: 'restore',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('builds creation restore state for core session based plays', () => {
|
test('builds creation restore state for core session based plays', () => {
|
||||||
expect(
|
expect(
|
||||||
buildBigFishCreationUrlState({
|
buildBigFishCreationUrlState({
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/co
|
|||||||
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
||||||
import type { SquareHoleSessionSnapshot } from '../../../packages/shared/src/contracts/squareHoleAgent';
|
import type { SquareHoleSessionSnapshot } from '../../../packages/shared/src/contracts/squareHoleAgent';
|
||||||
import type { VisualNovelAgentSessionSnapshot } from '../../../packages/shared/src/contracts/visualNovel';
|
import type { VisualNovelAgentSessionSnapshot } from '../../../packages/shared/src/contracts/visualNovel';
|
||||||
import type { CreationUrlState } from '../../services/creationUrlState';
|
import {
|
||||||
|
type CreationUrlState,
|
||||||
|
isCreationRestorePath,
|
||||||
|
} from '../../services/creationUrlState';
|
||||||
import type {
|
import type {
|
||||||
JumpHopSessionSnapshotResponse,
|
JumpHopSessionSnapshotResponse,
|
||||||
JumpHopWorkProfileResponse,
|
JumpHopWorkProfileResponse,
|
||||||
@@ -57,6 +60,37 @@ export function buildPuzzleRuntimeUrlStateKey(state: PuzzleRuntimeUrlState) {
|
|||||||
].join('|');
|
].join('|');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type InitialCreationUrlRestoreDecision =
|
||||||
|
| { type: 'skip' }
|
||||||
|
| { type: 'mark-handled' }
|
||||||
|
| { type: 'wait' }
|
||||||
|
| { type: 'restore' };
|
||||||
|
|
||||||
|
export function resolveInitialCreationUrlRestoreDecision(params: {
|
||||||
|
handled: boolean;
|
||||||
|
pathname: string | undefined;
|
||||||
|
state: CreationUrlState;
|
||||||
|
isLoadingPlatform: boolean;
|
||||||
|
canReadProtectedData: boolean;
|
||||||
|
}): InitialCreationUrlRestoreDecision {
|
||||||
|
if (params.handled) {
|
||||||
|
return { type: 'skip' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isCreationRestorePath(params.pathname) ||
|
||||||
|
!hasCreationUrlStateValue(params.state)
|
||||||
|
) {
|
||||||
|
return { type: 'mark-handled' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.isLoadingPlatform || !params.canReadProtectedData) {
|
||||||
|
return { type: 'wait' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type: 'restore' };
|
||||||
|
}
|
||||||
|
|
||||||
export function buildBigFishCreationUrlState(
|
export function buildBigFishCreationUrlState(
|
||||||
session: BigFishSessionSnapshotResponse | null,
|
session: BigFishSessionSnapshotResponse | null,
|
||||||
): CreationUrlState {
|
): CreationUrlState {
|
||||||
|
|||||||
Reference in New Issue
Block a user