合并 master 并修复架构分支回归

合入 master 最新的认证、玩法契约与推荐页改动。

修复拼图草稿生成、推荐页下一关和公开详情访客试玩回归。

修复抓大鹅草稿试玩鉴权与首屏推荐详情测试入口。

补齐相关测试夹具、文档与团队记忆更新。
This commit is contained in:
2026-06-07 21:35:47 +08:00
80 changed files with 2627 additions and 511 deletions

View File

@@ -115,6 +115,7 @@ import { buildCustomWorldPlayableCharacters } from '../../data/characterPresets'
import {
buildPublicWorkStagePath,
pushAppHistoryPath,
resolvePathForSelectionStage,
} from '../../routing/appPageRoutes';
import { resolveWorkNotFoundRecoveryAction } from '../../routing/runtimeNotFoundRecovery';
import {
@@ -371,7 +372,6 @@ import {
isPersistedBarkBattleDraftGenerating,
} from '../custom-world-home/creationWorkShelf';
import {
buildPlatformRecommendFeedEntries,
selectAdjacentPlatformRecommendEntry,
} from '../rpg-entry/rpgEntryPublicGalleryViewModel';
import {
@@ -493,7 +493,9 @@ import {
import {
canExposePublicWork,
EDUTAINMENT_HIDDEN_MESSAGE,
filterGeneralPublicWorks,
} from './platformEdutainmentVisibility';
import { buildPlatformRecommendedEntries } from './platformRecommendation';
import { PlatformEntryCreationTypeModal } from './PlatformEntryCreationTypeModal';
import type { PlatformCreationTypeId } from './platformEntryCreationTypes';
import {
@@ -2872,10 +2874,10 @@ export function PlatformEntryFlowShellImpl({
publicGalleryFeeds;
const recommendRuntimeEntries = useMemo(
() =>
buildPlatformRecommendFeedEntries(
featuredGalleryEntries,
latestGalleryEntries,
),
buildPlatformRecommendedEntries({
featuredEntries: filterGeneralPublicWorks(featuredGalleryEntries),
latestEntries: filterGeneralPublicWorks(latestGalleryEntries),
}),
[featuredGalleryEntries, latestGalleryEntries],
);
@@ -4440,6 +4442,18 @@ export function PlatformEntryFlowShellImpl({
activePuzzleBackgroundCompileTask?.session ?? puzzleSession;
const puzzleGenerationViewPayload =
activePuzzleBackgroundCompileTask?.payload ?? puzzleFormDraftPayload;
const puzzleGenerationViewStateRef = useRef(puzzleGenerationViewState);
const puzzleGenerationViewPayloadRef = useRef(puzzleGenerationViewPayload);
const setPuzzleSessionRef = useRef(puzzleFlow.setSession);
useEffect(() => {
puzzleGenerationViewStateRef.current = puzzleGenerationViewState;
}, [puzzleGenerationViewState]);
useEffect(() => {
puzzleGenerationViewPayloadRef.current = puzzleGenerationViewPayload;
}, [puzzleGenerationViewPayload]);
useEffect(() => {
setPuzzleSessionRef.current = puzzleFlow.setSession;
}, [puzzleFlow.setSession]);
const puzzleGenerationViewError =
activePuzzleBackgroundCompileTask?.error ?? puzzleError;
const isPuzzleGenerationViewBusy =
@@ -4835,21 +4849,21 @@ export function PlatformEntryFlowShellImpl({
if (hasRecoverableGeneratedPuzzleDraft(latestSession)) {
const payload =
puzzleGenerationViewPayload ??
puzzleGenerationViewPayloadRef.current ??
buildPuzzleFormPayloadFromSession(latestSession);
const generationState =
puzzleGenerationViewState ??
puzzleGenerationViewStateRef.current ??
createPuzzleDraftGenerationStateFromPayload(payload, latestSession);
await recoverCompletedPuzzleDraftGeneration({
sessionId: latestSession.sessionId,
payload,
generationState,
setSession: setPuzzleSession,
setSession: setPuzzleSessionRef.current,
});
return;
}
setPuzzleSession(latestSession);
setPuzzleSessionRef.current(latestSession);
setPuzzleBackgroundCompileTasks((current) => {
const task = current[activePuzzleGenerationSessionId];
if (!task) {
@@ -4892,11 +4906,8 @@ export function PlatformEntryFlowShellImpl({
};
}, [
activePuzzleGenerationSessionId,
puzzleGenerationViewPayload,
puzzleGenerationViewState,
recoverCompletedPuzzleDraftGeneration,
shouldPollPuzzleGenerationSession,
setPuzzleSession,
]);
const match3DGeneratingSessionId =
@@ -5258,27 +5269,48 @@ export function PlatformEntryFlowShellImpl({
);
setPuzzleOperation(response.operation);
const openResult = isViewingPuzzleGeneration(nextSession.sessionId);
const readyGenerationState =
resolveFinishedMiniGameDraftGenerationState(
generationState,
'ready',
{
completedAssetCount: 1,
totalAssetCount: 1,
},
);
const isCompileReady = isPuzzleCompileActionReady(response.session);
const nextGenerationState = isCompileReady
? resolveFinishedMiniGameDraftGenerationState(
generationState,
'ready',
{
completedAssetCount: 1,
totalAssetCount: 1,
},
)
: mergePuzzleSessionProgressIntoGenerationState(
generationState,
response.session,
);
setPuzzleBackgroundCompileTasks((current) => ({
...current,
[nextSession.sessionId]: {
session: response.session,
payload,
generationState: readyGenerationState,
generationState: nextGenerationState,
error: null,
},
}));
if (isViewingPuzzleGeneration(nextSession.sessionId)) {
puzzleFlow.setSession(response.session);
setPuzzleGenerationState(readyGenerationState);
setPuzzleGenerationState(nextGenerationState);
}
if (!isCompileReady) {
markDraftGenerating('puzzle', [
response.session.sessionId,
buildPuzzleResultWorkId(response.session.sessionId),
response.session.publishedProfileId,
buildPuzzleResultProfileId(response.session.sessionId),
]);
markPendingDraftGenerating(
'puzzle',
response.session.sessionId,
buildPendingPuzzleDraftMetadata(payload),
);
void refreshPuzzleShelf();
return;
}
const profileId =
@@ -6870,10 +6902,10 @@ export function PlatformEntryFlowShellImpl({
profileId: targetProfileId,
mode: 'play' as const,
};
const runtimeGuestOptions = await buildRecommendRuntimeAuthOptions(
authUi,
options.embedded,
);
const runtimeGuestOptions =
options.embedded || workDetail.summary.publishStatus === 'draft'
? await buildRecommendRuntimeAuthOptions(authUi, true)
: {};
const { run } = options.embedded
? await startVisualNovelRun(
targetProfileId,
@@ -8818,7 +8850,11 @@ export function PlatformEntryFlowShellImpl({
profile: Match3DWorkProfile | Match3DWorkSummary,
returnStage: 'match3d-result' | 'work-detail' = 'match3d-result',
mirrorErrorToPublicDetail = false,
options: { embedded?: boolean; itemTypeCountOverride?: number } = {},
options: {
embedded?: boolean;
authMode?: PuzzleRuntimeAuthMode;
itemTypeCountOverride?: number;
} = {},
) => {
if (isMatch3DBusy) {
return false;
@@ -8857,10 +8893,10 @@ export function PlatformEntryFlowShellImpl({
runtimeProfile.generatedBackgroundAsset,
{ expireSeconds: 300 },
);
const runtimeGuestOptions = await buildRecommendRuntimeAuthOptions(
authUi,
options.embedded,
);
const runtimeGuestOptions =
options.authMode === 'isolated'
? RECOMMEND_RUNTIME_BACKGROUND_AUTH_OPTIONS
: await buildRecommendRuntimeAuthOptions(authUi, options.embedded);
const runtimeOptions = {
...runtimeGuestOptions,
...(typeof options.itemTypeCountOverride === 'number'
@@ -9657,10 +9693,6 @@ export function PlatformEntryFlowShellImpl({
? await buildRecommendRuntimeGuestOptions()
: {};
const targetProfileId = _target?.profileId?.trim() ?? '';
const preferSimilarWork =
activeRecommendRuntimeKind === 'puzzle' &&
puzzleRuntimeReturnStage === 'platform' &&
puzzleRun.nextLevelMode === 'sameWork';
if (puzzleRun.nextLevelMode === 'similarWorks' && targetProfileId) {
const itemPromise =
selectedPuzzleDetail?.profileId === targetProfileId
@@ -9700,34 +9732,10 @@ export function PlatformEntryFlowShellImpl({
puzzleRuntimeAuthMode === 'isolated'
? await advancePuzzleNextLevel(
puzzleRun.runId,
preferSimilarWork ? { preferSimilarWork: true } : {},
{},
runtimeGuestOptions,
)
: await advancePuzzleNextLevel(
puzzleRun.runId,
preferSimilarWork ? { preferSimilarWork: true } : {},
);
const nextProfileId = run.currentLevel?.profileId?.trim() ?? '';
if (
nextProfileId &&
selectedPuzzleDetail?.profileId !== nextProfileId
) {
const item = await getPuzzleGalleryDetail(nextProfileId).then(
(response) => response.item,
);
const nextRecommendEntry = mapPuzzleWorkToPlatformGalleryCard(item);
setPuzzleGalleryEntries((current) => {
const nextEntries = current.filter(
(entry) => entry.profileId !== item.profileId,
);
nextEntries.push(item);
return nextEntries;
});
setSelectedPuzzleDetail(item);
setActiveRecommendEntryKey(
getPlatformPublicGalleryEntryKey(nextRecommendEntry),
);
}
: await advancePuzzleNextLevel(puzzleRun.runId, {});
setPuzzleRun(run);
} catch (error) {
setPuzzleError(resolvePuzzleErrorMessage(error, '准备下一关失败。'));
@@ -9739,12 +9747,8 @@ export function PlatformEntryFlowShellImpl({
[
isPuzzleBusy,
isPuzzleLeaderboardBusy,
activeRecommendRuntimeKind,
puzzleRun,
puzzleRuntimeReturnStage,
puzzleRuntimeAuthMode,
setActiveRecommendEntryKey,
setPuzzleGalleryEntries,
resolvePuzzleErrorMessage,
selectedPuzzleDetail,
setIsPuzzleBusy,
@@ -10956,12 +10960,18 @@ export function PlatformEntryFlowShellImpl({
}
try {
const detail = await jumpHopClient.getWorkDetail(item.profileId);
const detail = await jumpHopClient.getWorkDetail(item.profileId, {
audience: 'creation',
});
setJumpHopSession(null);
setJumpHopRun(null);
setJumpHopWork(detail.item);
setJumpHopRuntimeReturnStage('jump-hop-result');
enterCreateTab();
pushAppHistoryPath(resolvePathForSelectionStage('jump-hop-result'));
writeCreationUrlState(
buildJumpHopCreationUrlState({ work: detail.item }),
);
setSelectionStage('jump-hop-result');
} catch (error) {
setJumpHopError(
@@ -11572,6 +11582,8 @@ export function PlatformEntryFlowShellImpl({
const started = await startMatch3DRunFromProfile(
normalizedProfile,
'match3d-result',
false,
{ authMode: 'isolated' },
);
if (!started) {
setMatch3DProfile(normalizedProfile);
@@ -12147,7 +12159,11 @@ export function PlatformEntryFlowShellImpl({
let work: JumpHopWorkProfileResponse | null = null;
try {
if (profileId) {
work = (await jumpHopClient.getWorkDetail(profileId)).item;
work = (
await jumpHopClient.getWorkDetail(profileId, {
audience: 'creation',
})
).item;
}
} catch {
work = null;
@@ -12525,6 +12541,13 @@ export function PlatformEntryFlowShellImpl({
setActiveRecommendEntryKey(entryKey);
setActiveRecommendRuntimeKind(runtimeKind);
setActiveRecommendRuntimeError(null);
if (
runtimeKind === 'puzzle' &&
(isPuzzleBusy || puzzleStartInFlightKeyRef.current !== null)
) {
setIsStartingRecommendEntry(false);
return;
}
setIsStartingRecommendEntry(true);
try {
@@ -12659,6 +12682,7 @@ export function PlatformEntryFlowShellImpl({
[
activeRecommendEntryKey,
barkBattleGalleryEntries,
isPuzzleBusy,
saveAndExitRecommendPuzzleRuntime,
selectedPuzzleDetail,
setBarkBattleError,
@@ -12700,6 +12724,23 @@ export function PlatformEntryFlowShellImpl({
selectRecommendRuntimeEntry,
],
);
const resolveRecommendRuntimeEntryKeyByProfileId = useCallback(
(profileId: string | null | undefined) => {
const normalizedProfileId = profileId?.trim();
if (!normalizedProfileId) {
return null;
}
const matchedEntry =
recommendRuntimeEntries.find(
(entry) => entry.profileId === normalizedProfileId,
) ?? null;
return matchedEntry
? getPlatformPublicGalleryEntryKey(matchedEntry)
: null;
},
[recommendRuntimeEntries],
);
const recommendRuntimeContent = useMemo(() => {
if (
@@ -12861,7 +12902,13 @@ export function PlatformEntryFlowShellImpl({
void dragPuzzlePiece(payload);
}}
onAdvanceNextLevel={(target) => {
void advancePuzzleLevel(target);
const targetEntryKey = resolveRecommendRuntimeEntryKeyByProfileId(
target?.profileId,
);
void selectAdjacentRecommendRuntimeEntry(
1,
targetEntryKey ?? activeRecommendEntryKey,
);
}}
onRestartLevel={() => {
void restartPuzzleCurrentLevel();
@@ -13115,9 +13162,10 @@ export function PlatformEntryFlowShellImpl({
squareHoleRun,
submitBigFishInput,
submitVisualNovelRuntimeAction,
advancePuzzleLevel,
dragPuzzlePiece,
resolveRecommendRuntimeEntryKeyByProfileId,
restartPuzzleCurrentLevel,
selectAdjacentRecommendRuntimeEntry,
setSquareHoleError,
swapPuzzlePiecesInRun,
syncPuzzleRuntimeTimeout,
@@ -13166,7 +13214,8 @@ export function PlatformEntryFlowShellImpl({
isLoadingPlatform: platformBootstrap.isLoadingPlatform,
entries: recommendRuntimeEntries,
activeEntryKey: activeRecommendEntryKey,
isStarting: isStartingRecommendEntry,
isStarting: isStartingRecommendEntry || isPuzzleBusy,
hasStartError: Boolean(activeRecommendRuntimeError),
readyState: {
activeKind: activeRecommendRuntimeKind,
hasBabyObjectMatchDraft: Boolean(babyObjectMatchDraft),
@@ -13198,10 +13247,12 @@ export function PlatformEntryFlowShellImpl({
}, [
activeRecommendEntryKey,
activeRecommendRuntimeKind,
activeRecommendRuntimeError,
babyObjectMatchDraft,
bigFishRun,
jumpHopRun,
isStartingRecommendEntry,
isPuzzleBusy,
match3dRun,
platformBootstrap.isLoadingPlatform,
platformBootstrap.platformTab,
@@ -13952,8 +14003,11 @@ export function PlatformEntryFlowShellImpl({
canDeleteBigFish: isBigFishCreationVisible,
canDeleteMatch3D: true,
canDeleteSquareHole: isSquareHoleCreationVisible,
canDeleteJumpHop: isJumpHopCreationVisible,
canDeleteWoodenFish: true,
canDeletePuzzle: true,
canDeleteBabyObjectMatch: isBabyObjectMatchVisible,
canDeleteBarkBattle: true,
canDeleteVisualNovel: true,
onOpenRpgDraft: (item) => {
runProtectedAction(() => {
@@ -13993,12 +14047,16 @@ export function PlatformEntryFlowShellImpl({
});
}
: undefined,
onDeleteJumpHop: isJumpHopCreationVisible
? handleDeleteJumpHopWork
: undefined,
onOpenWoodenFishDetail: (item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
void openWoodenFishDraft(item);
});
},
onDeleteWoodenFish: handleDeleteWoodenFishWork,
onOpenMatch3DDetail: (item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
@@ -14038,6 +14096,7 @@ export function PlatformEntryFlowShellImpl({
openBarkBattleDraft(item);
});
},
onDeleteBarkBattle: handleDeleteBarkBattleWork,
onOpenVisualNovelDetail: (item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
@@ -14057,11 +14116,14 @@ export function PlatformEntryFlowShellImpl({
handleClaimPuzzlePointIncentive,
handleDeleteBabyObjectMatchWork,
handleDeleteBigFishWork,
handleDeleteBarkBattleWork,
handleDeleteJumpHopWork,
handleDeleteMatch3DWork,
handleDeletePublishedWork,
handleDeletePuzzleWork,
handleDeleteSquareHoleWork,
handleDeleteVisualNovelWork,
handleDeleteWoodenFishWork,
isBabyObjectMatchVisible,
isBigFishCreationVisible,
isJumpHopCreationVisible,
@@ -14884,7 +14946,13 @@ export function PlatformEntryFlowShellImpl({
normalizedProfile,
'match3d-result',
false,
options,
{
...options,
authMode:
normalizedProfile.publicationStatus === 'draft'
? 'isolated'
: undefined,
},
);
}}
/>