From 0d2d391cb277a895f6c4cabcfc98115d537e992c Mon Sep 17 00:00:00 2001 From: kdletters Date: Thu, 4 Jun 2026 01:43:31 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=94=B6=E5=8F=A3=E5=88=9B?= =?UTF-8?q?=E4=BD=9C=E7=9B=B4=E8=BE=BE=E6=81=A2=E5=A4=8D=E5=88=A4=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .hermes/shared-memory/decision-log.md | 1 + docs/README.md | 2 +- ...】CreationUrlStateModel收口计划-2026-06-03.md | 7 +-- .../PlatformEntryFlowShellImpl.tsx | 26 +++++------ .../platformCreationUrlStateModel.test.ts | 45 +++++++++++++++++++ .../platformCreationUrlStateModel.ts | 36 ++++++++++++++- 6 files changed, 98 insertions(+), 19 deletions(-) diff --git a/.hermes/shared-memory/decision-log.md b/.hermes/shared-memory/decision-log.md index 43701f76..919b3850 100644 --- a/.hermes/shared-memory/decision-log.md +++ b/.hermes/shared-memory/decision-log.md @@ -1331,6 +1331,7 @@ - 背景:平台壳内散落各玩法创作恢复 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。 +- 追加决策:初始创作 URL 恢复的已处理、非创作路径、无私有 query、平台配置加载中、受保护数据暂不可读与可恢复判定也收口到 `resolveInitialCreationUrlRestoreDecision`;壳层只按 `skip`、`mark-handled`、`wait`、`restore` 执行 ref 标记或进入原恢复副作用。 - 影响范围:创作流程刷新恢复、拼图草稿 / 发布 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`。 - 关联文档:`docs/technical/【前端架构】CreationUrlStateModel收口计划-2026-06-03.md`。 diff --git a/docs/README.md b/docs/README.md index a436abe6..b52ab02d 100644 --- a/docs/README.md +++ b/docs/README.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)。 -平台入口创作恢复 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)。 diff --git a/docs/technical/【前端架构】CreationUrlStateModel收口计划-2026-06-03.md b/docs/technical/【前端架构】CreationUrlStateModel收口计划-2026-06-03.md index 7c9c9d3f..d50cd8e5 100644 --- a/docs/technical/【前端架构】CreationUrlStateModel收口计划-2026-06-03.md +++ b/docs/technical/【前端架构】CreationUrlStateModel收口计划-2026-06-03.md @@ -7,9 +7,9 @@ ## 决策 - 新增 `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-*` 的互推规则。 -- `PlatformEntryFlowShellImpl.tsx` 保留 React state、路由、登录门禁、网络请求和 URL 写入副作用 Adapter;不再在壳层内定义各玩法 URL 状态构造函数。 +- `PlatformEntryFlowShellImpl.tsx` 保留 React state、路由、登录门禁、网络请求和 URL 写入副作用 Adapter;不再在壳层内定义各玩法 URL 状态构造函数,也不直接内联初始恢复的已处理 / 等待 / 可恢复判定。 ## Interface 约束 @@ -18,11 +18,12 @@ - work-backed 玩法优先使用后端 work summary 的公开 `workId` / `profileId`;仅缺失时才回退 session draft。 - 拼图 runtime query 独立使用 `mode`、`runtimeSessionId`、`runtimeProfileId`、`runtimeLevelId`、`publicWorkCode`,不与创作恢复 query 混写。 - 拼图 draft runtime 若没有 `sourceSessionId`,只允许从 `puzzle-profile-*` 反推出 `puzzle-session-*`。 +- 初始创作 URL 恢复只在未处理、当前路径属于创作恢复路径、私有 query 有值、平台配置加载完成且受保护数据可读时执行;非创作路径或无私有 query 时标记已处理,加载中或暂不可读时等待。 ## Depth / Leverage / Locality - **Depth**:调用方只传玩法快照或作品摘要,即可得到规范化 URL state;各玩法字段优先级藏在 Module Implementation 内。 -- **Leverage**:新增或调整玩法恢复规则时,优先补 Module Interface 测试,再接壳层 Adapter。 +- **Leverage**:新增或调整玩法恢复规则、恢复等待条件时,优先补 Module Interface 测试,再接壳层 Adapter。 - **Locality**:恢复 query、拼图 runtime query 和拼图稳定身份规则集中在两个小 Module,避免散落在页面壳、作品架和 runtime 打开逻辑中。 ## 验收 diff --git a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx index c9b26af8..665af49c 100644 --- a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx +++ b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx @@ -158,7 +158,6 @@ import { } from '../../services/creationEntryConfigService'; import { clearCreationUrlState, - isCreationRestorePath, readCreationUrlState, writeCreationUrlState, } from '../../services/creationUrlState'; @@ -408,9 +407,9 @@ import { buildSquareHoleCreationUrlState, buildVisualNovelCreationUrlState, buildWoodenFishCreationUrlState, - hasCreationUrlStateValue, hasPuzzleRuntimeUrlStateValue, normalizeCreationUrlValue, + resolveInitialCreationUrlRestoreDecision, } from './platformCreationUrlStateModel'; import { resolvePlatformCreationWorkDeleteConfirmationModel } from './platformCreationWorkDeleteFlow'; import { @@ -12136,23 +12135,22 @@ export function PlatformEntryFlowShellImpl({ ); 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; } - if (!isCreationRestorePath(window.location.pathname)) { + + if (restoreDecision.type === 'mark-handled') { handledInitialCreationUrlStateRef.current = true; return; } - if (!hasCreationUrlStateValue(initialCreationUrlState)) { - handledInitialCreationUrlStateRef.current = true; - return; - } - if (platformBootstrap.isLoadingPlatform) { - return; - } - if (!platformBootstrap.canReadProtectedData) { - return; - } handledInitialCreationUrlStateRef.current = true; diff --git a/src/components/platform-entry/platformCreationUrlStateModel.test.ts b/src/components/platform-entry/platformCreationUrlStateModel.test.ts index 54cf53ef..90ca06cc 100644 --- a/src/components/platform-entry/platformCreationUrlStateModel.test.ts +++ b/src/components/platform-entry/platformCreationUrlStateModel.test.ts @@ -32,6 +32,7 @@ import { hasCreationUrlStateValue, hasPuzzleRuntimeUrlStateValue, normalizeCreationUrlValue, + resolveInitialCreationUrlRestoreDecision, } from './platformCreationUrlStateModel'; describe('platformCreationUrlStateModel', () => { @@ -49,6 +50,50 @@ describe('platformCreationUrlStateModel', () => { 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', () => { expect( buildBigFishCreationUrlState({ diff --git a/src/components/platform-entry/platformCreationUrlStateModel.ts b/src/components/platform-entry/platformCreationUrlStateModel.ts index 6ce46fa8..0961b1fb 100644 --- a/src/components/platform-entry/platformCreationUrlStateModel.ts +++ b/src/components/platform-entry/platformCreationUrlStateModel.ts @@ -6,7 +6,10 @@ import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/co import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary'; import type { SquareHoleSessionSnapshot } from '../../../packages/shared/src/contracts/squareHoleAgent'; import type { VisualNovelAgentSessionSnapshot } from '../../../packages/shared/src/contracts/visualNovel'; -import type { CreationUrlState } from '../../services/creationUrlState'; +import { + type CreationUrlState, + isCreationRestorePath, +} from '../../services/creationUrlState'; import type { JumpHopSessionSnapshotResponse, JumpHopWorkProfileResponse, @@ -57,6 +60,37 @@ export function buildPuzzleRuntimeUrlStateKey(state: PuzzleRuntimeUrlState) { ].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( session: BigFishSessionSnapshotResponse | null, ): CreationUrlState {